All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

Commit 2b82d493 authored by Leigh Stoller's avatar Leigh Stoller

Merge branch 'master' into 'master'

Picker library for making custom dropdown menus

See merge request !24
parents ce93b70f 993197d0
......@@ -317,70 +317,73 @@ left: -999em;
}
}
.cluster_picker_status button {
.picker button {
padding-left: 16px;
}
.cluster_picker_status .btn {
.picker .btn {
cursor: default;
background-color: #FFF;
border-radius: 4px;
text-align: left;
}
.cluster_picker_status .caret {
.picker .caret {
position: absolute;
right: 16px;
top: 15px;
}
.cluster_picker_status .btn-default:hover, .cluster_picker_status .btn-default:focus, .cluster_picker_status .btn-default:active, .cluster_picker_status .btn-default.active, .cluster_picker_status .open .dropdown-toggle.btn-default {
.picker .btn-default:hover, .picker .btn-default:focus, .picker .btn-default:active, .picker .btn-default.active, .picker .open .dropdown-toggle.btn-default {
background-color: #FFF;
}
.cluster_picker_status
.cluster_picker_status .btn-group.open .dropdown-toggle {
.picker .btn-group.open .dropdown-toggle {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(102, 175, 233, 0.6)
}
.cluster_picker_status.btn-group {width: 100%}
.cluster_picker_status .dropdown-menu {width: 100%;}
.picker.btn-group {width: 100%}
.picker .dropdown-menu {width: 100%;}
.cluster_picker_status li {
.picker li {
position: relative;
}
.cluster_picker_status .tooltip_div {
.picker .dropdown_icon, .picker .icon_position_1 {
position: absolute;
right: 10px;
top: 3px;
}
.cluster_picker_status .dropdown-toggle .picker_stats {
position: absolute;
right: 35px;
top: 6px;
z-index: 2;
.picker .icon_position_2 {
position: absolute;
top: 3px;
right: 35px;
}
.cluster_picker_status .dropdown-toggle .warning_icon {
position: absolute;
right: 60px;
top: 6px;
z-index: 2;
.picker .dropdown-toggle .icon_position_1 {
right: 35px;
top: 6px;
z-index: 2;
}
.cluster_picker_status .text-healthy {
.picker .dropdown-toggle .icon_position_2 {
right: 60px;
top: 6px;
z-index: 2;
}
.picker .text-healthy {
color: #8ab800;
}
.cluster_picker_status .text-unhealthy {
.picker .text-unhealthy {
color: #FF6600;
}
.cluster_picker_status .circle {
.picker .circle {
display: block;
border-radius: 50%;
border: 5px solid;
......@@ -389,142 +392,145 @@ left: -999em;
position: relative;
}
.cluster_picker_status .resource_healthy .circle {
.picker .resource_healthy .circle {
background-color: #449d44;
}
.cluster_picker_status .resource_unhealthy .circle {
.picker .resource_unhealthy .circle {
background-color: #ec971f;
}
.cluster_picker_status .resource_down .circle {
.picker .resource_down .circle {
background-color: #c9302c;
}
.cluster_picker_status .resource_inactive .circle {
.picker .resource_inactive .circle {
background-color: #ddd;
}
.cluster_picker_status .status_healthy .circle {
.picker .status_healthy .circle {
border-color: #398439;
}
.cluster_picker_status .status_unhealthy .circle {
.picker .status_unhealthy .circle {
border-color: #d58512;
}
.cluster_picker_status .status_down .circle {
.picker .status_down .circle {
border-color: #ac2925;
background-color: #c9302c;
}
.cluster_picker_status .status_inactive .circle {
.picker .status_inactive .circle {
border-color: #999;
}
.cluster_picker_status .resource_healthy .tooltip.right .tooltip-inner, .cluster_picker_status .status_healthy .tooltip.left .tooltip-inner {
.picker .resource_healthy .tooltip.right .tooltip-inner, .picker .status_healthy .tooltip.left .tooltip-inner {
background-color: #398439;
}
.cluster_picker_status .resource_healthy .tooltip.right .tooltip-arrow, .cluster_picker_status .status_healthy .tooltip.left .tooltip-arrow {
.picker .resource_healthy .tooltip.right .tooltip-arrow, .picker .status_healthy .tooltip.left .tooltip-arrow {
border-right-color: #398439;
border-left-color: #398439;
}
.cluster_picker_status .resource_unhealthy .tooltip.right .tooltip-inner, .cluster_picker_status .status_unhealthy .tooltip.left .tooltip-inner {
.picker .resource_unhealthy .tooltip.right .tooltip-inner, .picker .status_unhealthy .tooltip.left .tooltip-inner {
background-color: #d58512;
}
.cluster_picker_status .resource_unhealthy .tooltip.right .tooltip-arrow, .cluster_picker_status .status_unhealthy .tooltip.left .tooltip-arrow {
.picker .resource_unhealthy .tooltip.right .tooltip-arrow, .picker .status_unhealthy .tooltip.left .tooltip-arrow {
border-right-color: #d58512;
border-left-color: #d58512;
}
.cluster_picker_status .resource_down .tooltip.right .tooltip-inner, .cluster_picker_status .status_down .tooltip.left .tooltip-inner {
.picker .resource_down .tooltip.right .tooltip-inner, .picker .status_down .tooltip.left .tooltip-inner {
background-color: #ac2925;
}
.cluster_picker_status .resource_down .tooltip.right .tooltip-arrow, .cluster_picker_status .status_down .tooltip.left .tooltip-arrow {
.picker .resource_down .tooltip.right .tooltip-arrow, .picker .status_down .tooltip.left .tooltip-arrow {
border-right-color: #ac2925;
border-left-color: #ac2925;
}
.cluster_picker_status .resource_down .tooltip.right .tooltip-inner, .cluster_picker_status .status_down .tooltip.left .tooltip-inner {
.picker .resource_down .tooltip.right .tooltip-inner, .picker .status_down .tooltip.left .tooltip-inner {
background-color: #ac2925;
}
.cluster_picker_status .resource_down .tooltip.right .tooltip-arrow, .cluster_picker_status .status_down .tooltip.left .tooltip-arrow {
.picker .resource_down .tooltip.right .tooltip-arrow, .picker .status_down .tooltip.left .tooltip-arrow {
border-right-color: #ac2925;
border-left-color: #ac2925;
}
.cluster_picker_status .resource_inactive .tooltip.right .tooltip-inner, .cluster_picker_status .status_inactive .tooltip.left .tooltip-inner {
.picker .resource_inactive .tooltip.right .tooltip-inner, .picker .status_inactive .tooltip.left .tooltip-inner {
background-color: #999;
}
.cluster_picker_status .resource_inactive .tooltip.right .tooltip-arrow, .cluster_picker_status .status_inactive .tooltip.left .tooltip-arrow {
.picker .resource_inactive .tooltip.right .tooltip-arrow, .picker .status_inactive .tooltip.left .tooltip-arrow {
border-right-color: #999;
border-left-color: #999;
}
.cluster_picker_status .selected {
.picker .selected {
background-color: #f5f5f5;
}
.cluster_picker_status .dropdown-menu:hover .selected {
.picker .dropdown-menu:hover .selected {
background-color: #FFF;
}
.cluster_picker_status .tooltip .tooltip-inner div {
.picker .tooltip .tooltip-inner div {
text-align: left;
white-space: nowrap;
}
.cluster_picker_status .tooltip .tooltip-inner {
.picker .tooltip .tooltip-inner {
max-width: 400px;
}
.cluster_picker_status .reservation_tooltip {
position: absolute;
top: 3px;
right: 35px;
}
.cluster_picker_status .warning_icon {
.picker .warning_icon {
font-size: 18px;
}
.cluster_picker_status .warning_icon.confirm {
.picker .warning_icon.confirm {
color: #449d44;
}
.cluster_picker_status .warning_icon.warn {
.picker .warning_icon.warn {
color: #d58512;
}
.cluster_picker_status .no_reservation .tooltip.right .tooltip-inner, .cluster_picker_status .future_reservation .tooltip.right .tooltip-inner {
.picker .no_reservation .tooltip.right .tooltip-inner, .picker .future_reservation .tooltip.right .tooltip-inner {
background-color: #d58512;
}
.cluster_picker_status .no_reservation .tooltip.right .tooltip-arrow, .cluster_picker_status .future_reservation .tooltip.right .tooltip-inner {
.picker .no_reservation .tooltip.right .tooltip-arrow, .picker .future_reservation .tooltip.right .tooltip-inner {
border-right-color: #d58512;
border-left-color: #d58512;
}
.cluster_picker_status .reservation_tooltip .warning_icon {
.picker .has_reservation .tooltip.right .tooltip-inner, .picker .has_reservation .tooltip.left .tooltip-inner {
background-color: #398439;
}
.picker .has_reservation .tooltip.right .tooltip-arrow, .picker .has_reservation .tooltip.left .tooltip-arrow {
border-right-color: #398439;
border-left-color: #398439;
}
.picker .reservation_tooltip .warning_icon {
height: 20px;
}
.cluster_picker_status .reservation_tooltip .tooltip .tooltip-inner {
.picker .reservation_tooltip .tooltip .tooltip-inner {
width: 200px;
}
.cluster_picker_status .federatedDivider {
.picker .dropdown-menu .divider.withText {
overflow: visible;
margin: 24px 0 0;
}
.cluster_picker_status .federatedDivider > div {
.picker .dropdown-menu .divider.withText > div {
color: #aaa;
position: absolute;
top: -20px;
......
......@@ -434,6 +434,7 @@ function SPITFORM($formfields, $newuser, $errors)
REQUIRE_PPWIZARDSTART();
REQUIRE_JACKS_EDITOR();
REQUIRE_WIZARD_TEMPLATE();
REQUIRE_PICKER();
REQUIRE_FORMHELPERS();
REQUIRE_FILESTYLE();
REQUIRE_MARKED();
......@@ -529,7 +530,7 @@ if (!isset($create)) {
SPITFORM($defaults, false, array());
echo "<div style='display: none'><div id='jacks-dummy'></div></div>\n";
AddTemplateList(array("instantiate", "instantiate-new", "aboutapt", "aboutcloudlab", "aboutpnet", "waitwait-modal", "rspectextview-modal"));
AddTemplateList(array("instantiate", "instantiate-new", "aboutapt", "aboutcloudlab", "aboutpnet", "waitwait-modal", "rspectextview-modal", "picker-template"));
SPITFOOTER();
return;
}
......
......@@ -156,7 +156,7 @@ $(function ()
CookieCollapse('#profile_name > span', 'pp_collpased');
_.defer(function () {
monitor = JSON.parse(_.unescape($('script#amstatus-json').html()));
CreateClusterStatus();
//CreateClusterStatus();
});
$('#waitwait_div').html(waitwaitString);
$('#waitwait-modal').modal({ backdrop: 'static', keyboard: false, show: false });
......@@ -928,7 +928,6 @@ $(function ()
}
function CreateClusterStatus() {
//console.log("CreateClusterStatus", monitor);
if (monitor == null || $.isEmptyObject(monitor)) {
return;
}
......@@ -953,16 +952,47 @@ $(function ()
}
var which = $(this).parent().attr('id');
var html = wt.ClusterStatusHTML($('#'+which+' .form-control option'), window.FEDERATEDLIST);
// Decide what classes each option element should have
var pickerTarget = '#'+which+' .select_where';
var attributes = {}
$('#'+which+' .form-control').after(html);
$('#'+which+' select.form-control').addClass('hidden');
$(pickerTarget).find('option').each(function() {
var attrs = {}
var siteName = $(this).attr('value');
html.find('.dropdown-menu a').on('click', function() {
wt.StatusClickEvent(html, this);
$('#'+which+' .form-control').val($('#'+which+' .cluster_picker_status .value').html());
// Hide "Please Select" option
if (siteName == "") {
attrs['class'] = 'hidden enabled';
}
else if ($(this).prop('disabled')) {
attrs['class'] = 'disabled';
attrs['tooltip'] = {
placement: 'right',
title: '<div>This testbed is incompatible with the selected profile</div>'
}
}
else {
attrs['class'] = 'enabled';
}
if (_.contains(window.FEDERATEDLIST, $(this).attr('value'))) {
attrs['class'] += " federated";
}
attributes[siteName] = attrs;
});
var dividers = [{ match: 'class',
key: 'federated',
text: 'Federated Clusters'
},
{ match: 'class',
key: 'disabled'
}];
picker.MakePicker(pickerTarget, wt.StatusClickEvent, attributes, dividers, {class: 'cluster_picker_status'});
// Assign health ratings and icons
_.each(amlist, function(name, key) {
var data = monitor[key];
var rating, classes;
......@@ -1006,6 +1036,7 @@ $(function ()
}
var project = $('#profile_pid').val();
var projectReservations = {}
$('#reservation_confirmation').addClass('hidden');
$('#reservation_warning').addClass('hidden');
......@@ -1027,22 +1058,42 @@ $(function ()
if (_.has(resinfo, cluster) && resinfo[cluster] != null) {
if (_.has(resinfo[cluster], 'reservations') && resinfo[cluster]['reservations'] != null) {
_.each(resinfo[cluster]['reservations'], function(types, resproj) {
var earliest = null;
// Find earliest reservation time
_.each(types, function(time) {
if (earliest == null || earliest > time) {
earliest = time;
}
});
// Current project has a reservation
// Used for cluster icons
if (project == resproj) {
hasReservation = true;
click = true;
// Get nearest time
_.each(types, function(time) {
if (start == null || start > time) {
start = time;
}
});
start = earliest;
}
// Icon for projects
var projectClass = 'futureReservation';
var priority = 1; // Used for sorting projects
var now = new Date();
var startTime = new Date(parseInt(earliest)*1000);
if (startTime < now) {
projectClass = 'hasReservation';
priority = 2;
}
projectReservations[resproj] = {
class: projectClass,
attr: {'data-priority': priority}
}
});
}
if (_.has(resinfo[cluster], 'pressure') && resinfo[cluster]['pressure'] != null) {
if (!hasReservation) {
console.log('here');
_.each(resinfo[cluster]['pressure'], function(reslist) {
if (_.has(reslist, project)) {
if (start == null || start > reslist[project][0][0]) {
......@@ -1058,10 +1109,10 @@ $(function ()
$(this).attr('data-res-pid', project);
$(this).attr('data-res-start', start);
if (end != null) {
$(this).removeAttr('data-now');
$(this).removeAttr('data-now');
$(this).attr('data-res-end', end);
target.append(wt.ReservationWarningHTML());
target.append(wt.ReservationWarningHTML('cluster', 2));
}
else {
$(this).removeAttr('data-res-end');
......@@ -1070,11 +1121,11 @@ $(function ()
if (startTime < now) {
$(this).attr('data-now', 'true');
target.append(wt.HasReservationHTML(project));
target.append(wt.HasReservationHTML(project, 'cluster', 2));
}
else {
$(this).attr('data-now', 'false');
target.append(wt.FutureReservationHTML(project));
target.append(wt.FutureReservationHTML(project, 'cluster', 2));
}
}
$('.reservation_tooltip > div').tooltip();
......@@ -1100,6 +1151,17 @@ $(function ()
$('#'+which+' .cluster_picker_status .dropdown-menu .selected a').click();
}
});
if (_.keys(projectReservations).length > 0 && $('#profile_pid_picker').length == 0) {
picker.MakePicker('#profile_pid', wt.ResClickEvent, projectReservations);
// Add icons
$('#profile_pid_picker .dropdown-menu .hasReservation a').append(wt.HasReservationHTML(project, 'project', 1))
$('#profile_pid_picker .dropdown-menu .futureReservation a').append(wt.FutureReservationHTML(project, 'project', 1))
$('#profile_pid_picker .dropdown-menu > li').sort(SortProfileList).prependTo($('#profile_pid_picker .dropdown-menu'));
$($('#profile_pid_picker .dropdown-menu a')[0]).click();
}
}
function SortClusterStatus(a, b) {
......@@ -1123,6 +1185,14 @@ $(function ()
return +b.dataset.rating - +a.dataset.rating;
}
function SortProfileList(a, b) {
if ((!a.dataset.priority && b.dataset.priority) || (a.dataset.priority < b.dataset.priority)) {
return 1;
}
return -1;
}
function SwitchJacks(which) {
if (which == 'small' && $('#stepsContainer-p-2 #inline_jacks').html() == '') {
$('#stepsContainer #finalize_container').removeClass('col-lg-12 col-md-12 col-sm-12');
......@@ -1525,7 +1595,7 @@ $(function ()
" </label> " +
" <div class='col-sm-6'>" +
" <select name='where' id='profile_where' " +
" class='form-control'>" +
" class='form-control select_where'>" +
" <option value=''>Please Select</option>" +
options +
" </select>" +
......@@ -1548,8 +1618,8 @@ $(function ()
" Site " + siteid + " Cluster:</a>" +
" </label> " +
" <div class='col-sm-6'>" +
" <select name=\"sites[" + siteid + "]\"" +
" class='form-control'>" +
" <select id='site"+sitenum+"_selector' name=\"sites[" + siteid + "]\"" +
" class='form-control select_where'>" +
" <option value=''>Please Select</option>" +
options +
" </select>" +
......@@ -1566,7 +1636,6 @@ $(function ()
$("#cluster_selector").html("");
$("#cluster_selector").html(html);
updateWhere();
CreateClusterStatus();
$("#cluster_selector").removeClass("hidden");
}
......@@ -1782,7 +1851,7 @@ $(function ()
var nodes = nodesBySite[siteId];
var sitenum = siteIdToSiteNum[siteId];
var domid = '#cluster_selector #site' + sitenum + 'cluster' +
'.cluster-group';
' .cluster-group';
if (nodes) {
updateSiteConstraints(nodes, $(domid));
}
......@@ -1791,6 +1860,11 @@ $(function ()
}
})
}
// Moved here to deal with race condition of custer status
// getting built before constraints were finished running
$($('#profile_pid_picker .dropdown-menu a')[0]).click();
CreateClusterStatus();
}
function updateSiteConstraints(nodes, domNode)
......
$(function() {
window.picker = (function() {
var templates = APT_OPTIONS.fetchTemplateList(['picker-template']);
var pickerTemplate = _.template(templates['picker-template']);
/* target - <select> element to picker
clickCallback - Function that gets called when an item is clicked on
optionAttributes - If provided, attributes for specific list items to be added to the li HTML for that item.
Matched by item's value.
example:
{
"Cloudlab Utah": {
class: "class-1 class-2"
attr: { attr-1: "value-1", attr-2: "value-2" },
tooltip: {
placement: 'right',
title: '<div>This testbed is incompatible with the selected profile</div>'
}
}
}
dividers - If provided, the list elements will be separated by dividers as defined by the object.
name is optional
All elements that match the condition will be but BELOW the divider.
If an element matches more than one condition, it will be put under the bottom-most match.
Order of dividers will be from top to bottom.
example:
[
{
match: 'class',
key: 'federated',
text: 'Federated Clusters'
},
{
match: 'attr',
key: 'data-health',
value: '0',
text: 'Clusters Down'
},
{
match: 'attr',
key: 'disabled'
}
]
pickerAttributes - HTML attributes to be added to the list container
example:
{
id: 'cluster1_picker',
class: 'cluster_picker_status cluster1',
attr: { data-cluser:, '1' }
}
*/
function MakePicker(target, clickCallback=null, optionAttributes=null, dividers=null, pickerAttributes=null) {
if ($(target).hasClass('pickered')) {
return 0;
}
$(target).addClass('pickered');
var options = $(target).find('option');
var items = MakeListElements(options, optionAttributes);
var divided = MakeDividers(items, dividers);
if (!pickerAttributes) {
pickerAttributes = {}
}
if (!pickerAttributes['id'] && $(target).attr('id')) {
pickerAttributes['id'] = $(target).attr('id') + '_picker';
}
var html = pickerTemplate({
items: divided,
pickerAttributes: pickerAttributes
});
$(target).after(html);
$(target).addClass('hidden');
var container = '#'+pickerAttributes['id'];
$(container).find('.dropdown-menu a').on('click', function() {
if (!$(this).hasClass('disabled')) {
PickerItemClickEvent(target, container, this);
if (clickCallback) { clickCallback(container, this, target); }
}
});
$(target).find('option:selected').removeAttr('')
$('[data-toggle="tooltip"]').tooltip();
return 1;
}
<