mirror of https://github.com/D4-project/d4-core
chg: [Analyzer Queue] add template: edit queue
parent
14d3a650e5
commit
ab261a6bd2
|
@ -162,12 +162,15 @@ def get_queue_size(queue_uuid, format_type, extended_type=None):
|
||||||
return length
|
return length
|
||||||
|
|
||||||
def get_queue_format_type(queue_uuid):
|
def get_queue_format_type(queue_uuid):
|
||||||
return r_serv_metadata.hget('analyzer:{}'.format(queue_uuid), 'type')
|
return int(r_serv_metadata.hget('analyzer:{}'.format(queue_uuid), 'type'))
|
||||||
|
|
||||||
def get_queue_extended_type(queue_uuid):
|
def get_queue_extended_type(queue_uuid):
|
||||||
return r_serv_metadata.hget('analyzer:{}'.format(queue_uuid), 'metatype')
|
return r_serv_metadata.hget('analyzer:{}'.format(queue_uuid), 'metatype')
|
||||||
|
|
||||||
def get_queue_metadata(queue_uuid, format_type=None, extended_type=None, f_date='str_date', force_is_group_queue=False):
|
def is_queue_group_of_sensors(queue_uuid):
|
||||||
|
return r_serv_metadata.exists('analyzer_sensor_group:{}'.format(queue_uuid))
|
||||||
|
|
||||||
|
def get_queue_metadata(queue_uuid, format_type=None, extended_type=None, f_date='str_date', is_group=None, force_is_group_queue=False):
|
||||||
dict_queue_meta = {}
|
dict_queue_meta = {}
|
||||||
dict_queue_meta['uuid'] = queue_uuid
|
dict_queue_meta['uuid'] = queue_uuid
|
||||||
dict_queue_meta['size_limit'] = get_queue_max_size(queue_uuid)
|
dict_queue_meta['size_limit'] = get_queue_max_size(queue_uuid)
|
||||||
|
@ -187,10 +190,13 @@ def get_queue_metadata(queue_uuid, format_type=None, extended_type=None, f_date=
|
||||||
|
|
||||||
dict_queue_meta['length'] = get_queue_size(queue_uuid, format_type, extended_type=extended_type)
|
dict_queue_meta['length'] = get_queue_size(queue_uuid, format_type, extended_type=extended_type)
|
||||||
|
|
||||||
if force_is_group_queue:
|
if is_group and not force_is_group_queue:
|
||||||
dict_queue_meta['is_group_queue'] = True
|
dict_queue_meta['is_group_queue'] = is_queue_group_of_sensors(queue_uuid)
|
||||||
else:
|
else:
|
||||||
dict_queue_meta['is_group_queue'] = False
|
if force_is_group_queue:
|
||||||
|
dict_queue_meta['is_group_queue'] = True
|
||||||
|
else:
|
||||||
|
dict_queue_meta['is_group_queue'] = False
|
||||||
|
|
||||||
return dict_queue_meta
|
return dict_queue_meta
|
||||||
|
|
||||||
|
@ -207,11 +213,30 @@ def edit_queue_max_size(queue_uuid, max_size):
|
||||||
if r_serv_metadata.exists('analyzer:{}'.format(queue_uuid)) and max_size > 0:
|
if r_serv_metadata.exists('analyzer:{}'.format(queue_uuid)) and max_size > 0:
|
||||||
r_serv_metadata.hset('analyzer:{}'.format(queue_uuid), 'max_size', max_size)
|
r_serv_metadata.hset('analyzer:{}'.format(queue_uuid), 'max_size', max_size)
|
||||||
|
|
||||||
|
def edit_queue_sensors_set(queue_uuid, l_sensors_uuid):
|
||||||
|
format_type = get_queue_format_type(queue_uuid)
|
||||||
|
set_current_sensors = get_queue_group_all_sensors(queue_uuid)
|
||||||
|
l_new_sensors_uuid = []
|
||||||
|
for sensor_uuid in l_sensors_uuid:
|
||||||
|
l_new_sensors_uuid.append(sensor_uuid.replace('-', ''))
|
||||||
|
|
||||||
|
sensors_to_add = l_sensors_uuid.difference(set_current_sensors)
|
||||||
|
sensors_to_remove = set_current_sensors.difference(l_sensors_uuid)
|
||||||
|
|
||||||
|
for sensor_uuid in sensors_to_add:
|
||||||
|
r_serv_metadata.sadd('analyzer_sensor_group:{}'.format(queue_uuid), sensor_uuid)
|
||||||
|
r_serv_metadata.sadd('sensor:queues:{}:{}'.format(format_type, sensor_uuid), queue_uuid)
|
||||||
|
|
||||||
|
for sensor_uuid in sensors_to_remove:
|
||||||
|
r_serv_metadata.srem('analyzer_sensor_group:{}'.format(queue_uuid), sensor_uuid)
|
||||||
|
r_serv_metadata.srem('sensor:queues:{}:{}'.format(format_type, sensor_uuid), queue_uuid)
|
||||||
|
|
||||||
|
|
||||||
# create queu by type or by group of uuid
|
# create queu by type or by group of uuid
|
||||||
# # TODO: add size limit
|
# # TODO: add size limit
|
||||||
def create_queues(format_type, queue_uuid=None, l_uuid=[], queue_type='list', metatype_name=None, description=None):
|
def create_queues(format_type, queue_uuid=None, l_uuid=[], queue_type='list', metatype_name=None, description=None):
|
||||||
format_type = sanitize_queue_type(format_type)
|
format_type = sanitize_queue_type(format_type)
|
||||||
|
|
||||||
if not d4_type.is_accepted_format_type(format_type):
|
if not d4_type.is_accepted_format_type(format_type):
|
||||||
return {'error': 'Invalid type'}
|
return {'error': 'Invalid type'}
|
||||||
|
|
||||||
|
@ -271,12 +296,6 @@ def add_data_to_queue(sensor_uuid, format_type, data):
|
||||||
r_serv_analyzer.ltrim('analyzer:{}:{}'.format(format_type, queue_uuid), 0, analyser_queue_max_size)
|
r_serv_analyzer.ltrim('analyzer:{}:{}'.format(format_type, queue_uuid), 0, analyser_queue_max_size)
|
||||||
|
|
||||||
|
|
||||||
def is_queue_group_of_sensors(queue_uuid):
|
|
||||||
if r_serv_metadata.exists('analyzer_sensor_group:{}'.format(queue_uuid)):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def flush_queue(queue_uuid, format_type):
|
def flush_queue(queue_uuid, format_type):
|
||||||
r_serv_analyzer.delete('analyzer:{}:{}'.format(format_type, queue_uuid))
|
r_serv_analyzer.delete('analyzer:{}:{}'.format(format_type, queue_uuid))
|
||||||
|
|
||||||
|
@ -302,7 +321,7 @@ def remove_queues(queue_uuid, format_type, metatype_name=None):
|
||||||
r_serv_metadata.delete('analyzer:{}'.format(queue_uuid))
|
r_serv_metadata.delete('analyzer:{}'.format(queue_uuid))
|
||||||
|
|
||||||
# delete queue group of sensors uuid
|
# delete queue group of sensors uuid
|
||||||
l_sensors_uuid = get_queue_group_all_sensors(queue_uuid, r_list=None)
|
l_sensors_uuid = get_queue_group_all_sensors(queue_uuid)
|
||||||
if l_sensors_uuid:
|
if l_sensors_uuid:
|
||||||
r_serv_metadata.delete('analyzer_sensor_group:{}'.format(queue_uuid))
|
r_serv_metadata.delete('analyzer_sensor_group:{}'.format(queue_uuid))
|
||||||
for sensor_uuid in l_sensors_uuid:
|
for sensor_uuid in l_sensors_uuid:
|
||||||
|
|
|
@ -30,6 +30,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
for extended_type in d4_type.get_all_accepted_extended_type():
|
for extended_type in d4_type.get_all_accepted_extended_type():
|
||||||
for queue_uuid in Analyzer_Queue.get_all_queues_by_extended_type(extended_type):
|
for queue_uuid in Analyzer_Queue.get_all_queues_by_extended_type(extended_type):
|
||||||
|
r_serv_metadata.hset('analyzer:{}'.format(queue_uuid), 'type', 254)
|
||||||
r_serv_metadata.hset('analyzer:{}'.format(queue_uuid), 'metatype', extended_type)
|
r_serv_metadata.hset('analyzer:{}'.format(queue_uuid), 'metatype', extended_type)
|
||||||
r_serv_metadata.sadd('all:analyzer:extended_type', extended_type)
|
r_serv_metadata.sadd('all:analyzer:extended_type', extended_type)
|
||||||
r_serv_metadata.sadd('all:analyzer:format_type', 254)
|
r_serv_metadata.sadd('all:analyzer:format_type', 254)
|
||||||
|
|
|
@ -79,3 +79,45 @@ def create_analyzer_queue_post():
|
||||||
return jsonify(res)
|
return jsonify(res)
|
||||||
if res:
|
if res:
|
||||||
return redirect(url_for('server_management', _anchor=res))
|
return redirect(url_for('server_management', _anchor=res))
|
||||||
|
|
||||||
|
@analyzer_queue.route("/analyzer_queue/edit_queue", methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
@login_user_basic
|
||||||
|
def edit_queue_analyzer_queue():
|
||||||
|
queue_uuid = request.args.get("queue_uuid")
|
||||||
|
queue_metadata = Analyzer_Queue.get_queue_metadata(queue_uuid, is_group=True)
|
||||||
|
if 'is_group_queue' in queue_metadata:
|
||||||
|
l_sensors_uuid = Analyzer_Queue.get_queue_group_all_sensors(queue_uuid)
|
||||||
|
else:
|
||||||
|
l_sensors_uuid = None
|
||||||
|
return render_template("analyzer_queue/queue_editor.html", queue_metadata=queue_metadata, l_sensors_uuid=l_sensors_uuid)
|
||||||
|
|
||||||
|
@analyzer_queue.route("/analyzer_queue/edit_queue_post", methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
@login_user_basic
|
||||||
|
def edit_queue_analyzer_queue_post():
|
||||||
|
l_queue_meta = ['queue_uuid', 'description']
|
||||||
|
queue_uuid = request.form.get("queue_uuid")
|
||||||
|
queue_description = request.form.get("description")
|
||||||
|
|
||||||
|
l_uuid = set()
|
||||||
|
l_invalid_uuid = set()
|
||||||
|
for obj_tuple in list(request.form):
|
||||||
|
if obj_tuple not in l_queue_meta:
|
||||||
|
sensor_uuid = request.form.get(obj_tuple)
|
||||||
|
if Analyzer_Queue.is_valid_uuid_v4(sensor_uuid):
|
||||||
|
l_uuid.add(sensor_uuid)
|
||||||
|
else:
|
||||||
|
if sensor_uuid:
|
||||||
|
l_invalid_uuid.add(sensor_uuid)
|
||||||
|
|
||||||
|
if l_invalid_uuid:
|
||||||
|
queue_metadata = Analyzer_Queue.get_queue_metadata(queue_uuid, is_group=True)
|
||||||
|
if queue_description:
|
||||||
|
queue_metadata['description'] = queue_description
|
||||||
|
return render_template("analyzer_queue/queue_editor.html", queue_metadata=queue_metadata, l_sensors_uuid=l_uuid, l_invalid_uuid=l_invalid_uuid)
|
||||||
|
|
||||||
|
Analyzer_Queue.edit_queue_description(queue_uuid, queue_description)
|
||||||
|
Analyzer_Queue.edit_queue_sensors_set(queue_uuid, l_uuid)
|
||||||
|
|
||||||
|
return redirect(url_for('analyzer_queue.edit_queue_analyzer_queue', queue_uuid=queue_uuid))
|
||||||
|
|
|
@ -49,11 +49,11 @@
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="generate_new_uuid();"><i class="fa fa-random"></i></button>
|
<button class="btn btn-outline-secondary" type="button" onclick="generate_new_uuid();"><i class="fa fa-random"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<input class="form-control col-md-4" type="text" name="analyzer_uuid" id="analyzer_uuid" {%if queue_uuid%}value="{{queue_uuid}}"{%endif%} required placeholder="Analyzer uuid - (Optional)">
|
<input class="form-control col-md-4" type="text" name="analyzer_uuid" id="analyzer_uuid" {%if queue_uuid%}value="{{queue_uuid}}"{%endif%} placeholder="Analyzer uuid - (Optional)">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group my-2">
|
<div class="form-group my-2">
|
||||||
<input class="form-control" type="text" name="description" id="analyzer_description" {%if description%}value="{{description}}"{%endif%} required placeholder="Description - (Optional)">
|
<input class="form-control" type="text" name="description" id="analyzer_description" {%if description%}value="{{description}}"{%endif%} placeholder="Description - (Optional)">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="container-id-to-import">
|
<div id="container-id-to-import">
|
||||||
|
@ -110,7 +110,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$("#nav-sensor").addClass("active");
|
|
||||||
{%if queue_type!=2 and queue_type!=254%}
|
{%if queue_type!=2 and queue_type!=254%}
|
||||||
$('#analyzer_metatype_name').hide();
|
$('#analyzer_metatype_name').hide();
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>D4-Project</title>
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png')}}">
|
||||||
|
<!-- Core CSS -->
|
||||||
|
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||||
|
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||||
|
<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- JS -->
|
||||||
|
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.popover{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card mb-3 mt-1">
|
||||||
|
<div class="card-header text-white bg-dark">
|
||||||
|
<h5 class="card-title">Analyzer Queue: <b>{{queue_metadata['uuid']}}</b></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Type Name</th>
|
||||||
|
<th>Group</th>
|
||||||
|
<th style="max-width: 800px;">Name</th>
|
||||||
|
<th style="max-width: 800px;">Last updated</th>
|
||||||
|
<th style="max-width: 800px;">Change max size limit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{%if queue_metadata['format_type'] == 254%}
|
||||||
|
{{queue_metadata['extended_type']}}
|
||||||
|
{%else%}
|
||||||
|
{{queue_metadata['format_type']}}
|
||||||
|
{%endif%}
|
||||||
|
</td>
|
||||||
|
{%if queue_metadata['is_group_queue']%}
|
||||||
|
<td class="text-center"><i class="fa fa-group"></i></td>
|
||||||
|
{%else%}
|
||||||
|
<td></td>
|
||||||
|
{%endif%}
|
||||||
|
<td>
|
||||||
|
<div class="d-flex">
|
||||||
|
<b>{{queue_metadata['uuid']}}:{{queue_metadata['format_type']}}{%if queue_metadata['format_type'] == 254%}:{{queue_metadata['extended_type']}}{%endif%}</b>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{queue_metadata['last_updated']}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-xl-flex justify-content-xl-center">
|
||||||
|
<input class="form-control mr-lg-1" style="max-width: 100px;" type="number" id="max_size_analyzer_{{queue_metadata['uuid']}}" value="{{queue_metadata['size_limit']}}" min="0" required="">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="window.location.href ='{{ url_for('analyzer_change_max_size') }}?analyzer_uuid={{queue_metadata['uuid']}}&redirect=0&max_size_analyzer='+$('#max_size_analyzer_{{queue_metadata['uuid']}}').val();">Change Max Size</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<form action="{{ url_for('analyzer_queue.edit_queue_analyzer_queue_post') }}" method="post" enctype=multipart/form-data>
|
||||||
|
|
||||||
|
<input class="form-control" type="text" name="queue_uuid" id="queue_uuid" value="{{queue_metadata['uuid']}}" hidden>
|
||||||
|
|
||||||
|
<div class="form-group my-2">
|
||||||
|
<input class="form-control" type="text" name="description" id="analyzer_description" {%if 'description' in queue_metadata%}value="{{queue_metadata['description']}}"{%endif%} placeholder="Description - (Optional)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div for="first_sensor_uuid"><b>Sensor UUID</b></div>
|
||||||
|
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="fields">
|
||||||
|
|
||||||
|
{% if l_sensors_uuid %}
|
||||||
|
{% for sensor_uuid in l_sensors_uuid %}
|
||||||
|
{% with sensor_uuid=sensor_uuid, error=False%}
|
||||||
|
{% include 'analyzer_queue/block_add_sensor_to_group_block.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{% if l_invalid_uuid %}
|
||||||
|
{% for sensor_uuid in l_invalid_uuid %}
|
||||||
|
{% with sensor_uuid=sensor_uuid, error=True%}
|
||||||
|
{% include 'analyzer_queue/block_add_sensor_to_group_block.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
<div class="input-group mb-1">
|
||||||
|
<input type="text" class="form-control col-10" name="first_sensor_uuid" id="first_sensor_uuid">
|
||||||
|
<span class="btn btn-info input-group-addon add-field col-2"><i class="fa fa-plus"></i></span>
|
||||||
|
</div>
|
||||||
|
<span class="help-block" hidden>Sensor UUID</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-info" type="submit">Edit Queue</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% include 'navfooter.html' %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var input_part_1 = '<div class="input-group mb-1"><input type="text" class="form-control col-10" name="'
|
||||||
|
var input_part_2 = '"></div>'
|
||||||
|
var minusButton = '<span class="btn btn-danger input-group-addon delete-field col-2"><i class="fa fa-trash"></i></span>'
|
||||||
|
|
||||||
|
$('.add-field').click(function() {
|
||||||
|
var new_uuid = uuidv4();
|
||||||
|
var template = input_part_1 + new_uuid + input_part_2;
|
||||||
|
var temp = $(template).insertBefore('.help-block');
|
||||||
|
temp.append(minusButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.fields').on('click', '.delete-field', function(){
|
||||||
|
console.log($(this).parent());
|
||||||
|
$(this).parent().remove();
|
||||||
|
//$.get( "#")
|
||||||
|
});
|
||||||
|
|
||||||
|
function uuidv4() {
|
||||||
|
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||||
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -256,7 +256,9 @@
|
||||||
{%endif%}
|
{%endif%}
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{{analyzer['uuid']}}
|
<a href="{{ url_for('analyzer_queue.edit_queue_analyzer_queue') }}?queue_uuid={{analyzer['uuid']}}">
|
||||||
|
{{analyzer['uuid']}}
|
||||||
|
</a>
|
||||||
<a href="{{ url_for('remove_analyzer') }}?redirect=1&type={{type['id']}}&analyzer_uuid={{analyzer['uuid']}}" class="ml-auto">
|
<a href="{{ url_for('remove_analyzer') }}?redirect=1&type={{type['id']}}&analyzer_uuid={{analyzer['uuid']}}" class="ml-auto">
|
||||||
<button type="button" class="btn btn-outline-danger px-2 py-0"><i class="fa fa-trash"></i></button>
|
<button type="button" class="btn btn-outline-danger px-2 py-0"><i class="fa fa-trash"></i></button>
|
||||||
</a>
|
</a>
|
||||||
|
@ -308,7 +310,9 @@
|
||||||
{%endif%}
|
{%endif%}
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{{dict_queue['uuid']}}
|
<a href="{{ url_for('analyzer_queue.edit_queue_analyzer_queue') }}?queue_uuid={{dict_queue['uuid']}}">
|
||||||
|
{{dict_queue['uuid']}}
|
||||||
|
</a>
|
||||||
<a href="{{ url_for('remove_analyzer') }}?redirect=1&type=254&metatype_name={{dict_queue['extended_type']}}&analyzer_uuid={{dict_queue['uuid']}}" class="ml-auto">
|
<a href="{{ url_for('remove_analyzer') }}?redirect=1&type=254&metatype_name={{dict_queue['extended_type']}}&analyzer_uuid={{dict_queue['uuid']}}" class="ml-auto">
|
||||||
<button type="button" class="btn btn-outline-danger px-2 py-0"><i class="fa fa-trash"></i></button>
|
<button type="button" class="btn btn-outline-danger px-2 py-0"><i class="fa fa-trash"></i></button>
|
||||||
</a>
|
</a>
|
||||||
|
|
Loading…
Reference in New Issue