diff options
Diffstat (limited to 'dashboard/src/templates/dashboard')
-rw-r--r-- | dashboard/src/templates/dashboard/genericselect.html | 104 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/idf.yaml | 46 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/lab_detail.html | 71 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/lab_list.html | 107 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/landing.html | 148 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/multiple_select_filter_widget.html | 434 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/pdf.yaml | 175 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/resource.html | 10 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/resource_all.html | 10 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/resource_detail.html | 6 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/searchable_select_multiple.html | 461 | ||||
-rw-r--r-- | dashboard/src/templates/dashboard/table.html | 9 |
12 files changed, 648 insertions, 933 deletions
diff --git a/dashboard/src/templates/dashboard/genericselect.html b/dashboard/src/templates/dashboard/genericselect.html new file mode 100644 index 0000000..441d8dc --- /dev/null +++ b/dashboard/src/templates/dashboard/genericselect.html @@ -0,0 +1,104 @@ +{% extends "workflow/viewport-element.html" %} +{% load staticfiles %} + +{% load bootstrap4 %} + +{% block content %} + +<style> + #page-wrapper { + display: flex; + flex-direction: column; + } + + #{{select_type}}_form_div div { + } + + #{{select_type}}_form_div > * { + margin-left: 10px; + margin-right: 10px; + margin-bottom: 20px; + } + + #{{select_type}}_form_div div * { + } + + #{{select_type}}_form_div { + flex: 1; + margin: 30px; + display: flex; + flex-direction: column; + } + + #select_section { + flex: 1; + display: flex; + flex-direction: column; + } + + #{{select_type}}_select_form { + flex: 1; + display: flex; + flex-direction: column; + } + + .autocomplete { + flex: 1; + } + + #create_section { + } + + #select_header_section { + } + + h3 { + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + + .divider { + border-top: 1px solid #ccc; + } + + +</style> + +<div id="{{select_type}}_form_div"> + <h3 id="create_section">Create a Resource + <button class="btn btn-primary {% if disabled %} disabled {% endif %}" + {% if not disabled %}onclick="parent.add_wf({{addable_type_num}})" + {% endif %}>Here + </button> + </h3> + <div class="divider"></div> + <h3 id="select_header_section">Or select from the list below:</h3> + <div id="select_section"> + <form id="{{select_type}}_select_form" method="post" action="" class="form" id="{{select_type}}selectorform"> + {% csrf_token %} + {{ form|default:"<p>no form loaded</p>" }} + {% buttons %} + + {% endbuttons %} + </form> + </div> +</div> + +<script> + {% if disabled %} + disable(); + {% endif %} +</script> + +{% endblock content %} +{% block onleave %} +var form = $("#{{select_type}}_select_form"); +var formData = form.serialize(); +var req = new XMLHttpRequest(); +req.open("POST", "/wf/workflow/", false); +req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); +req.onerror = function() { alert("problem with form submission"); } +req.send(formData); +{% endblock %} + diff --git a/dashboard/src/templates/dashboard/idf.yaml b/dashboard/src/templates/dashboard/idf.yaml new file mode 100644 index 0000000..9e0cc26 --- /dev/null +++ b/dashboard/src/templates/dashboard/idf.yaml @@ -0,0 +1,46 @@ +--- +idf: + version: {{version|default:"0.1"}} + net_config: + oob: + ip-range: {{net_config.oob.ip_range}} + vlan: {{net_config.oob.vlan}} + admin: + interface: {{net_config.admin.interface}} + vlan: {{net_config.admin.vlan}} + network: {{net_config.admin.network}} + mask: {{net_config.admin.mask}} + mgmt: + interface: {{net_config.mgmt.interface}} + vlan: {{net_config.mgmt.vlan}} + network: {{net_config.mgmt.network}} + mask: {{net_config.mgmt.mask}} + private: + interface: {{net_config.private.interface}} + vlan: {{net_config.private.vlan}} + network: {{net_config.private.network}} + mask: {{net_config.private.mask}} + public: + interface: {{net_config.public.interface}} + vlan: {{net_config.public.vlan}} + network: {{net_config.public.network}} + mask: {{net_config.public.mask}} + ip-range: {{net_config.public.ip_range}} + mask: {{net_config.public.mask}} + gateway: {{net_config.public.gateway}} + dns: {% for serv in net_config.public.dns %} + - {{serv}}{% endfor %} + fuel: + jumphost: + bridges: + admin: {{fuel.jumphost.bridges.admin}} + mgmt: {{fuel.jumphost.bridges.mgmt}} + private: {{fuel.jumphost.bridges.private}} + public: {{fuel.jumphost.bridges.public}} + network: {% for node in fuel.network.nodes %} + node: + - interfaces: {% for iface in node.interfaces %} + - {{ iface }}{% endfor %} + - busaddr: {% for addr in node.bus_addrs %} + - {{addr}}{% endfor %} + {% endfor %} diff --git a/dashboard/src/templates/dashboard/lab_detail.html b/dashboard/src/templates/dashboard/lab_detail.html index 4c06245..336b32e 100644 --- a/dashboard/src/templates/dashboard/lab_detail.html +++ b/dashboard/src/templates/dashboard/lab_detail.html @@ -9,12 +9,12 @@ {% block content %} <div class="row"> <div class="col-lg-4"> - <div class="panel panel-default"> - <div class="panel-heading clearfix"> - <h4 style="display: inline;">Lab Profile</h4> - <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a> + <div class="card my-2"> + <div class="card-header d-flex"> + <h4>Lab Profile</h4> + <button class="btn btn-outline-secondary ml-auto" data-toggle="collapse" data-target="#panel_overview">Expand</button> </div> - <div class="panel-body" id="panel_overview"> + <div id="panel_overview" class="card-body collapse show"> <table class="table"> <tr> <td>Lab Name: </td><td>{{lab.name}}</td> @@ -50,19 +50,18 @@ </table> </div> </div> - <div class="panel panel-default"> - <div class="panel-heading clearfix"> - <h4 style="display: inline;">Host Profiles</h4> - - <a data-toggle="collapse" data-target="#profile_panel" class="btn pull-right" style="line-height: 1;" >Expand</a> + <div class="card my-2"> + <div class="card-header d-flex"> + <h4 class="d-inline-block">Host Profiles</h4> + <button data-toggle="collapse" data-target="#profile_panel" class="btn btn-outline-secondary ml-auto" style="line-height: 1;" >Expand</button> </div> - <div class="panel-body pod_panel" id="profile_panel"> + <div id="profile_panel" class="card-body collapse show"> <table class="table"> {% for profile in hostprofiles %} <tr> <td>{{profile.name}}</td> <td>{{profile.description}}</td> - <td>{{profile.labs}}</td> + <td><a href="/resource/profiles/{{ profile.id }}" class="btn btn-info">Profile</a></td> </tr> {% endfor %} </table> @@ -70,31 +69,30 @@ </div> - <div class="panel panel-default"> - <div class="panel-heading clearfix"> + <div class="card my-2"> + <div class="card-header d-flex"> <h4 style="display: inline;">Networking Capabilities</h4> - <a data-toggle="collapse" data-target="#network_panel" class="btn pull-right" style="line-height: 1;" >Expand</a> + <button data-toggle="collapse" data-target="#network_panel" class="btn btn-outline-secondary ml-auto" style="line-height: 1;" >Expand</button> </div> - <div class="panel-body" id="network_panel"> - - <table class="table"> - <tr> - <td>Block Size: (number of VLANs allowed per deployment)</td><td>{{lab.vlan_manager.block_size}}</td> - </tr> - <tr> - <td>Overlapping Vlans Allowed (user can pick which VLANs they wish to use): </td> - <td>{{lab.vlan_manager.allow_overlapping}}</td> - </tr> - </table> + <div class="card-body collapse show" id="network_panel"> + <table class="table"> + <tr> + <td>Block Size: (number of VLANs allowed per deployment)</td><td>{{lab.vlan_manager.block_size}}</td> + </tr> + <tr> + <td>Overlapping Vlans Allowed (user can pick which VLANs they wish to use): </td> + <td>{{lab.vlan_manager.allow_overlapping}}</td> + </tr> + </table> </div> </div> - <div class="panel panel-default"> - <div class="panel-heading clearfix"> - <h4 style="display: inline;">Images</h4> - <a data-toggle="collapse" data-target="#image_panel" class="btn pull-right" style="line-height: 1;" >Expand</a> + <div class="card my-2"> + <div class="card-header d-flex"> + <h4>Images</h4> + <button data-toggle="collapse" data-target="#image_panel" class="btn btn-outline-secondary ml-auto">Expand</button> </div> - <div class="panel-body" id="image_panel"> + <div class="card-body collapse show" id="image_panel"> <table class="table"> <tr> <th>Name</th> @@ -116,14 +114,13 @@ </div> <div class="col-lg-8"> - <div class="panel panel-default"> - <div class="panel-heading clearfix"> - <h4 style="display: inline;">Lab Hosts</h4> - <p style="display: inline; margin-left: 10px;"></p> - <a data-toggle="collapse" data-target="#lab_hosts_panel" class="btn pull-right" style="line-height: 1;" >Expand</a> + <div class="card my-2"> + <div class="card-header d-flex"> + <h4>Lab Hosts</h4> + <button data-toggle="collapse" data-target="#lab_hosts_panel" class="btn btn-outline-secondary ml-auto">Expand</button> </div> - <div class="panel-body" id="lab_hosts_panel"> + <div class="card-body collapse show" id="lab_hosts_panel"> <table class="table"> <tr> <th>Name</th> diff --git a/dashboard/src/templates/dashboard/lab_list.html b/dashboard/src/templates/dashboard/lab_list.html index a86f7f4..9cde80c 100644 --- a/dashboard/src/templates/dashboard/lab_list.html +++ b/dashboard/src/templates/dashboard/lab_list.html @@ -1,87 +1,28 @@ {% extends "base.html" %} -{% load staticfiles %} - -{% block extrahead %} - {{block.super}} - <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=yaml"></script> -{% endblock %} - {% block content %} - <style> - .grid-item-container { - padding: 10px; - } - - .grid-item { - cursor: pointer; - border:2px; - border-style:none; - border-color:black; - border-radius: 5px; - padding: 7px; - color: inherit; - - box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75); - transition-property: box-shadow, background-color; - transition-duration: .2s; - } - - .grid-item-text - { - color: inherit; - text-decoration: none; - } - .grid-item-text:hover - { - color: #121212; - text-decoration: none; - } - - .grid-item:hover { - box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75); - transition-property: box-shadow; - transition-duration: .2s; - - } - - .selected_node { - box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75); - background-color: #CCECD7; - transition-property: background-color; - transition-duration: .2s; - } - - .disabled_node { - cursor: not-allowed; - background-color: #EFEFEF; - box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75); - transition-property: box-shadow; - transition-duration: .2s; - } - - .disabled_node:hover { - box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75); - } - - </style> - <div class="container-fluid"> - <div class="row"> - - <div class="listgrid"> - {% for lab in labs %} - <div class="grid-item-container col-lg-2 col-mid-4 col-sm-6"> - - <a href="{{ lab.name }}" class="grid-item-text"> - - <div class="grid-item"> - <h4 class="grid-item-header">{{ lab.name }}</h4> - <p class="grid-item-description">{{ lab.description }}</p> - </div> - </a> +<h2>Labs</h2> +<div class="card_container"> + {% for lab in labs %} + <div class="card"> + <div class="card-header"> + <h3 class="mt-2">{{lab.name}}</h3> + </div> + <div class="p-4"> + <ul class="list-group"> + <li class="list-group-item">name: {{lab.name}}</li> + <li class="list-group-item">description: {{lab.description}}</li> + <li class="list-group-item">location: {{lab.location}}</li> + {% if lab.status == 0 %} + <li class="list-group-item">status: Up</li> + {% elif lab.status == 100 %} + <li class="list-group-item">status: Down for Maintenance</li> + {% elif lab.status == 200 %} + <li class="list-group-item">status: Down</li> + {% endif %} + </ul> + <a class="btn btn-primary mt-4 w-100" href="/lab/{{lab.name}}/">Details</a> </div> - {% endfor %} - </div> - </div> </div> - -{% endblock content %} + {% endfor %} +</div> +{% endblock %}
\ No newline at end of file diff --git a/dashboard/src/templates/dashboard/landing.html b/dashboard/src/templates/dashboard/landing.html index 3e0aacd..e6a235f 100644 --- a/dashboard/src/templates/dashboard/landing.html +++ b/dashboard/src/templates/dashboard/landing.html @@ -2,20 +2,30 @@ {% load staticfiles %} {% block content %} - <div class=""> - <p style="text-align:center;">Welcome to the Pharos Dashboard! To get started, select one of the options below:</p> +<div class="" style="text-align: center;"> + {% if not request.user.is_anonymous %} + {% if not request.user.userprofile.ssh_public_key %} + <div class="alert alert-danger" role="alert"> + Warning: you need to upload an ssh key under <a href="/accounts/settings">account settings</a> if you wish to + log into the servers you book </div> + {% endif %} + {% else %} + {% endif %} +</div> {% csrf_token %} <style> - .wf_create{ + .wf_create { display: inline-block; text-align: center; } - .wf_create_div{ + + .wf_create_div { text-align: center; } - .hidden_form{ + + .hidden_form { display: none; } @@ -27,48 +37,122 @@ display: grid; grid-template-columns: 33% 34% 33%; } -</style> -<script type="text/javascript"> - function cwf(wf_type){ - document.getElementById('id_workflow').selectedIndex = wf_type; - document.getElementById('wf_selection_form').submit(); + + .landing_container { + display: grid; + grid-template-columns: 1fr 30px 1fr; } -</script> -<div class='wf_create_div'> -<button class="wf_create btn" onclick="cwf(0)">Create a Booking</button> -<button class="wf_create btn" onclick="cwf(1)">Create a Pod</button> -<button class="wf_create btn" onclick="cwf(2)">Configure a Pod</button> -<button class="wf_create btn" onclick="cwf(3)">Create a Snapshot</button> -{% if manager == True %} -<button class="wf_continue btn" onclick="continue_wf()">Finish Unfinished Business</button> -{% endif %} + + .grid_panel { + padding: 30px; + } + + .btn-primary { + margin: 10px; + } + + h2 { + border-bottom: 1px solid #cccccc; + } + + h1 {} +</style> +<div class="container-fluid"> + <div class="row"> + <!-- About us --> + <div class="col-12 col-lg-6 mb-4"> + <h2>About Us:</h2> + <p>The Lab as a Service (LaaS) project aims to help in the development and testing of LFN projects such as + OPNFV + by hosting hardware and providing access to the community. Currently, the only participating lab is the + University of New Hampshire Interoperability Lab (UNH-IOL).</p> + <p>To get started, you can request access to a server at the right. PTL's have the ability to design and + book a + whole block of servers with customized layer2 networks (e.g. a Pharos Pod). Read more here: <a + href="https://wiki.opnfv.org/display/INF/Lab+as+a+Service+2.0">LaaS Wiki</a></p> + </div> + <!-- Get started --> + <div class="col-12 col-lg-6 mb-4"> + <h2>Get Started:</h2> + {% if request.user.is_anonymous %} + <h4 style="text-align:center;">To get started, please log in with your <a href="/accounts/login">Linux + Foundation Jira account</a></h4> + {% else %} + <p>To get started, book a server below:</p> + <a class="wf_create btn btn-primary" + style="display: flex; flex-direction: column; justify-content: center; margin: 20px; height: 100pt; vertical-align: middle; text-align: center; color: #FFF;" + href="/booking/quick/"> + <p style="font-size: xx-large">Book a Server</p> + </a> + <p>PTLs can use our advanced options to book multi-node pods. If you are a PTL, you may use the options + below: + </p> + <div class='container'> + <div class="row"> + <div class="col-12 col-xl-4"> + <button class="wf_create btn btn-primary w-100" onclick="cwf(0)">Book a Pod</button> + </div> + <div class="col-12 col-xl-4"> + <button class="wf_create btn btn-primary w-100" onclick="cwf(1)">Design a Pod</button> + </div> + <div class="col-12 col-xl-4"> + <button class="wf_create btn btn-primary w-100" onclick="cwf(2)">Configure a Pod</button> + </div> + </div> + {% endif %} + </div> + </div> + <!-- Returning users --> + {% if not request.user.is_anonymous %} + <div class="col-12 col-lg-6 offset-lg-6 mb-4 mt-lg-4"> + <h2 class="ht-4">Returning Users:</h2> + <p>If you're a returning user, some of the following options may be of interest:</p> + <div class="container"> + <div class="row"> + <div class="col-12 col-xl-4"> + <button class="wf_create btn btn-primary w-100" onclick="cwf(3)">Snapshot a Host</button> + </div> + <div class="col-12 col-xl-4"> + <a class="wf_create btn btn-primary w-100" href="{% url 'account:my-bookings' %}">My + Bookings</a> + </div> + {% if manager == True %} + <div class="col-12 col-xl-4"> + <button class="wf_continue btn btn-primary w-100" onclick="continue_wf()">Resume + Workflow</button> + </div> + {% endif %} + </div> + </div> + </div> + {% endif %} + </div> </div> <script type="text/javascript"> - function cwf(type) - { + function cwf(type) { $.ajax({ type: "POST", url: "/", - data: {"create":type}, - beforeSend: function(request) { + data: { + "create": type + }, + beforeSend: function (request) { request.setRequestHeader("X-CSRFToken", - $('input[name="csrfmiddlewaretoken"]').val() + $('input[name="csrfmiddlewaretoken"]').val() ); } }).done(function (data) { window.location.replace("/wf/"); - }).fail(function(jqxHR, textstatus) { - alert("Something went wrong...");}); + }).fail(function (jqxHR, textstatus) { + alert("Something went wrong..."); + }); } - function continue_wf() - { + + function continue_wf() { window.location.replace("/wf/"); } - - //success: window.location.replace("/wf/") - </script> <div class="hidden_form" id="form_div"> @@ -85,4 +169,4 @@ {% block vport_comm %} {% endblock %} -{% endblock content %} +{% endblock content %}
\ No newline at end of file diff --git a/dashboard/src/templates/dashboard/multiple_select_filter_widget.html b/dashboard/src/templates/dashboard/multiple_select_filter_widget.html index 31b8f33..4302543 100644 --- a/dashboard/src/templates/dashboard/multiple_select_filter_widget.html +++ b/dashboard/src/templates/dashboard/multiple_select_filter_widget.html @@ -1,403 +1,149 @@ +<script src="/static/js/dashboard.js"> +</script> + <style> .object_class_wrapper { display: grid; grid-template-columns: 1fr 1fr 1fr; border: 0px; } + .class_grid_wrapper { border: 0px; - border-left: 1px; + text-align: center; border-right: 1px; border-style: solid; border-color: grey; - text-align: center; } + +.class_grid_wrapper:last-child { + border-right: none; +} + .grid_wrapper { display: grid; grid-template-columns: 1fr 1fr; } + .grid-item { cursor: pointer; - border:2px; - border-style:none; - border-color:black; + border: 1px solid #cccccc; border-radius: 5px; - margin:20px; + margin: 20px; height: 200px; padding: 7px; - box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75); - transition-property: box-shadow, background-color; - transition-duration: .2s; + transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s; + box-shadow: 0 1px 1px rgba(0,0,0,.075); + + display: flex; + flex-direction: column; } -.grid-item:hover { - box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75); - transition-property: box-shadow; - transition-duration: .2s; +.grid-item > .btn:active, .grid-item > .btn:focus { + outline: none; !important; + box-shadow: none; +} +.grid-item-description { + flex: 1; } .selected_node { - box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75); - background-color: #CCECD7; - transition-property: background-color; - transition-duration: .2s; + border-color: #40c640; + box-shadow: 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(109, 243, 76, 0.6); + transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s; +} + +.grid-item:hover:not(.selected_node):not(.disabled_node) { + box-shadow: 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(100, 100, 100, 0.3); + transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s; } .disabled_node { cursor: not-allowed; background-color: #EFEFEF; - box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75); - transition-property: box-shadow; - transition-duration: .2s; } -.disabled_node:hover { - box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75); -} +.disabled_node:hover {} .cleared_node { background-color: #FFFFFF; } -.grid-item-header -{ +.grid-item-header { font-weight: bold; font-size: 20px; margin-top: 10px; } -</style> -<input name="filter_field" id="filter_field" type="hidden"/> -<div id="grid_wrapper" class="grid_wrapper"> -{% for object_class, object_list in filter_objects %} -<div class="class_grid_wrapper"> - <div style="display:inline-block;margin:auto"> - <h4>{{object_class}}</h4> - </div> - <div id="{{object_class}}" class="object_class_wrapper"> - {% for obj in object_list %} - <div id="object_parent"> - <div id="{{ obj.id|default:'not_provided' }}" class="grid-item"> - <p class="grid-item-header">{{obj.name}}</p> - <p class="grid-item-description">{{obj.description}}</p> - <button type="button" class="btn btn-success grid-item-select-btn" onclick="processClick('{{obj.id}}', {% if obj.multiple %}true{% else %}false{% endif %});">{% if obj.multiple %}Add{% else %}Select{% endif %}</button> - </div> - <input type="hidden" name="{{obj.id}}_selected" value="false"/> - </div> - {% endfor %} - </div> - </div> -{% endfor %} -</div> - -<div id="dropdown_wrapper"> -</div> - -<script> -var initialized = false; -var mapping = {{ mapping|safe }}; -var items = {{ items|safe }}; -var result = {}; -var selection = {{selection_data|default_if_none:"null"|safe}}; -var dropdown_count = 0; - -{% if selection_data %} -make_selection({{selection_data|safe}}); -{% endif %} - -function make_selection( selection_data ){ - if(!initialized) { - init(); - } - for(var k in selection_data) { - selected_items = selection_data[k]; - for( var item in selected_items ){ - var node = items[item]; - if(!node['multiple']){ - var input_value = selected_items[item]; - if( input_value != 'false' ) { - select(node); - markAndSweep(node); - } - var div = document.getElementById(item) - var input = div.parentNode.getElementsByTagName("input")[0] - input.value = input_value; - updateResult(item); - } else { - make_multiple_selection(selected_items, item); - } - } - } -} - -function make_multiple_selection(data, item_class){ - var node = items[item_class]; - select(node); - markAndSweep(node); - prepop_data = data[item_class]; - for(var i=0; i<prepop_data.length; i++){ - var div = add_item_prepopulate(node, prepop_data[i]); - updateObjectResult(div); - } -} - -function markAndSweep(root){ - for(var nodeId in items) { - node = items[nodeId]; - node['marked'] = true; //mark all nodes - //clears grey background of everything - } - - toCheck = []; - toCheck.push(root); - - while(toCheck.length > 0){ - node = toCheck.pop(); - if(!node['marked']) { - //already visited, just continue - continue; - } - node['marked'] = false; //mark as visited - if(node['follow'] || node == root){ //add neighbors if we want to follow this node (labs) - var mappingId = node.id - var neighbors = mapping[mappingId]; - for(var neighId in neighbors) { - neighId = neighbors[neighId]; - var neighbor = items[neighId]; - toCheck.push(neighbor); - } - } - } - - //now remove all nodes still marked - for(var nodeId in items){ - node = items[nodeId]; - if(node['marked']){ - disable(node); - } - } -} - -function process(node) { - if(node['selected']) { - markAndSweep(node); - } - else { - var selected = [] - //remember the currently selected, then reset everything and reselect one at a time - for(var nodeId in items) { - node = items[nodeId]; - if(node['selected']) { - selected.push(node); - } - clear(node); - - } - for(var i=0; i<selected.length; i++) { - node = selected[i]; - select(node); - markAndSweep(selected[i]); - } - } -} - -function select(node) { - elem = document.getElementById(node['id']); - node['selected'] = true; - elem.classList.remove('cleared_node') - elem.classList.remove('disabled_node') - elem.classList.add('selected_node') - var input = elem.parentNode.getElementsByTagName("input")[0]; - input.disabled = false; - input.value = true; -} - -function clear(node) { - elem = document.getElementById(node['id']); - node['selected'] = false; - node['selectable'] = true; - elem.classList.add('cleared_node') - elem.classList.remove('disabled_node') - elem.classList.remove('selected_node') - elem.parentNode.getElementsByTagName("input")[0].disabled = true; -} - -function disable(node) { - elem = document.getElementById(node['id']); - node['selected'] = false; - node['selectable'] = false; - elem.classList.remove('cleared_node') - elem.classList.add('disabled_node') - elem.classList.remove('selected_node') - elem.parentNode.getElementsByTagName("input")[0].disabled = true; -} - -function processClick(id, multiple){ - if(!initialized){ - init(); - } - var element = document.getElementById(id); - var node = items[id]; - if(!node['selectable']){ - return; - } - if(multiple){ - return processClickMultipleObject(node); - } - node['selected'] = !node['selected']; //toggle on click - - if(node['selected']) { - select(node); - } - else { - clear(node); - } - process(node); - updateResult(id); +.dropdown_item { + border: 1px; + border-style: solid; + border-color: lightgray; + border-radius: 5px; + margin: 20px; + padding: 2px; + grid-column: 1; + display: grid; + grid-template-columns: 1fr 3fr 1fr; + justify-items: center; } -function processClickMultipleObject(node){ - select(node); - add_item(node); - process(node); +.dropdown_item > button { + margin: 2px; + justify-self: end; } -function add_item(node){ - return add_item_prepopulate(node, {}); +.dropdown_item > h5 { + margin: auto; } -inputs = [] - -function restrictchars(input) -{ - if( input.validity.patternMismatch ) - { - input.setCustomValidity("Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed"); - input.reportValidity(); - } - - input.value = input.value.replace(/([^A-Za-z0-9-_.])+/g, ""); - - checkunique(input); -} - -function checkunique(tocheck) -{ - val = tocheck.value; - for( var i = 0; i < inputs.length; i++ ) - { - if( inputs[i].value == val && inputs[i] != tocheck) - { - tocheck.setCustomValidity("All hostnames must be unique"); - tocheck.reportValidity(); - return; - } - } - tocheck.setCustomValidity(""); -} - -function add_item_prepopulate(node, prepopulate){ - inputs = []; - var div = document.createElement("DIV"); - div.class = node['id']; - div.id = "dropdown_" + dropdown_count; - dropdown_count++; - var label = document.createElement("H5"); - label.style['display'] = 'inline'; - label.appendChild(document.createTextNode(node['name'])); - div.appendChild(label); - for(var i=0; i<node['forms'].length; i++){ - form = node['forms'][i]; - var input = document.createElement("INPUT"); - input.type = form['type']; - input.name = form['name']; - input.pattern = "(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})"; - input.title = "Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed" - input.placeholder = form['placeholder']; - inputs.push(input); - input.onchange = function() { updateObjectResult(div); restrictchars(this); }; - input.oninput = function() { restrictchars(this); }; - if(form['name'] in prepopulate){ - input.value = prepopulate[form['name']]; - } - div.appendChild(input); - } - //add class id to dropdown object - var hiddenInput = document.createElement("INPUT"); - hiddenInput.type = "hidden"; - hiddenInput.name = "class"; - hiddenInput.value = node['id']; - div.appendChild(hiddenInput); - button = document.createElement("BUTTON"); - button.onclick = function(){ - remove_dropdown(div.id); - } - button.type = "button"; - button.appendChild(document.createTextNode("Remove")); - div.appendChild(button); - document.getElementById("dropdown_wrapper").appendChild(div); - updateObjectResult(div); - return div; +.dropdown_item > input { + padding: 7px; + margin: 2px; + width: 90%; } -function remove_dropdown(id){ - var div = document.getElementById(id); - var parent = div.parentNode; - div.parentNode.removeChild(div); - //checks if we have removed last item in class - var deselect_class = true; - var div_inputs = div.getElementsByTagName("input"); - var div_class = div_inputs[div_inputs.length-1].value; - var result_class = document.getElementById(div_class).parentNode.parentNode.id; - delete result[result_class][div.id]; - for(var i=0; i<parent.children.length; i++){ - var inputs = parent.children[i].getElementsByTagName("input"); - var object_class = ""; - for(var k=0; k<inputs.length; k++){ - if(inputs[k].name == "class"){ - object_class = inputs[k].value; - } - } - if(object_class == div_class){ - deselect_class = false; - } - } - if(deselect_class){ - clear(items[div_class]); - } +#dropdown_wrapper { + display: grid; + grid-template-columns: 4fr 5fr; } +</style> -function updateResult(nodeId){ - if(!initialized){ - init(); - } - if(!items[nodeId]['multiple']){ - var node = document.getElementById(nodeId); - var value = {} - value[nodeId] = node.parentNode.getElementsByTagName("input")[0].value; - result[node.parentNode.parentNode.id][nodeId] = value; - } -} +<input name="filter_field" id="filter_field" type="hidden"/> +<div id="grid_wrapper" class="grid_wrapper"> +{% for object_class, object_list in display_objects %} + <div class="class_grid_wrapper"> + <div style="display:inline-block;margin:auto"> + <h4>{{object_class}}</h4> + </div> + <div id="{{object_class}}" class="object_class_wrapper"> + {% for obj in object_list %} + <div id="{{ obj.id|default:'not_provided' }}" class="grid-item" onclick="multi_filter_widget.processClick( + '{{obj.id}}');"> + <p class="grid-item-header">{{obj.name}}</p> + <p class="grid-item-description">{{obj.description}}</p> + <button type="button" class="btn btn-success grid-item-select-btn"> + {% if obj.multiple %}Add{% else %}Select{% endif %} + </button> + </div> + {% endfor %} + </div> + </div> +{% endfor %} +</div> -function updateObjectResult(parentElem){ - node_type = document.getElementById(parentElem.class).parentNode.parentNode.id; - input = {}; - inputs = parentElem.getElementsByTagName("input"); - for(var i in inputs){ - var e = inputs[i]; - input[e.name] = e.value; - } - result[node_type][parentElem.id] = input; -} +<div id="dropdown_wrapper"> +</div> +<script> +function multipleSelectFilterWidgetEntry() { + const graph_neighbors = {{ neighbors|safe }}; + const filter_items = {{ filter_items|safe }}; + const initial_value = {{ initial_value|default_if_none:"{}"|safe }}; -function init() { - for(nodeId in items) { - element = document.getElementById(nodeId); - node = items[nodeId]; - result[element.parentNode.parentNode.id] = {} - } - initialized = true; + //global variable + multi_filter_widget = new MultipleSelectFilterWidget(graph_neighbors, filter_items, initial_value); } +multipleSelectFilterWidgetEntry(); </script> diff --git a/dashboard/src/templates/dashboard/pdf.yaml b/dashboard/src/templates/dashboard/pdf.yaml index 297e04b..c893919 100644 --- a/dashboard/src/templates/dashboard/pdf.yaml +++ b/dashboard/src/templates/dashboard/pdf.yaml @@ -1,95 +1,92 @@ --- version: {{version|default:"1.0"}} details: - pod_owner: {{details.owner}} - contact: {{details.contact}} - lab: {{details.lab}} - location: {{details.location}} - type: {{details.type}} - link: {{details.link}} - + contact: {{details.contact}} + lab: {{details.lab}} + link: {{details.link}} + location: {{details.location}} + pod_owner: {{details.owner}} + type: {{details.type}} jumphost: - name: {{jumphost.name}} - node: - type: {{jumphost.node.type}} - vendor: {{jumphost.node.vendor}} - model: {{jumphost.node.model}} - arch: {{jumphost.node.arch}} - cpus: {{jumphost.node.cpus}} - cpu_cflags: {{jumphost.node.cpu_cflags}} - cores: {{jumphost.node.cores}} - memory: {{jumphost.node.memory}} - disks: - {% for disk in jumphost.disks %} - - name: {{disk.name}} - disk_capacity: {{disk.capacity}} - disk_type: {{disk.type}} - disk_interface: {{disk.interface}} - disk_rotation: {{disk.rotation}} - - {% endfor %} - os: {{jumphost.os}} - remote_params: - type: {{jumphost.remote.type}} - versions: - {% for version in jumphost.remote.versions %} - - {{version}} - {% endfor %} - user: {{jumphost.remote.user}} - pass: {{jumphost.remote.pass}} - remote_management: - type: {{jumphost.remote.type}} - versions: - {% for version in jumphost.remote.versions %} - - {{version}} - {% endfor %} - user: {{jumphost.remote.user}} - pass: {{jumphost.remote.pass}} - address: {{jumphost.remote.address}} - mac_address: {{jumphost.remote.mac_address}} - interfaces: - {% for interface in jumphost.interfaces %} - - name: {{interface.name}} - address: {{interface.address}} - mac_address: {{interface.mac_address}} - vlan: {{interface.vlan}} - {% endfor %} + disks: + {% for disk in jumphost.disks %} + - disk_capacity: {{disk.capacity}} + disk_interface: {{disk.interface}} + disk_rotation: {{disk.rotation}} + disk_type: {{disk.type}} + name: {{disk.name}} + {% endfor %} + interfaces: + {% for interface in jumphost.interfaces %} + - features: {{interface.features}} + mac_address: {{interface.mac_address}} + name: {{interface.name}} + speed: {{interface.speed}} + {% endfor %} + name: {{jumphost.name}} + node: + arch: {{jumphost.node.arch}} + cores: {{jumphost.node.cores}} + cpu_cflags: {{jumphost.node.cpu_cflags}} + cpus: {{jumphost.node.cpus}} + memory: {{jumphost.node.memory}} + model: {{jumphost.node.model}} + type: {{jumphost.node.type}} + vendor: {{jumphost.node.vendor}} + os: {{jumphost.os}} + remote_management: + address: {{jumphost.remote.address}} + mac_address: {{jumphost.remote.mac_address}} + pass: {{jumphost.remote.pass}} + type: {{jumphost.remote.type}} + user: {{jumphost.remote.user}} + versions: + {% for version in jumphost.remote.versions %} + - {{version}} + {% endfor %} + remote_params: + pass: {{jumphost.remote.pass}} + type: {{jumphost.remote.type}} + user: {{jumphost.remote.user}} + versions: + {% for version in jumphost.remote.versions %} + - {{version}} + {% endfor %} nodes: - {% for node in nodes %} - - name: {{node.name}} - node: - type: {{node.node.type}} - vendor: {{node.node.vendor}} - model: {{node.node.model}} - arch: {{node.node.arch}} - cpus: {{node.node.cpus}} - cpu_cflags: {{node.node.cpu_cflags}} - cores: {{node.node.cores}} - memory: {{node.node.memory}} - disks: - {% for disk in node.disks %} - - name: {{disk.name}} - disk_capacity: {{disk.capacity}} - disk_type: {{disk.type}} - disk_interface: {{disk.interface}} - disk_rotation: {{disk.rotation}} - - {% endfor %} - remote_management: - type: {{node.remote.type}} - versions: - {% for version in node.remote.versions %} - - {{version}} - {% endfor %} - user: {{node.remote.user}} - pass: {{node.remote.pass}} - address: {{node.remote.address}} - mac_address: {{node.remote.mac_address}} - interfaces: - {% for interface in node.interfaces %} - - name: {{interface.name}} - address: {{interface.address}} - mac_address: {{interface.mac_address}} - vlan: {{interface.vlan}} - {% endfor %} +{% for node in nodes %} +- disks: + {% for disk in node.disks %} + - disk_capacity: {{disk.capacity}} + disk_interface: {{disk.interface}} + disk_rotation: {{disk.rotation}} + disk_type: {{disk.type}} + name: {{disk.name}} + {% endfor %} + interfaces: + {% for interface in node.interfaces %} + - features: {{interface.features}} + mac_address: {{interface.mac_address}} + name: {{interface.name}} + speed: {{interface.speed}} {% endfor %} + name: {{node.name}} + node: + arch: {{node.node.arch}} + cores: {{node.node.cores}} + cpu_cflags: {{node.node.cpu_cflags}} + cpus: {{node.node.cpus}} + memory: {{node.node.memory}} + model: {{node.node.model}} + type: {{node.node.type}} + vendor: {{node.node.vendor}} + remote_management: + address: {{node.remote.address}} + mac_address: {{node.remote.mac_address}} + pass: {{node.remote.pass}} + type: {{node.remote.type}} + user: {{node.remote.user}} + versions: + {% for version in node.remote.versions %} + - {{version}} + {% endfor %} +{% endfor %} diff --git a/dashboard/src/templates/dashboard/resource.html b/dashboard/src/templates/dashboard/resource.html index 28e7998..f36ee7b 100644 --- a/dashboard/src/templates/dashboard/resource.html +++ b/dashboard/src/templates/dashboard/resource.html @@ -7,11 +7,11 @@ <link href="{% static "bower_components/morrisjs/morris.css" %}" rel="stylesheet"> <!-- DataTables CSS --> - <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}" + <link href="{% static "bower_components/datatables.net-bs4/css/dataTables.bootstrap4.min.css" %}" rel="stylesheet"> <!-- DataTables Responsive CSS --> - <link href="{% static "bower_components/datatables-responsive/css/dataTables.responsive.css" %}" + <link href="{% static "bower_components/datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css" %}" rel="stylesheet"> {% endblock extrahead %} @@ -23,11 +23,11 @@ {% block extrajs %} <!-- DataTables JavaScript --> - <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}" + <link href="{% static "bower_components/datatables/media/css/dataTables.bootstrap.css" %}" rel="stylesheet"> - <script src={% static "bower_components/datatables/media/js/jquery.dataTables.min.js" %}></script> - <script src={% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js" %}></script> + <script src={% static "bower_components/datatables.net/js/jquery.dataTables.min.js" %}></script> + <script src={% static "bower_components/datatables.net-bs4/js/dataTables.bootstrap4.min.js" %}></script> diff --git a/dashboard/src/templates/dashboard/resource_all.html b/dashboard/src/templates/dashboard/resource_all.html index 0b0d0d4..fb8cc7e 100644 --- a/dashboard/src/templates/dashboard/resource_all.html +++ b/dashboard/src/templates/dashboard/resource_all.html @@ -7,11 +7,11 @@ <link href="{% static "bower_components/morrisjs/morris.css" %}" rel="stylesheet"> <!-- DataTables CSS --> - <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}" + <link href="{% static "bower_components/datatables.net-bs4/css/dataTables.bootstrap4.min.css" %}" rel="stylesheet"> <!-- DataTables Responsive CSS --> - <link href="{% static "bower_components/datatables-responsive/css/dataTables.responsive.css" %}" + <link href="{% static "bower_components/datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css" %}" rel="stylesheet"> {% endblock extrahead %} @@ -36,11 +36,11 @@ {% block extrajs %} <!-- DataTables JavaScript --> - <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}" + <link href="{% static "bower_components/datatables/media/css/dataTables.bootstrap.css" %}" rel="stylesheet"> - <script src={% static "bower_components/datatables/media/js/jquery.dataTables.min.js" %}></script> - <script src={% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js" %}></script> + <script src={% static "bower_components/datatables.net/js/jquery.dataTables.min.js" %}></script> + <script src={% static "bower_components/datatables.net-bs4/js/dataTables.bootstrap4.min.js" %}></script> diff --git a/dashboard/src/templates/dashboard/resource_detail.html b/dashboard/src/templates/dashboard/resource_detail.html index 79389f0..0a443d9 100644 --- a/dashboard/src/templates/dashboard/resource_detail.html +++ b/dashboard/src/templates/dashboard/resource_detail.html @@ -101,12 +101,10 @@ {{ resource.owner.email }} </p> <p> - <a href="{% url 'booking:create' resource_id=resource.id %}" class="btn - btn-primary"> + <a href="{% url 'booking:create' resource_id=resource.id %}" class="btn btn-primary"> Booking </a> - <a href="{{ resource.url }}" class="btn - btn-primary"> + <a href="{{ resource.url }}" class="btn btn-primary"> OPNFV Wiki </a> </p> diff --git a/dashboard/src/templates/dashboard/searchable_select_multiple.html b/dashboard/src/templates/dashboard/searchable_select_multiple.html index e7128b0..8bcf890 100644 --- a/dashboard/src/templates/dashboard/searchable_select_multiple.html +++ b/dashboard/src/templates/dashboard/searchable_select_multiple.html @@ -1,35 +1,58 @@ <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> +<script src="/static/js/dashboard.js"></script> + +<div id="search_select_outer" class="autocomplete"> + <div id="warning_pane" style="background: #FFFFFF; color: #CC0000;"> + {% if incompatible == "true" %} + <h3>Warning: Incompatible Configuration</h3> + <p>Please make a different selection, as the current config conflicts with the selected pod</p> + {% endif %} + </div> + <div id="added_counter"> + <p id="added_number">0</p> + <p id="addable_limit">/ {% if selectable_limit > -1 %} {{ selectable_limit }} {% else %} ∞ {% endif %}added</p> + </div> + + <div id="added_list"> -<div class="autocomplete" style="width:400px;"> - <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="{{initial.name}}" oninput="search(this.value)" + </div> + + <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="" oninput="searchable_select_multiple_widget.search(this.value)" {% if disabled %} disabled {% endif %} > + </input> <input type="hidden" id="selector" name="{{ name }}" class="form-control" style="display: none;" {% if disabled %} disabled {% endif %} > </input> - <ul id="drop_results"></ul> - - - <div id="default_entry_wrap" style="display: none;"> - <div class="list_entry unremovable_list_entry"> - <p id="default_text" class="full_name"></p> - <button class="btn-remove btn disabled">remove</button> - </div> + <div id="scroll_restrictor"> + <ul id="drop_results"></ul> </div> + <style> + #scroll_restrictor { + flex: 1; + position: relative; + overflow-y: auto; + padding-bottom: 10px; + } - <div id="added_list"> + #added_list { + margin-bottom: 5px; + } - </div> - <div id="added_counter" style="text-align: center; margin: 10px;"><p id="added_number" style="display: inline;">0</p><p style="display: inline;">/ - {% if selectable_limit > -1 %} {{ selectable_limit }} {% else %} ∞ {% endif %}added</p></div> - <style> + .autocomplete { + display: flex; + flex: 1; + flex-direction: column; + } #user_field { font-size: 14pt; - width: 400px; padding: 5px; + height: 40px; + border: 1px solid #ccc; + border-radius: 5px; } @@ -37,372 +60,150 @@ list-style-type: none; padding: 0; margin: 0; - max-height: 300px; min-height: 0; - overflow-y: scroll; - overflow-x: hidden; border: solid 1px #ddd; - display: none; + border-top: none; + border-bottom: none; + visibility: inherit; + flex: 1; + + position: absolute; + width: 100%; } #drop_results li a{ font-size: 14pt; - border: 1px solid #ddd; background-color: #f6f6f6; - padding: 12px; + padding: 7px; text-decoration: none; display: block; - width: 400px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } - .btn-remove { - float: right; - height: 30px; - margin: 4px; + #drop_results li a { + border-bottom: 1px solid #ddd; } .list_entry { - width: 400px; - border: 1px solid #ddd; - border-radius: 3px; + border: 1px solid #ccc; + border-radius: 5px; margin-top: 5px; vertical-align: middle; line-height: 40px; height: 40px; padding-left: 12px; + width: 100%; + display: flex; } #drop_results li a:hover{ background-color: #ffffff; } - .small_name { - display: inline-block; + .added_entry_text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline; + width: 100%; } - .full_name { - display: inline-block; + .btn-remove { + float: right; + height: 30px; + margin: 4px; + padding: 1px; + max-width: 20%; + width: 15%; + min-width: 70px; + overflow: hidden; + text-overflow: ellipsis; } - </style> -</div> - -<script type="text/javascript"> - //flags - var show_from_noentry = {{show_from_noentry|default:"false"}}; - var show_x_results = {{show_x_results|default:-1}}; - var results_scrollable = {{results_scrollable|default:"false"}}; - var selectable_limit = {{selectable_limit|default:-1}}; - var field_name = "{{name|default:"users"}}"; - var placeholder = "{{placeholder|default:"begin typing"}}"; - var default_entry = "{{default_entry}}"; - - //needed info - var items = {{items|safe}} - - //tries - var expanded_name_trie = {} - expanded_name_trie.isComplete = false; - var small_name_trie = {} - small_name_trie.isComplete = false; - var string_trie = {} - string_trie.isComplete = false; - - var added_items = []; - - var added_template = {{ added_list|default:"{}" }}; - - if( default_entry ) - { - var default_entry_div = document.getElementById("default_entry_wrap"); - default_entry_div.style.display = "inherit"; - - var entry_p = document.getElementById("default_text"); - entry_p.innerText = default_entry; - } - - init(); - - if( show_from_noentry ) - { - search(""); - } - - function disable() { - var textfield = document.getElementById("user_field"); - var drop = document.getElementById("drop_results"); - - textfield.disabled = "True"; - drop.style.display = "none"; - - var btns = document.getElementsByClassName("btn-remove"); - for( var i = 0; i < btns.length; i++ ) - { - btns[i].classList.add("disabled"); + .entry_tooltip { + display: none; } - } - - function init() { - build_all_tries(items); - var initial = {{ initial|safe }}; + #drop_results li a:hover .entry_tooltip { + position: absolute; + background: #444; + color: #ddd; + text-align: center; + font-size: 12pt; + border-radius: 3px; - for( var i = 0; i < initial.length; i++) - { - select_item(String(initial[i])); - } - if(initial.length == 1) - { - search(items[initial[0]]["small_name"]); - document.getElementById("user_field").value = items[initial[0]]["small_name"]; } - } - function build_all_tries(dict) - { - for( var i in dict ) - { - add_item(dict[i]); + #drop_results { + max-width: 100%; + display: inline-block; + list-style-type: none; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } - } - - function add_item(item) - { - var id = item['id']; - add_to_tree(item['expanded_name'], id, expanded_name_trie); - add_to_tree(item['small_name'], id, small_name_trie); - add_to_tree(item['string'], id, string_trie); - } - - function add_to_tree(str, id, trie) - { - inner_trie = trie; - while( str ) - { - if( !inner_trie[str.charAt(0)] ) - { - new_trie = {}; - inner_trie[str.charAt(0)] = new_trie; - } - else - { - new_trie = inner_trie[str.charAt(0)]; - } - if( str.length == 1 ) - { - new_trie.isComplete = true; - new_trie.itemID = id; - } - inner_trie = new_trie; - str = str.substring(1); + #drop_results li { + overflow: hidden; + text-overflow: ellipsis; } - } - function search(input) - { - if( input.length == 0 && !show_from_noentry){ - dropdown([]); - return; - } - else if( input.length == 0 && show_from_noentry) - { - dropdown(items); //show all items - } - else - { - var trees = [] - var tr1 = getSubtree(input, expanded_name_trie); - trees.push(tr1); - var tr2 = getSubtree(input, small_name_trie); - trees.push(tr2); - var tr3 = getSubtree(input, string_trie); - trees.push(tr3); - var results = collate(trees); - dropdown(results); + #added_counter { + text-align: center; } - } - function getSubtree(input, given_trie) - { - /* - recursive function to return the trie accessed at input - */ - - if( input.length == 0 ){ - return given_trie; + #added_number, #addable_limit { + display: inline; } + </style> +</div> - else{ - var substr = input.substring(0, input.length - 1); - var last_char = input.charAt(input.length-1); - var subtrie = getSubtree(substr, given_trie); - if( !subtrie ) //substr not in the trie - { - return {}; - } - var indexed_trie = subtrie[last_char]; - return indexed_trie; - } - } +<script type="text/javascript"> + function searchableSelectMultipleWidgetEntry() { + let format_vars = { + "show_from_noentry": {{show_from_noentry|yesno:"true,false"}}, + "show_x_results": {{show_x_results|default:-1}}, + "results_scrollable": {{results_scrollable|yesno:"true,false"}}, + "selectable_limit": {{selectable_limit|default:-1}}, + "placeholder": "{{placeholder|default:"begin typing"}}" + }; - function serialize(trie) - { - /* - takes in a trie and returns a list of its item id's - */ - var itemIDs = []; - if ( !trie ) - { - return itemIDs; //empty, base case - } - for( var key in trie ) - { - if(key.length > 1) - { - continue; - } - itemIDs = itemIDs.concat(serialize(trie[key])); - } - if ( trie.isComplete ) - { - itemIDs.push( trie.itemID ); - } + let field_dataset = {{items|safe}}; - return itemIDs; - } + let field_initial = {{ initial|safe }}; - function collate(trees) - { - /* - takes a list of tries - returns a list of ids of objects that are available - */ - results = []; - for( var i in trees ) - { - var available_IDs = serialize(trees[i]); - for( var j=0; j<available_IDs.length; j++){ - var itemID = available_IDs[j]; - results[itemID] = items[itemID]; - } - } - return results; + //global + searchable_select_multiple_widget = new SearchableSelectMultipleWidget(format_vars, field_dataset, field_initial); } - function dropdown(ids) - { - /* - takes in a mapping of ids to objects in items - and displays them in the dropdown - */ - var drop = document.getElementById("drop_results"); - while(drop.firstChild) - { - drop.removeChild(drop.firstChild); - } + searchableSelectMultipleWidgetEntry(); - for( var id in ids ) - { - var result_entry = document.createElement("li"); - var result_button = document.createElement("a"); - var obj = items[id]; - var result_text = document.createTextNode(obj['small_name'] + " : " + obj['expanded_name']); - result_button.appendChild(result_text); - result_button.setAttribute('onclick', 'select_item("' + obj['id'] + '")'); - result_entry.appendChild(result_button); - drop.appendChild(result_entry); - } - - if( !drop.firstChild ) - { - drop.style.display = 'none'; - } - else - { - drop.style.display = 'inherit'; - } - } + /* + var show_from_noentry = context(show_from_noentry|yesno:"true,false") // whether to show any results before user starts typing + var show_x_results = context(show_x_results|default:-1) // how many results to show at a time, -1 shows all results + var results_scrollable = {{results_scrollable|yesno:"true,false") // whether list should be scrollable + var selectable_limit = {{selectable_limit|default:-1) // how many selections can be made, -1 allows infinitely many + var placeholder = "context(placeholder|default:"begin typing")" // placeholder that goes in text box - function select_item(item_id) - { - //TODO make faster - var item = items[item_id]; - if( (selectable_limit > -1 && added_items.length < selectable_limit) || selectable_limit < 0 ) + needed info + var items = context(items|safe) // items to add to trie. Type is a dictionary of dictionaries with structure: { - if( added_items.indexOf(item) == -1 ) - { - added_items.push(item); + id# : { + "id": any, identifiable on backend + "small_name": string, displayed first (before separator), searchable (use for e.g. username) + "expanded_name": string, displayed second (after separator), searchable (use for e.g. email address) + "string": string, not displayed, still searchable } } - update_selected_list(); - document.getElementById("user_field").focus(); - } - - function remove_item(item_ref) - { - - item = Object.values(items)[item_ref]; - var index = added_items.indexOf(item); - added_items.splice(index, 1); - - update_selected_list() - document.getElementById("user_field").focus(); - } - - function edit_item(item_id){ - var wf_type = "{{wf_type}}"; - parent.add_edit_wf(wf_type, item_id); - } - - function update_selected_list() - { - document.getElementById("added_number").innerText = added_items.length; - selector = document.getElementById('selector'); - selector.value = JSON.stringify(added_items); - added_list = document.getElementById('added_list'); - - while(selector.firstChild) - { - selector.removeChild(selector.firstChild); - } - while(added_list.firstChild) - { - added_list.removeChild(added_list.firstChild); - } - - list_html = ""; - - for( var key in added_items ) - { - item = added_items[key]; - - list_html += '<div class="list_entry"><p class="full_name">' - + item["expanded_name"] - + '</p><p class="small_name">, ' - + item["small_name"] - + '</p><button onclick="remove_item(' - + Object.values(items).indexOf(item) - + ')" class="btn-remove btn">remove</button>'; - {% if edit %} - list_html += '<button onclick="edit_item(' - + item['id'] - + ')" class="btn-remove btn">edit</button>'; - {% endif %} - list_html += '</div>'; - } - - added_list.innerHTML = list_html; - } - + used later: + context(selectable_limit): changes what number displays for field + context(name): form identifiable name, relevant for backend + // when submitted, form will contain field data in post with name as the key + context(placeholder): "greyed out" contents put into search field initially to guide user as to what they're searching for + context(initial): in search_field_init(), marked safe, an array of id's each referring to an id from items + */ </script> -<style> - .full_name { - display: inline-block; - } - .small_name { - display: inline-block; - } -</style> diff --git a/dashboard/src/templates/dashboard/table.html b/dashboard/src/templates/dashboard/table.html index b3f4b5f..0a37ded 100644 --- a/dashboard/src/templates/dashboard/table.html +++ b/dashboard/src/templates/dashboard/table.html @@ -4,11 +4,12 @@ {% block extrahead %} {{ block.super }} <!-- DataTables CSS --> - <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}" + <link href="{% static "bower_components/datatables.net-bs4/css/dataTables.bootstrap4.min.css" %}" rel="stylesheet"> <!-- DataTables Responsive CSS --> - <link href="{% static "bower_components/datatables-responsive/css/dataTables.responsive.css" %}" rel="stylesheet"> + <link href="{% static "bower_components/datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css" %}" + rel="stylesheet"> {% endblock extrahead %} {% block content %} @@ -34,8 +35,8 @@ {% block extrajs %} <!-- DataTables JavaScript --> - <script src={% static "bower_components/datatables/media/js/jquery.dataTables.min.js" %}></script> - <script src={% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js" %}></script> + <script src={% static "bower_components/datatables.net/js/jquery.dataTables.min.js" %}></script> + <script src={% static "bower_components/datatables.net-bs4/js/dataTables.bootstrap4.min.js" %}></script> <script src={% static "js/dataTables-sort.js" %}></script> |