Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
diff --git a/planetstack/core/xoslib/dashboards/xosAdminDashboard.html b/planetstack/core/xoslib/dashboards/xosAdminDashboard.html
index af9a0f4..bf10240 100644
--- a/planetstack/core/xoslib/dashboards/xosAdminDashboard.html
+++ b/planetstack/core/xoslib/dashboards/xosAdminDashboard.html
@@ -14,6 +14,7 @@
<script src="{{ STATIC_URL }}/js/xoslib/xos-validators.js"></script>
<script src="{{ STATIC_URL }}/js/xoslib/xos-backbone.js"></script>
<script src="{{ STATIC_URL }}/js/xoslib/xosHelper.js"></script>
+<script src="{{ STATIC_URL }}/js/picker.js"></script>
<script src="{{ STATIC_URL }}/js/xosAdminSite.js"></script>
<script type="text/template" id="xos-log-template">
diff --git a/planetstack/core/xoslib/static/css/xosAdminSite.css b/planetstack/core/xoslib/static/css/xosAdminSite.css
index ad9eb47..91d4928 100644
--- a/planetstack/core/xoslib/static/css/xosAdminSite.css
+++ b/planetstack/core/xoslib/static/css/xosAdminSite.css
@@ -107,3 +107,11 @@
#xos-confirm-dialog {
display: none;
}
+
+.picker_row {
+ display: table;
+}
+.picker_column {
+ display: table-cell;
+ padding: 10px;
+}
diff --git a/planetstack/core/xoslib/static/js/picker.js b/planetstack/core/xoslib/static/js/picker.js
new file mode 100644
index 0000000..0302cf4
--- /dev/null
+++ b/planetstack/core/xoslib/static/js/picker.js
@@ -0,0 +1,50 @@
+function init_picker(selector, ordered) {
+ //console.log("init_picker");
+ //console.log($(selector));
+
+ var addBtn = $(selector).find(".btn-picker-add");
+ var removeBtn = $(selector).find(".btn-picker-remove");
+ var upBtn = $(selector).find(".btn-picker-up");
+ var downBtn = $(selector).find(".btn-picker-down");
+ var from = $(selector).find(".select-picker-from");
+ var to = $(selector).find(".select-picker-to");
+
+ if (!ordered) {
+ upBtn.hide();
+ downBtn.hide();
+ }
+
+ addBtn.click(function(){
+ console.log("add");
+ from.find(":selected").each( function() {
+ to.append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");
+ $(this).remove();
+ });
+ });
+ removeBtn.click(function(){
+ console.log("remove");
+ to.find(":selected").each( function() {
+ from.append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");
+ $(this).remove();
+ });
+ });
+ upBtn.bind('click', function() {
+ to.find(":selected").each( function() {
+ var newPos = to.find('option').index(this) - 1;
+ if (newPos > -1) {
+ to.find("option").eq(newPos).before("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");
+ $(this).remove();
+ }
+ });
+ });
+ downBtn.bind('click', function() {
+ var countOptions = to.find("option").size();
+ to.find(":selected").each( function() {
+ var newPos = to.find("option").index(this) + 1;
+ if (newPos < countOptions) {
+ to.find("option").eq(newPos).after("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");
+ $(this).remove();
+ }
+ });
+ });
+};
diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
index 554ff5f..b0ee0dc 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
@@ -79,6 +79,20 @@
return Backbone.Model.prototype.save.call(this, attributes, options);
},
+ getChoices: function(fieldName, excludeChosen) {
+ choices=[];
+ if (fieldName in this.m2mFields) {
+ for (index in xos[this.m2mFields[fieldName]].models) {
+ candidate = xos[this.m2mFields[fieldName]].models[index];
+ if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
+ continue;
+ }
+ choices.push(candidate.id);
+ }
+ }
+ return choices;
+ },
+
/* If a 'validate' method is supplied, then it will be called
automatically on save. Unfortunately, save calls neither the
'error' nor the 'success' callback if the validator fails.
@@ -324,6 +338,7 @@
attrs.inputType = attrs.inputType || {};
attrs.foreignFields = attrs.foreignFields || {};
+ attrs.m2mFields = attrs.m2mFields || {};
attrs.readOnlyFields = attrs.readOnlyFields || [];
attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
@@ -473,9 +488,11 @@
define_model(this, { urlRoot: DEPLOYMENT_API,
relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
+ m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
modelName: "deployment",
listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
- detailFields: ["backend_status", "name", "backend_type", "admin_tenant"],
+ detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
+ inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
});
define_model(this, {urlRoot: IMAGE_API,
@@ -529,9 +546,10 @@
define_model(this, {urlRoot: FLAVOR_API,
modelName: "flavor",
+ m2mFields: {"deployments": "deployments"},
listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
- detailFields: ["backend_status", "name", "description", "flavor", "order", "default"],
- inputType: {"default": "checkbox"},
+ detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
+ inputType: {"default": "checkbox", "deployments": "picker"},
});
// enhanced REST
diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-util.js b/planetstack/core/xoslib/static/js/xoslib/xos-util.js
index 638ba9a..3fd597b 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xos-util.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xos-util.js
@@ -1,5 +1,15 @@
// misc utility functions
+function idInArray(id, arr) {
+ // because sometimes ids are strings and sometimes they're integers
+ for (index in arr) {
+ if (id.toString() == arr[index].toString()) {
+ return true;
+ }
+ }
+ return false;
+}
+
function assert(outcome, description) {
if (!outcome) {
console.log(description);
diff --git a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
index 65ccdbd..fe975aa 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
@@ -404,6 +404,8 @@
XOSDetailView = Marionette.ItemView.extend({
tagName: "div",
+ viewInitializers: [],
+
events: {"click button.btn-xos-save-continue": "submitContinueClicked",
"click button.btn-xos-save-leave": "submitLeaveClicked",
"click button.btn-xos-save-another": "submitAddAnotherClicked",
@@ -420,6 +422,12 @@
this.synchronous = false;
},
+ onShow: function() {
+ _.each(this.viewInitializers, function(initializer) {
+ initializer();
+ });
+ },
+
afterSave: function(e) {
},
@@ -574,7 +582,6 @@
onFormDataInvalid: function(errors) {
var self=this;
var markErrors = function(value, key) {
- console.log("name='" + key + "'");
var $inputElement = self.$el.find("[name='" + key + "']");
var $inputContainer = $inputElement.parent();
//$inputContainer.find(".help-inline").remove();
@@ -593,6 +600,7 @@
detailLinkFields: this.model.detailLinkFields,
inputType: this.model.inputType,
model: this.model,
+ detailView: this,
}},
});
@@ -972,6 +980,10 @@
return xos.idToName(id, collectionName, fieldName);
};
+makeIdToName = function(collectionName, fieldName) {
+ return function(id) { return idToName(id, collectionName, fieldName); }
+};
+
/* Constructs lists of <option> html blocks for items in a collection.
selectedId = the id of an object that should be selected, if any
diff --git a/planetstack/core/xoslib/templates/xosAdmin.html b/planetstack/core/xoslib/templates/xosAdmin.html
index acc318b..92b5046 100644
--- a/planetstack/core/xoslib/templates/xosAdmin.html
+++ b/planetstack/core/xoslib/templates/xosAdmin.html
@@ -159,6 +159,9 @@
<td><%= idToSelect(fieldName, model.attributes[fieldName], foreignFields[fieldName], "humanReadableName", readOnly) %></td>
<% } else if (inputType[fieldName] == "checkbox") { %>
<td><input type="checkbox" name="<%= fieldName %>" <% if (model.attributes[fieldName]) print("checked"); %><%= readOnly %>></td>
+ <% } else if (inputType[fieldName] == "picker") { %>
+ <% lookupFunc = makeIdToName(model.m2mFields[fieldName], "humanReadableName"); %>
+ <td><%= xosPickerTemplate({pickedItems: model.attributes[fieldName], unpickedItems: model.getChoices(fieldName,true), id: "picker_" + fieldName, detailView: detailView, lookupFunc: lookupFunc}) %></td>
<% } else if (fieldName=="backend_status") { %>
<td><%= xosBackendStatusTextTemplate.apply(this, args) %></td>
<% } else { %>
@@ -216,6 +219,42 @@
</div>
</script>
+
+<script type="text/template" id="xos-picker-template">
+ <!-- arguments: unpickedItems, pickedItems -->
+ <div id="<%= id %>">
+ <div class="picker_row">
+ <div class="picker_column">
+ <div>Available</div>
+ <select name="pickerfrom" class="select-picker-from" multiple size="5">
+ <% _.each(unpickedItems, function(item) { %>
+ <option value="<%= item %>"><%= lookupFunc? lookupFunc(item) : item %></option>
+ <% });%>
+ </select>
+ </div>
+ <div class="picker_column">
+ <br>
+ <div class="btn btn-success btn-picker-add">Add »</div><br><br>
+ <div class="btn btn-success btn-picker-remove">« Remove</div>
+ </div>
+ <div class="picker_column">
+ <div>Selected</div>
+ <select name="pickerto" class="select-picker-to" multiple size="5">
+ <% _.each(pickedItems, function(item) { %>
+ <option value="<%= item %>"><%= lookupFunc ? lookupFunc(item) : item %></option>
+ <% }); %>
+ </select>
+ </div>
+ <div class="picker_column">
+ <br>
+ <div class="btn btn-success btn-picker-up">Up</div><br><br>
+ <div class="btn btn-success btn-picker-down">Down</div>
+ </div>
+ </div>
+ </div>
+ <% detailView.viewInitializers.push( function() { init_picker("#" + id); } ); %>
+</script>
+
<script>
xosInlineDetailButtonsTemplate = _.template($("#xos-inline-detail-buttons-template").html());
xosListHeaderTemplate = _.template($("#xos-list-header-template").html());
@@ -224,5 +263,6 @@
xosDetailLinkTemplate = _.template($("#xos-detail-link-template").html());
xosBackendStatusIconTemplate = _.template($("#xos-backend-status-icon-template").html());
xosBackendStatusTextTemplate = _.template($("#xos-backend-status-text-template").html());
+xosPickerTemplate = _.template($("#xos-picker-template").html());
</script>