diff options
Diffstat (limited to 'src/templates/dashboard')
-rw-r--r-- | src/templates/dashboard/genericselect.html | 104 | ||||
-rw-r--r-- | src/templates/dashboard/lab_detail.html | 71 | ||||
-rw-r--r-- | src/templates/dashboard/lab_list.html | 44 | ||||
-rw-r--r-- | src/templates/dashboard/landing.html | 159 | ||||
-rw-r--r-- | src/templates/dashboard/multiple_select_filter_widget.html | 389 | ||||
-rw-r--r-- | src/templates/dashboard/resource.html | 10 | ||||
-rw-r--r-- | src/templates/dashboard/resource_all.html | 10 | ||||
-rw-r--r-- | src/templates/dashboard/searchable_select_multiple.html | 246 | ||||
-rw-r--r-- | src/templates/dashboard/table.html | 9 |
9 files changed, 598 insertions, 444 deletions
diff --git a/src/templates/dashboard/genericselect.html b/src/templates/dashboard/genericselect.html new file mode 100644 index 0000000..441d8dc --- /dev/null +++ b/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/src/templates/dashboard/lab_detail.html b/src/templates/dashboard/lab_detail.html index 7938e86..336b32e 100644 --- a/src/templates/dashboard/lab_detail.html +++ b/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><a href="/resource/profiles/{{ profile.id }}" class="btn btn-primary">Profile</a></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/src/templates/dashboard/lab_list.html b/src/templates/dashboard/lab_list.html index c459dd9..9cde80c 100644 --- a/src/templates/dashboard/lab_list.html +++ b/src/templates/dashboard/lab_list.html @@ -1,26 +1,28 @@ {% extends "base.html" %} {% block content %} - <h2>Labs</h2> - <div class="card_container"> +<h2>Labs</h2> +<div class="card_container"> {% for lab in labs %} - <div class="detail_card"> - <div> - <h3>{{lab.name}}</h3> - <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> - </div> - <a class="btn btn-primary" href="/lab/{{lab.name}}/">Details</a> + <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> -{% endblock %} + {% endfor %} +</div> +{% endblock %}
\ No newline at end of file diff --git a/src/templates/dashboard/landing.html b/src/templates/dashboard/landing.html index fb75d5f..e6a235f 100644 --- a/src/templates/dashboard/landing.html +++ b/src/templates/dashboard/landing.html @@ -2,27 +2,30 @@ {% load staticfiles %} {% block content %} - <div class="" style="text-align: center;"> - {% if not request.user.is_anonymous %} - {% if not request.user.userprofile.ssh_public_key %} - <h4 style="display: inline; text-align: center; border: 3px solid red; padding: 10px; border-radius: 10000px; height: 40px;"> - 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 - </h4> - {% endif %} - {% else %} - {% endif %} +<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; } @@ -39,81 +42,117 @@ display: grid; grid-template-columns: 1fr 30px 1fr; } + .grid_panel { padding: 30px; } + .btn-primary { margin: 10px; } + h2 { border-bottom: 1px solid #cccccc; } - h1 { - } -</style> -<div class="landing_container"> - <div class="info_panel grid_panel"> - <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> + 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 %} - <h2 style="margin-top: 50px;">Returning Users:</h2> - <p>If you're a returning user, some of the following options may be of interest:</p> - <button class="wf_create btn btn-primary" onclick="cwf(3)">Snapshot a Host</button> - <a class="wf_create btn btn-primary" href="{% url 'account:my-bookings' %}">My Bookings</a> - {% if manager == True %} - <button class="wf_continue btn btn-primary" onclick="continue_wf()">Continue Unfinished Workflow</button> - {% endif %} - {% endif %} - </div> - <div class=""> - </div> - <div class="actions_panel grid_panel"> - <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='wf_create_div'> - - <button class="wf_create btn btn-primary" onclick="cwf(0)">Book a Pod</button> - <button class="wf_create btn btn-primary" onclick="cwf(1)">Design a Pod</button> - <button class="wf_create btn btn-primary" onclick="cwf(2)">Configure a Pod</button> - {% endif %} + <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/"); } - </script> <div class="hidden_form" id="form_div"> @@ -130,4 +169,4 @@ {% block vport_comm %} {% endblock %} -{% endblock content %} +{% endblock content %}
\ No newline at end of file diff --git a/src/templates/dashboard/multiple_select_filter_widget.html b/src/templates/dashboard/multiple_select_filter_widget.html index c7a1943..3a7e148 100644 --- a/src/templates/dashboard/multiple_select_filter_widget.html +++ b/src/templates/dashboard/multiple_select_filter_widget.html @@ -4,6 +4,7 @@ grid-template-columns: 1fr 1fr 1fr; border: 0px; } + .class_grid_wrapper { border: 0px; text-align: center; @@ -20,112 +21,92 @@ display: grid; grid-template-columns: 1fr 1fr; } + .grid-item { cursor: pointer; - border:1px solid #cccccc; + border: 1px solid #cccccc; border-radius: 5px; - margin:20px; + margin: 20px; height: 200px; padding: 7px; - transition-property: box-shadow, background-color; - transition-duration: .2s; -} - -.grid-item:hover { - box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.45); - transition-property: box-shadow; - 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); } .selected_node { - box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.45); - background-color: #fff; - 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; } .disabled_node { cursor: not-allowed; background-color: #EFEFEF; - transition-property: box-shadow; - transition-duration: .2s; - border: 1px solid #ccc; } -.disabled_node:hover { -} +.disabled_node:hover {} .cleared_node { background-color: #FFFFFF; } -.grid-item-header -{ +.grid-item-header { font-weight: bold; font-size: 20px; margin-top: 10px; } -#dropdown_wrapper > div > h5 { - margin: 12px; - display: inline-block; - vertical-align: middle; +.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; } -#dropdown_wrapper > div > button { - padding: 7px; +.dropdown_item > button { margin: 2px; - float: right; - width: 80px; + justify-self: end; } -#dropdown_wrapper > div > input { - padding: 7px; - margin: 2px; - float: right; - width: 300px; - width: calc(100% - 240px); + +.dropdown_item > h5 { + margin: auto; } -#dropdown_wrapper > div { - border:2px; - border-style:none; - border-color:black; - border-radius: 5px; - margin:20px; - padding: 2px; - box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75); - transition-property: box-shadow, background-color; - transition-duration: .2s; - display: inline-block; - vertical-align: middle; +.dropdown_item > input { + padding: 7px; + margin: 2px; + width: 90%; } #dropdown_wrapper { display: grid; - grid-template-columns: 3fr 5fr; + grid-template-columns: 4fr 5fr; } - </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> +{% 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"> + <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 %}Add{% else %}Select{% endif %}</button> + </div> + {% endfor %} </div> - <input type="hidden" name="{{obj.id}}_selected" value="false"/> - </div> - {% endfor %} - </div> </div> {% endfor %} </div> @@ -135,63 +116,55 @@ <script> var initialized = false; -var mapping = {{ mapping|safe }}; +var inputs = []; +var graph_neighbors = {{ neighbors|safe }}; var filter_items = {{ filter_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 %} +{% if initial_value %} -function make_selection( selection_data ){ - if(!initialized) { - filter_field_init(); - } - for(var k in selection_data) { - selected_items = selection_data[k]; - for( var selected_item in selected_items ){ - var node = filter_items[selected_item]; - if(!node['multiple']){ - var input_value = selected_items[selected_item]; - if( input_value != 'false' ) { - select(node); - markAndSweep(node); - } - var div = document.getElementById(selected_item) - var inputs = div.parentNode.getElementsByTagName("input") - var input = div.parentNode.getElementsByTagName("input")[0] - input.value = input_value; - updateResult(selected_item); - } else { - make_multiple_selection(selected_items, selected_item); +var initial_value = {{ initial_value|safe }}; + + +function make_selection( initial_data ){ + try_init(); + for(var item_class in initial_data) { + var selected_items = initial_data[item_class]; + for( var node_id in selected_items ){ + var node = filter_items[node_id]; + var selection_data = selected_items[node_id] + if( selection_data.selected ) { + select(node); + markAndSweep(node); + updateResult(node); + } + if(node['multiple']){ + make_multiple_selection(node, selection_data); } } } } -function make_multiple_selection(data, item_class){ - var node = filter_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 make_multiple_selection(node, selection_data){ + prepop_data = selection_data.values; + for(var k in prepop_data){ + var div = add_item_prepopulate(node, prepop_data[k]); + updateObjectResult(node, div.id, prepop_data[k]); } } +make_selection({{initial_value|safe}}); + +{% endif %} + function markAndSweep(root){ - for(var nodeId in filter_items) { - node = filter_items[nodeId]; + for(var i in filter_items) { + node = filter_items[i]; node['marked'] = true; //mark all nodes - //clears grey background of everything } - toCheck = []; - toCheck.push(root); - + toCheck = [root]; while(toCheck.length > 0){ node = toCheck.pop(); if(!node['marked']) { @@ -199,11 +172,10 @@ function markAndSweep(root){ 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]; + if(node['follow'] || node == root){ //add neighbors if we want to follow this node + var neighbors = graph_neighbors[node.id]; + for(var i in neighbors) { + var neighId = neighbors[i]; var neighbor = filter_items[neighId]; toCheck.push(neighbor); } @@ -211,8 +183,8 @@ function markAndSweep(root){ } //now remove all nodes still marked - for(var nodeId in filter_items){ - node = filter_items[nodeId]; + for(var i in filter_items){ + node = filter_items[i]; if(node['marked']){ disable_node(node); } @@ -223,7 +195,7 @@ function process(node) { if(node['selected']) { markAndSweep(node); } - else { + else { //TODO: make this not dumb var selected = [] //remember the currently selected, then reset everything and reselect one at a time for(var nodeId in filter_items) { @@ -232,7 +204,6 @@ function process(node) { selected.push(node); } clear(node); - } for(var i=0; i<selected.length; i++) { node = selected[i]; @@ -248,9 +219,6 @@ function select(node) { 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) { @@ -260,7 +228,6 @@ function clear(node) { 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(node) { @@ -270,60 +237,54 @@ function disable_node(node) { 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){ - filter_field_init(); - } - var element = document.getElementById(id); +function processClick(id){ + try_init(); var node = filter_items[id]; - if(!node['selectable']){ + if(!node['selectable']) return; + + if(node['multiple']){ + return processClickMultiple(node); + } else { + return processClickSingle(node); } - if(multiple){ - return processClickMultipleObject(node); - } +} + +function processClickSingle(node){ node['selected'] = !node['selected']; //toggle on click if(node['selected']) { select(node); - } - else { + } else { clear(node); } process(node); - updateResult(id); + updateResult(node); } -function processClickMultipleObject(node){ +function processClickMultiple(node){ select(node); - add_node(node); + var div = add_node(node); process(node); + updateObjectResult(node, div.id, ""); } function add_node(node){ - return add_item_prepopulate(node, {}); + return add_item_prepopulate(node, false); } -inputs = [] - -function restrictchars(input) -{ - if( input.validity.patternMismatch ) - { +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) -{ +function checkunique(tocheck){ val = tocheck.value; for( var i = 0; i < inputs.length; i++ ) { @@ -337,109 +298,83 @@ function checkunique(tocheck) 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.appendChild(document.createTextNode(node['name'])); - div.appendChild(label); - button = document.createElement("BUTTON"); +function make_remove_button(div, node){ + var button = document.createElement("BUTTON"); button.type = "button"; button.appendChild(document.createTextNode("Remove")); button.classList.add("btn-danger"); button.classList.add("btn"); - div.appendChild(button); - 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.onclick = function(){ - remove_dropdown(div.id); + remove_dropdown(div.id, node.id); } + return button; +} + +function make_input(div, node, prepopulate){ + var input = document.createElement("INPUT"); + input.type = node.form.type; + input.name = node.id + node.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 = node.form.placeholder; + inputs.push(input); + input.onchange = function() { updateObjectResult(node, div.id, input.value); restrictchars(this); }; + input.oninput = function() { restrictchars(this); }; + if(prepopulate) + input.value = prepopulate; + return input; +} + +function add_item_prepopulate(node, prepopulate){ + var div = document.createElement("DIV"); + div.id = "dropdown_" + dropdown_count; + div.classList.add("dropdown_item"); + dropdown_count++; + var label = document.createElement("H5") + label.appendChild(document.createTextNode(node['name'])) + div.appendChild(label); + div.appendChild(make_input(div, node, prepopulate)); + div.appendChild(make_remove_button(div, node)); document.getElementById("dropdown_wrapper").appendChild(div); - var linebreak = document.createElement("BR"); - document.getElementById("dropdown_wrapper").appendChild(linebreak); - updateObjectResult(div); return div; } -function remove_dropdown(id){ - var div = document.getElementById(id); +function remove_dropdown(div_id, node_id){ + var div = document.getElementById(div_id); + var node = filter_items[node_id] var parent = div.parentNode; div.parentNode.removeChild(div); + delete result[node.class][node.id]['values'][div.id]; + //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(filter_items[div_class]); + if(jQuery.isEmptyObject(result[node.class][node.id]['values'])){ + delete result[node.class][node.id]; + clear(node); } } - -function updateResult(nodeId){ - if(!initialized){ - filter_field_init(); - } - if(!filter_items[nodeId]['multiple']){ - var node = document.getElementById(nodeId); - var value = {} - value[nodeId] = node.parentNode.getElementsByTagName("input")[0].value; - result[node.parentNode.parentNode.id] = {}; - result[node.parentNode.parentNode.id][nodeId] = value; +function updateResult(node){ + try_init(); + if(!node['multiple']){ + result[node.class][node.id] = {selected: node.selected, id: node.model_id} + if(!node.selected) + delete result[node.class][node.id]; } } -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; +function updateObjectResult(node, childKey, childValue){ + try_init(); + if(!result[node.class][node.id]) + result[node.class][node.id] = {selected: true, id: node.model_id, values: {}} + + result[node.class][node.id]['values'][childKey] = childValue; } -function filter_field_init() { +function try_init() { + if(initialized) return; for(nodeId in filter_items) { - element = document.getElementById(nodeId); - node = filter_items[nodeId]; - result[element.parentNode.parentNode.id] = {} + var element = document.getElementById(nodeId); + var node = filter_items[nodeId]; + result[node.class] = {} } initialized = true; } diff --git a/src/templates/dashboard/resource.html b/src/templates/dashboard/resource.html index 28e7998..f36ee7b 100644 --- a/src/templates/dashboard/resource.html +++ b/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/src/templates/dashboard/resource_all.html b/src/templates/dashboard/resource_all.html index 0b0d0d4..fb8cc7e 100644 --- a/src/templates/dashboard/resource_all.html +++ b/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/src/templates/dashboard/searchable_select_multiple.html b/src/templates/dashboard/searchable_select_multiple.html index c08fbe5..91ed09c 100644 --- a/src/templates/dashboard/searchable_select_multiple.html +++ b/src/templates/dashboard/searchable_select_multiple.html @@ -1,41 +1,57 @@ <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> -<div class="autocomplete" style="width:400px;"> +<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> - <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="{{initial.name}}" oninput="search(this.value)" + <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> + + <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="" oninput="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; } @@ -43,69 +59,135 @@ 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%; + } + + .btn-remove { + float: right; + height: 30px; + margin: 4px; + padding: 1px; + max-width: 20%; + width: 15%; + min-width: 70px; + overflow: hidden; + text-overflow: ellipsis; + } + + .entry_tooltip { + display: none; + } + + #drop_results li a:hover .entry_tooltip { + position: absolute; + background: #444; + color: #ddd; + text-align: center; + font-size: 12pt; + border-radius: 3px; + } - .full_name { + #drop_results { + max-width: 100%; display: inline-block; + list-style-type: none; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + #drop_results li { + overflow: hidden; + text-overflow: ellipsis; } + #added_counter { + text-align: center; + } + + #added_number, #addable_limit { + display: inline; + } </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}}"; + var show_from_noentry = {{show_from_noentry|yesno:"true,false"}}; // whether to show any results before user starts typing + var show_x_results = {{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 = "{{placeholder|default:"begin typing"}}"; // placeholder that goes in text box //needed info - var items = {{items|safe}} + var items = {{items|safe}} // items to add to trie. Type is a dictionary of dictionaries with structure: + /* + { + 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 + } + } + */ + + /* used later: + {{ selectable_limit }}: changes what number displays for field + {{ name }}: form identifiable name, relevant for backend + // when submitted, form will contain field data in post with name as the key + {{ placeholder }}: "greyed out" contents put into search field initially to guide user as to what they're searching for + {{ initial }}: in search_field_init(), marked safe, an array of id's each referring to an id from items + */ //tries var expanded_name_trie = {} @@ -116,18 +198,6 @@ string_trie.isComplete = false; var added_items = []; - var initial_log = {{ initial|safe }}; - - 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; - } search_field_init(); @@ -200,7 +270,11 @@ if( str.length == 1 ) { new_trie.isComplete = true; - new_trie.itemID = id; + if( !new_trie.ids ) + { + new_trie.ids = []; + } + new_trie.ids.push(id); } inner_trie = new_trie; str = str.substring(1); @@ -274,7 +348,7 @@ } if ( trie.isComplete ) { - itemIDs.push( trie.itemID ); + itemIDs.push(...trie.ids); } return itemIDs; @@ -298,6 +372,14 @@ return results; } + function generate_element_text(obj) + { + var content_strings = [obj['expanded_name'], obj['small_name'], obj['string']].filter(x => Boolean(x)); + var result = content_strings.shift(); + if( result == null || content_strings.length < 1) return result; + return result + " (" + content_strings.join(", ") + ")"; + } + function dropdown(ids) { /* @@ -315,27 +397,34 @@ 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); + var result_text = generate_element_text(obj); + result_button.appendChild(document.createTextNode(result_text)); result_button.setAttribute('onclick', 'select_item("' + obj['id'] + '")'); + var tooltip = document.createElement("span"); + var tooltiptext = document.createTextNode(result_text); + tooltip.appendChild(tooltiptext); + tooltip.setAttribute('class', 'entry_tooltip'); + result_button.appendChild(tooltip); result_entry.appendChild(result_button); drop.appendChild(result_entry); } + var scroll_restrictor = document.getElementById("scroll_restrictor"); + if( !drop.firstChild ) { - drop.style.display = 'none'; + scroll_restrictor.style.visibility = 'hidden'; } else { - drop.style.display = 'inherit'; + scroll_restrictor.style.visibility = 'inherit'; } } function select_item(item_id) { //TODO make faster - var item = items[item_id]; + var item = items[item_id]['id']; if( (selectable_limit > -1 && added_items.length < selectable_limit) || selectable_limit < 0 ) { if( added_items.indexOf(item) == -1 ) @@ -344,7 +433,10 @@ } } update_selected_list(); + // clear search bar contents + document.getElementById("user_field").value = ""; document.getElementById("user_field").focus(); + search(""); } function remove_item(item_ref) @@ -357,11 +449,6 @@ 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; @@ -382,32 +469,21 @@ for( var key in added_items ) { - item = added_items[key]; + item_id = added_items[key]; + item = items[item_id]; - 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(' + var element_entry_text = generate_element_text(item); + + list_html += '<div class="list_entry">' + + '<p class="added_entry_text">' + + element_entry_text + + '</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; } - </script> -<style> - .full_name { - display: inline-block; - } - .small_name { - display: inline-block; - } -</style> diff --git a/src/templates/dashboard/table.html b/src/templates/dashboard/table.html index b3f4b5f..0a37ded 100644 --- a/src/templates/dashboard/table.html +++ b/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> |