Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Adds support for negotations #10

Merged
merged 11 commits into from

2 participants

@daf
daf commented

As outlined in Requesting Roles, Enrolling in Observatory, and Requesting Resource Access

From the UI a user/org manager is now able to:

  • Request observatory enrollment
  • Request a role in an observatory
  • Acquire a resource in an observatory
  • Exclusively acquire a resource in an observatory
  • Invite a user to enroll in an observatory
  • Invite a user to take a role in an observatory
  • Accept/Reject negotiations from the "Open Requests" table in either the Org or User facepages.

Initiating Requests

Most of these actions are initiated from the page dropdown, and get changed out based on current perms/roles (example):

Screen shot 2013-04-01 at 6 21 39 PM

If Request Role is picked, a modal is brought up:

Screen shot 2013-04-01 at 6 23 48 PM

(these vary based on which action is selected)

Accepting/Rejecting Requests

Open/closed requests are presented on the facepages of Org/UserInfo. A user with correct permissions on the correct page may accept or reject a negotiation. Allowable approved items will feature a new column in their data table:

Screen shot 2013-04-01 at 6 14 15 PM

And clicking that row will yield a modal to Accept/Reject:

Screen shot 2013-04-01 at 6 14 23 PM

daf added some commits
@daf daf Typo 1c86ac8
@daf daf Request enroll for user working, framework set for others 3e18210
@daf daf Role request for orgs e7c36cc
@daf daf Invite user negotiations for org manager ab16b99
@daf daf Offer user role for orgs 8c87e82
@daf daf Refresh view after any negotiation completion c6c7c3e
@daf daf Fix for request_role template, using wrong attribute b759b0b
@daf daf Add actor_id to session model e31df82
@daf daf Request/Release and Exclusive access to devices c63b1a4
@daf daf Accept/reject negotiations from UI
Adds two optional params to the DataTable view:
- popup_view: A backbone view (should be a modal popup) to instantiate when the added column is clicked
- popup_label: Label to use for the column that indicates a popup
- popup_filter_method: callback to determine if the current passed in row should have the modal behavior.
ffc90a5
@daf daf Allow users to accept negotiations too 0e41227
@tboteler-ooi tboteler-ooi merged commit c422203 into ooici:master
@daf daf deleted the daf:negotiations branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 27, 2013
  1. @daf

    Typo

    daf authored
  2. @daf
  3. @daf

    Role request for orgs

    daf authored
  4. @daf
  5. @daf

    Offer user role for orgs

    daf authored
  6. @daf
Commits on Mar 28, 2013
  1. @daf
  2. @daf

    Add actor_id to session model

    daf authored
  3. @daf
Commits on Apr 1, 2013
  1. @daf

    Accept/reject negotiations from UI

    daf authored
    Adds two optional params to the DataTable view:
    - popup_view: A backbone view (should be a modal popup) to instantiate when the added column is clicked
    - popup_label: Label to use for the column that indicates a popup
    - popup_filter_method: callback to determine if the current passed in row should have the modal behavior.
  2. @daf
This page is out of date. Refresh to see the latest.
View
86 main.py
@@ -145,11 +145,89 @@ def unsubscribe_to_resource(resource_type, resource_id):
@login_required
def enroll_request(resource_type, resource_id):
actor_id = session.get('actor_id') if session.has_key('actor_id') else None
- print 'zzzzz', actor_id, resource_id
resp = ServiceApi.enroll_request(resource_id, actor_id)
- return jsonify(data=actor_id)
- # return render_json_response(resp)
+ return render_json_response(resp)
+
+@app.route('/<resource_type>/status/<resource_id>/request_role/', methods=['POST'])
+@app.route('/<resource_type>/face/<resource_id>/request_role/', methods=['POST'])
+@app.route('/<resource_type>/related/<resource_id>/request_role/', methods=['POST'])
+@login_required
+def request_role(resource_type, resource_id):
+ actor_id = session.get('actor_id') if session.has_key('actor_id') else None
+ role_name = request.form.get('role_name', None)
+
+ resp = ServiceApi.request_role(resource_id, actor_id, role_name)
+ return render_json_response(resp)
+
+@app.route('/<resource_type>/status/<resource_id>/invite_user/', methods=['POST'])
+@app.route('/<resource_type>/face/<resource_id>/invite_user/', methods=['POST'])
+@app.route('/<resource_type>/related/<resource_id>/invite_user/', methods=['POST'])
+@login_required
+def invite_user(resource_type, resource_id):
+ user_id = request.form.get('user_id', None)
+
+ resp = ServiceApi.invite_user(resource_id, user_id)
+ return render_json_response(resp)
+
+@app.route('/<resource_type>/status/<resource_id>/offer_user_role/', methods=['POST'])
+@app.route('/<resource_type>/face/<resource_id>/offer_user_role/', methods=['POST'])
+@app.route('/<resource_type>/related/<resource_id>/offer_user_role/', methods=['POST'])
+@login_required
+def offer_user_role(resource_type, resource_id):
+ user_id = request.form.get('user_id', None)
+ role_name = request.form.get('role_name', None)
+
+ resp = ServiceApi.offer_user_role(resource_id, user_id, role_name)
+ return render_json_response(resp)
+
+@app.route('/<resource_type>/status/<resource_id>/request_access/', methods=['POST'])
+@app.route('/<resource_type>/face/<resource_id>/request_access/', methods=['POST'])
+@app.route('/<resource_type>/related/<resource_id>/request_access/', methods=['POST'])
+@login_required
+def request_access(resource_type, resource_id):
+ org_id = request.form.get('org_id', None)
+ actor_id = session.get('actor_id') if session.has_key('actor_id') else None
+ resp = ServiceApi.request_access(resource_id, actor_id, org_id)
+ return render_json_response(resp)
+
+@app.route('/<resource_type>/status/<resource_id>/release_access/', methods=['POST'])
+@app.route('/<resource_type>/face/<resource_id>/release_access/', methods=['POST'])
+@app.route('/<resource_type>/related/<resource_id>/release_access/', methods=['POST'])
+@login_required
+def release_access(resource_type, resource_id):
+ commitment_id = request.form.get('commitment_id', None)
+
+ resp = ServiceApi.release_access(commitment_id)
+ return render_json_response(resp)
+
+@app.route('/<resource_type>/status/<resource_id>/request_exclusive_access/', methods=['POST'])
+@app.route('/<resource_type>/face/<resource_id>/request_exclusive_access/', methods=['POST'])
+@app.route('/<resource_type>/related/<resource_id>/request_exclusive_access/', methods=['POST'])
+@login_required
+def request_exclusive_access(resource_type, resource_id):
+ expiration = int(request.form.get('expiration', None))
+ curtime = int(round(time.time() * 1000))
+ full_expiration = curtime + (expiration * 60 * 60 * 1000) # in ms
+ actor_id = session.get('actor_id') if session.has_key('actor_id') else None
+ org_id = request.form.get('org_id', None)
+
+ resp = ServiceApi.request_exclusive_access(resource_id, actor_id, org_id, full_expiration)
+ return render_json_response(resp)
+
+@app.route('/negotiation/accept/', methods=['POST'])
+@login_required
+def accept_negotiation():
+ negotiation_id = request.form.get('negotiation_id', None)
+ resp = ServiceApi.accept_negotiation(negotiation_id)
+ return render_json_response(resp)
+
+@app.route('/negotiation/reject/', methods=['POST'])
+@login_required
+def reject_negotiation():
+ negotiation_id = request.form.get('negotiation_id', None)
+ resp = ServiceApi.reject_negotiation(negotiation_id)
+ return render_json_response(resp)
@app.route('/<resource_type>/status/<resource_id>/transition/', methods=['POST'])
@app.route('/<resource_type>/face/<resource_id>/transition/', methods=['POST'])
@@ -480,7 +558,7 @@ def session_info():
session_values = {'user_id': None, 'roles': None, 'is_registered': False, 'is_logged_in': False, 'ui_mode': UI_MODE, 'version': version }
if session.has_key('user_id'):
- session_values.update({'name': session['name'], 'user_id': session['user_id'], 'roles': session['roles'], 'is_registered': session['is_registered'], 'is_logged_in': True})
+ session_values.update({'name': session['name'], 'user_id': session['user_id'], 'actor_id': session['actor_id'], 'roles': session['roles'], 'is_registered': session['is_registered'], 'is_logged_in': True})
return jsonify(data=session_values)
View
100 service_api.py
@@ -101,11 +101,105 @@ def delete_user_subscription(notification_id):
@staticmethod
def enroll_request(resource_id, actor_id):
- print 'zzzzz', resource_id, actor_id
- return True
- # return service_gateway_post('user_notification', 'delete_notification', params={'notification_id': notification_id})
+ sap = {'type_': 'EnrollmentProposal',
+ 'originator': 1,
+ 'consumer': actor_id,
+ 'provider': resource_id,
+ 'proposal_status': 1 }
+ return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
+ @staticmethod
+ def request_role(resource_id, actor_id, role_name):
+ sap = {'type_': 'RequestRoleProposal',
+ 'originator': 1,
+ 'consumer': actor_id,
+ 'provider': resource_id,
+ 'proposal_status': 1,
+ 'role_name': role_name }
+
+ return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
+
+ @staticmethod
+ def invite_user(resource_id, user_id):
+ # look up actor id from user id
+ actor_id = service_gateway_get('resource_registry', 'find_subjects', params={'predicate': 'hasInfo', 'object': user_id, 'id_only': True})[0]
+
+ sap = {'type_': 'EnrollmentProposal',
+ 'originator': 2,
+ 'consumer': actor_id,
+ 'provider': resource_id,
+ 'proposal_status': 1 }
+
+ return service_gateway_post('org_management', 'negotiate', params={'negotiation_type': 2,
+ 'sap':sap})
+
+ @staticmethod
+ def offer_user_role(resource_id, user_id, role_name):
+ # look up actor id from user id
+ actor_id = service_gateway_get('resource_registry', 'find_subjects', params={'predicate': 'hasInfo', 'object': user_id, 'id_only': True})[0]
+
+ sap = {'type_': 'RequestRoleProposal',
+ 'originator': 2,
+ 'consumer': actor_id,
+ 'provider': resource_id,
+ 'proposal_status': 1,
+ 'role_name': role_name }
+
+ return service_gateway_post('org_management', 'negotiate', params={'negotiation_type': 2,
+ 'sap':sap})
+
+ @staticmethod
+ def request_access(resource_id, actor_id, org_id):
+ sap = {'type_': 'AcquireResourceProposal',
+ 'originator': 1,
+ 'consumer': actor_id,
+ 'provider': org_id,
+ 'proposal_status': 1,
+ 'resource_id': resource_id }
+
+ return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
+ @staticmethod
+ def release_access(commitment_id):
+ return service_gateway_post('org_management', 'release_commitment', params={'commitment_id':commitment_id})
+
+ @staticmethod
+ def request_exclusive_access(resource_id, actor_id, org_id, expiration):
+ sap = {'type_': 'AcquireResourceExclusiveProposal',
+ 'originator': 1,
+ 'consumer': actor_id,
+ 'provider': org_id,
+ 'proposal_status': 1,
+ 'resource_id': resource_id,
+ 'expiration': expiration}
+
+ return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
+
+ @staticmethod
+ def _accept_reject_negotiation(negotiation_id, accept_value):
+ if not accept_value in [3,4]:
+ return error_message("Unknown accept_value %s" % accept_value)
+
+ # read the negotiation first so we can determine the sap to send
+ negotiation = service_gateway_get('resource_registry', 'read', params={'object_id': negotiation_id})
+
+ sap = {'type_': negotiation['proposals'][0]['type_'],
+ 'negotiation_id': negotiation_id,
+ 'sequence_num': int(negotiation['proposals'][0]['sequence_num']) + 1,
+ 'originator': 2,
+ 'proposal_status': accept_value,
+ 'consumer': negotiation['proposals'][0]['consumer'],
+ 'provider': negotiation['proposals'][0]['provider']}
+
+ return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
+
+ @staticmethod
+ def accept_negotiation(negotiation_id):
+ return ServiceApi._accept_reject_negotiation(negotiation_id, 3) # 3 == accepted
+
+ @staticmethod
+ def reject_negotiation(negotiation_id):
+ return ServiceApi._accept_reject_negotiation(negotiation_id, 4) # 4 == rejected
@staticmethod
def get_event_types():
View
2  static/js/ion-ux.js
@@ -37,7 +37,7 @@ IONUX = {
},
is_owner: function(){
var user_id = IONUX.SESSION_MODEL.get('user_id');
- var owner_match = _.findWhere(MODEL_DATA.owners[0], {_id: user_id}) ? true : false;
+ var owner_match = _.findWhere(MODEL_DATA.owners, {_id: user_id}) ? true : false;
return owner_match;
}
}
View
2  static/js/ux-models.js
@@ -64,7 +64,7 @@ IONUX.Models.Session = Backbone.Model.extend({
return this.get('is_registered');
},
is_resource_owner: function(){
- return _.findWhere(MODEL_DATA.owners[0], {_id: this.get('user_id')}) ? true : false;
+ return _.findWhere(MODEL_DATA.owners, {_id: this.get('user_id')}) ? true : false;
}
});
View
33 static/js/ux-router.js
@@ -194,6 +194,28 @@ function replace_url_with_html_links(text) {
return text.replace(exp,"<a class='external' target='_blank' href='$1'>$1</a>");
};
+// Filter method for open negotiations shown in a data table
+// If this method returns true, there should be a column indicating
+// the view will be shown on row click.
+function negotiation_show_controls(row_data) {
+ // row data first column should always be of form resource_id/resource_type
+ // use this info to go and look up the real item, because UI columns may change
+ var neg = _.findWhere(window.MODEL_DATA.open_negotiations, {_id:row_data[0].split("::")[0]});
+ if (neg &&
+ window.MODEL_DATA.resource_type == "Org" &&
+ neg.proposals[0].originator == 1 && // originator == user proposed (1)
+ _.contains(IONUX.SESSION_MODEL.get('roles')[window.MODEL_DATA.resource.org_governance_name], 'ORG_MANAGER'))
+ return true;
+
+ if (neg &&
+ window.MODEL_DATA.resource_type == "UserInfo" &&
+ neg.proposals[0].originator == 2 && // originator == org proposed (2)
+ neg.proposals[0].consumer == IONUX.SESSION_MODEL.get('actor_id')) // this is likely redundant
+ return true;
+
+ return false;
+};
+
// Returns a displayable resource type for the resource_type given.
// If the type is not displayable, traverse up the heirarchy of resources
// until one is found.
@@ -287,7 +309,13 @@ function render_page(resource_type, resource_id, model) {
var data_path = $(el).data('path');
var raw_table_data = get_descendant_properties(window.MODEL_DATA, data_path);
if (!_.isEmpty(raw_table_data)) {
- var table = new IONUX.Views.DataTable({el: $(el), data: raw_table_data});
+ var opts = {el: $(el), data: raw_table_data}
+ if (data_path == "open_negotiations") {
+ _.extend(opts, {popup_view: IONUX.Views.NegotiationCommands,
+ popup_label: "Accept/Reject",
+ popup_filter_method: negotiation_show_controls});
+ }
+ var table = new IONUX.Views.DataTable(opts);
} else {
var table = new IONUX.Views.DataTable({el: $(el), data: []});
};
@@ -359,9 +387,6 @@ function render_page(resource_type, resource_id, model) {
case 'Recent Events':
new IONUX.Views.EventActions({el:$(el)});
break;
- case 'Participants':
- new IONUX.Views.NegotiationActions({el: $(el)});
- break;
default:
new IONUX.Views.GroupActions({el:$(el)});
};
View
7 static/js/ux-templates.js
@@ -3,4 +3,9 @@
IONUX.Templates = {
modal_template: '<div id="action-modal" class="modal modal-ooi"></div>',
-}
+ full_modal_template: '<div id="action-modal" class="modal modal-ooi">' +
+ '<div class="modal-header"><h1><%= header_text %></h1></div>' +
+ '<div class="modal-body"><%= body %></div>' +
+ '<div class="modal-footer"><%= buttons %></div>' +
+ '</div>',
+}
View
87 static/js/ux-views-actionmenu.js
@@ -64,7 +64,64 @@ IONUX.Views.ViewActions = IONUX.Views.ActionMenu.extend({
modal_template: '<div id="action-modal" class="modal hide fade modal-ooi">',
initialize: function() {
_.bindAll(this);
- this.interaction_items = INTERACTIONS_OBJECT.view_interactions;
+ this.interaction_items = INTERACTIONS_OBJECT.view_interactions.slice(0); // ensure clone
+
+ // append resource-specific items here
+ if (window.MODEL_DATA.resource_type == 'Org') {
+
+ // ENROLLMENT
+ if (IONUX.is_logged_in()) {
+ if (IONUX.is_owner()) {
+ // INVITE USER
+ this.interaction_items.push("Invite User");
+ this.on("action__invite_user", this.action_org__invite_user);
+
+ // INVITE ROLE
+ this.interaction_items.push("Offer User Role");
+ this.on("action__offer_user_role", this.action_org__offer_user_role);
+
+ } else {
+ if (!_.some(window.MODEL_DATA.members, function(x) { return x._id == IONUX.SESSION_MODEL.get("user_id") })) {
+ // REQUEST ENROLLMENT
+ this.interaction_items.push("Enroll");
+ this.on("action__enroll", this.action_org__enroll);
+ } else {
+ // REQUEST ROLE
+ this.interaction_items.push("Request Role");
+ this.on("action__request_role", this.action_org__request_role);
+ }
+ }
+ }
+ } else if (_.contains(['PlatformDevice', 'InstrumentDevice', 'DataProduct'], window.MODEL_DATA.resource_type)) {
+ if (IONUX.is_logged_in() && !IONUX.is_owner()) {
+ // check commitments on current object for resource commitments for the current owner
+ var resource_commitments = _.filter(window.MODEL_DATA.commitments, function (c) { return c.commitment.type_ == "ResourceCommitment" && c.consumer == IONUX.SESSION_MODEL.get('actor_id'); });
+
+ if (resource_commitments.length > 0) {
+ // we have access to this instrument, add item to release it
+ this.interaction_items.push("Release Resource");
+ // @TODO: assumption, only one commitment?
+ this.on("action__release_resource", _.partial(this.action__release_resource, resource_commitments[0]._id));
+
+ // exclusive access?
+ var exc = _.find(resource_commitments, function(c) { return c.commitment.exclusive; });
+ if (exc == null) {
+ this.interaction_items.push("Request Exclusive Access");
+ // @TODO: assumption, only one commitment?
+ this.on("action__request_exclusive_access", _.partial(this.action__request_exclusive_access, resource_commitments[0].provider));
+
+ } else {
+ this.interaction_items.push("Release Exclusive Access");
+ this.on("action__release_exclusive_access", _.partial(this.action__release_exclusive_access, exc._id));
+ }
+
+ } else {
+ this.interaction_items.push("Request Access");
+ this.on("action__request_access", this.action__request_access);
+ }
+ }
+ }
+
this.create_actionmenu();
this.on("action__subscribe", this.action__subscribe);
this.on("action__lifecycle", this.action__lifecycle);
@@ -143,6 +200,34 @@ IONUX.Views.ViewActions = IONUX.Views.ActionMenu.extend({
alert("Download not available for this resource.");
};
},
+ action_org__invite_user: function(e) {
+ var model = new IONUX.Collections.Resources(null, {resource_type: 'UserInfo'});
+ model.fetch()
+ .done(function(data) {
+ new IONUX.Views.InviteUser({model: model}).render().el;
+ });
+ },
+ action_org__offer_user_role: function(e) {
+ new IONUX.Views.OfferUserRole().render().el;
+ },
+ action_org__enroll: function(e) {
+ new IONUX.Views.Enroll().render().el;
+ },
+ action_org__request_role: function(e) {
+ new IONUX.Views.RequestRole().render().el;
+ },
+ action__request_access: function(e) {
+ new IONUX.Views.RequestAccess().render().el;
+ },
+ action__release_resource: function(commitment_id, e) {
+ new IONUX.Views.ReleaseAccess({commitment_id: commitment_id}).render().el;
+ },
+ action__request_exclusive_access: function(org_id, e) {
+ new IONUX.Views.RequestExclusiveAccess({org_id: org_id}).render().el;
+ },
+ action__release_exclusive_access: function(commitment_id, e) {
+ new IONUX.Views.ReleaseExclusiveAccess({commitment_id: commitment_id}).render().el;
+ },
});
View
28 static/js/ux-views-datatable.js
@@ -45,7 +45,7 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
"click .show-hide-filters":"show_hide_filters",
"click .filters-apply":"filters_apply",
"click .filters-reset":"filters_reset",
- "click table tbody tr":"table_row_click"
+ "click table tbody tr":"table_row_click",
},
template: _.template($('#datatable-tmpl').html()),
@@ -61,6 +61,7 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
this.$el.html(this.template());
var header_data = this.header_data();
var table_data = this.table_data(this.options.data);
+ var self = this;
this.datatable = this.$el.find(".datatable-container table").dataTable({
"sDom":"Rlfrtip",
"aaData":table_data,
@@ -76,9 +77,10 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
$('td', nRow).each(function() {
$(this).attr('title', $(this).text());
});
- }
+ },
});
if (this.options.data.length == 0){this.$el.find(".dataTables_scrollBody").css("overflow", "hidden")};
+
return this;
},
@@ -102,6 +104,12 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
data_item["sClass"] = "center"; //TODO choose dependant on 'item[0]'
data.push(data_item);
});
+
+ // blank column for popup column
+ if (this.options.hasOwnProperty('popup_view')) {
+ data.push({sTitle: "", sType: "title", sClass: "center"});
+ }
+
return data;
},
@@ -128,6 +136,16 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
data_row.push(value);
});
+ // insert false column for popup
+ if (self.options.hasOwnProperty('popup_view')) {
+ if (self.options.popup_filter_method(data_row)) {
+ var label = self.options.popup_label || "Options";
+ data_row.push(label);
+ } else {
+ data_row.push('');
+ }
+ }
+
// Temp fix to block associations from appearing in search results.
// Need to sub-class this view eventually.
if (resource_type != 'Association') data.push(data_row);
@@ -333,7 +351,11 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
var resource_type = row_info_list[1];
if (resource_type.match(/Event$/)) return false;
-
+
+ if (this.options.hasOwnProperty('popup_view') && this.options.popup_filter_method(table_row_data)) {
+ new this.options.popup_view({data:table_row_data, datatable:this.datatable}).render().el;
+ return;
+ }
// Check for type_ attachment and pop modal to let user
// choose between downloading an attachment or viewing
// its metadata on a face page.
View
443 static/js/ux-views-negotiations.js
@@ -9,7 +9,7 @@ IONUX.Views.Enroll = Backbone.View.extend({
},
render: function(){
var resource_name = window.MODEL_DATA.resource.name;
- $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
+ this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
.on('hide', function(){
$('#action-modal').remove();
});
@@ -17,14 +17,449 @@ IONUX.Views.Enroll = Backbone.View.extend({
return this;
},
request_enrollment: function(e){
+ var self = this;
e.preventDefault();
$.ajax({
type: 'POST',
url: window.location.href + 'enroll/',
- data: 'Word',
+ data: {},
success: function(resp) {
-
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
+ body: 'Your request to enroll has been received and will be reviewed by a manager.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
}
})
},
-});
+});
+
+IONUX.Views.RequestRole = Backbone.View.extend({
+ el: '#action-modal',
+ template: _.template($('#request-role-tmpl').html()),
+ events: {
+ 'click #btn-request': 'request_role'
+ },
+ initialize: function(){
+ _.bindAll(this);
+ },
+ render: function(){
+ var resource_name = window.MODEL_DATA.resource.name;
+ this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
+ .on('hide', function(){
+ $('#action-modal').remove();
+ });
+ this.setElement('#action-modal');
+ return this;
+ },
+ request_role: function(e){
+ var self = this;
+ var role_name = self.$('select[name="role_name"]').val();
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.href + 'request_role/',
+ data: {role_name: role_name},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
+ body: 'Your request has been received and will be reviewed by a manager.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+
+});
+
+IONUX.Views.InviteUser = Backbone.View.extend({
+ el: '#action-modal',
+ template: _.template($('#invite-user-tmpl').html()),
+ events: {
+ 'click #btn-invite': 'invite_user'
+ },
+ initialize: function(){
+ _.bindAll(this);
+ },
+ render: function(){
+ var resource_name = window.MODEL_DATA.resource.name;
+ this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
+ .on('hide', function(){
+ $('#action-modal').remove();
+ });
+ this.setElement('#action-modal');
+ return this;
+ },
+ invite_user: function(e){
+ var self = this;
+ var user = self.$('select[name="user"]').val();
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.href + 'invite_user/',
+ data: {user_id: user},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Invite Received',
+ body: 'Your invite has been received and will be reviewed by the user.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+});
+
+IONUX.Views.OfferUserRole = Backbone.View.extend({
+ el: '#action-modal',
+ template: _.template($('#offer-user-role-tmpl').html()),
+ events: {
+ 'click #btn-offer': 'offer_user_role',
+ 'change select[name="user"]': 'user_changed'
+ },
+ initialize: function(){
+ _.bindAll(this);
+ this.user_cache = {}
+ },
+ render: function(){
+ var resource_name = window.MODEL_DATA.resource.name;
+ this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
+ .on('hide', function(){
+ $('#action-modal').remove();
+ });
+ this.setElement('#action-modal');
+ return this;
+ },
+ user_changed: function(e) {
+ var user_id = this.$('select[name="user"]').val();
+ var self = this;
+
+ if (user_id == null) {
+ e.preventDefault();
+ return;
+ }
+
+ if (this.user_cache.hasOwnProperty(user_id)) {
+ var user_roles = this.user_cache[user_id];
+ this.update_displayed_roles(user_roles);
+ } else {
+
+ self.$('#rolespinner').show();
+ self.$('select[name="role_name"]').empty();
+
+ $.ajax({
+ url: window.location.protocol + "//" + window.location.host + "/UserInfo/extension/" + user_id + "/",
+ success: function(resp) {
+ self.$('#rolespinner').hide();
+
+ var user_roles = _.pluck(_.where(resp.data.roles, { org_governance_name: window.MODEL_DATA.resource.org_governance_name }), 'governance_name');
+ self.user_cache[user_id] = user_roles;
+
+ self.update_displayed_roles(user_roles);
+ }
+ });
+ }
+ },
+ update_displayed_roles: function(user_roles) {
+ var role = this.$('select[name="role_name"]');
+
+ var new_roles = _.filter(window.MODEL_DATA.roles, function(r) { return !_.contains(user_roles, r.governance_name); });
+
+ role.empty();
+ _.each(new_roles, function(r) {
+ role.append("<option value='" + r.governance_name + "'>" + r.name + "</option>");
+ });
+ },
+ offer_user_role: function(e){
+ var self = this;
+ var user = self.$('select[name="user"]').val();
+ var role_name = self.$('select[name="role_name"]').val();
+
+ if (user == null || role_name == null)
+ return;
+
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.href + 'offer_user_role/',
+ data: {user_id: user,
+ role_name: role_name},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Role Offer Received',
+ body: 'Your role offer has been received and will be reviewed by the user.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+});
+
+IONUX.Views.RequestAccess = Backbone.View.extend({
+ events: {
+ 'click #btn-request': 'request_access'
+ },
+ initialize: function(){
+ _.bindAll(this);
+ },
+ render: function(){
+ // devices etc have a list of orgs they are a part of
+ // the user has a list of orgs/roles they have
+ // need to find where they intersect (as INSTRUMENT_OPERATOR)
+ var org_id = null;
+ var roles = IONUX.SESSION_MODEL.get('roles');
+
+ // @TODO: there's got to be a better way than this
+ var user_orgs = _.map(_.filter(_.pairs(roles), function(r) { return _.contains(r[1], "INSTRUMENT_OPERATOR") } ), function(v) { return v[0] })
+
+ this.matching_orgs = _.filter(window.MODEL_DATA.orgs, function(o) { return _.contains(user_orgs, o.org_governance_name); });
+
+ var resource_name = window.MODEL_DATA.resource.name;
+
+ var cancel_button = "<button class='btn-general' data-dismiss='modal'>Cancel</button>";
+ var template_vars = {header_text: 'Request Access: ' + resource_name,
+ body: 'Press the Request Access button below to request access to this resource. All requests must be approved by a manager.',
+ buttons: "<button class='btn-blue' id='btn-request'>Request Access</button>" + cancel_button}
+
+ // sub out message/buttons if we don't have perms
+ if (this.matching_orgs.length == 0) {
+ template_vars.body = "You must have the instrument operator role in the associated org to request access to this device.";
+ template_vars.buttons = cancel_button;
+ }
+
+ this.modal = $(_.template(IONUX.Templates.full_modal_template, template_vars));
+ this.modal.modal('show')
+ .on('hide', function() {
+ $('#action-modal').remove();
+ });
+ this.setElement('#action-modal');
+ return this;
+ },
+ request_access: function(e){
+ if (this.matching_orgs.length == 0) {
+ alert("Could not find an Org that you are an instrument operator of and this device belongs to!");
+ return;
+ } else if (this.matching_orgs.length > 1) {
+ console.warn("More than one matching Org found (" + this.matching_orgs.length + "), using first: " + this.matching_orgs[0]);
+ }
+
+ org_id = this.matching_orgs[0]._id;
+
+ var self = this;
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.href + 'request_access/',
+ data: {org_id: org_id},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
+ body: 'Your request has been received and will be reviewed by a manager.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+});
+
+IONUX.Views.ReleaseAccess = Backbone.View.extend({
+ events: {
+ 'click #btn-release': 'release_access'
+ },
+ initialize: function(){
+ _.bindAll(this);
+ },
+ template_vars: {
+ header_text: 'Release Access',
+ body: 'Press the Release Access button below to release access to this resource.',
+ buttons: "<button class='btn-blue' id='btn-release'>Release Access</button><button class='btn-general' data-dismiss='modal'>Cancel</button>",
+ },
+ render: function(){
+ this.modal = $(_.template(IONUX.Templates.full_modal_template, this.template_vars));
+ this.modal.modal('show')
+ .on('hide', function() {
+ $('#action-modal').remove();
+ });
+ this.setElement('#action-modal');
+ return this;
+ },
+ release_access: function(e){
+ var self = this;
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.href + 'release_access/',
+ data: {commitment_id: this.options.commitment_id},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Release Received',
+ body: 'Your release has been received.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+});
+
+IONUX.Views.RequestExclusiveAccess = Backbone.View.extend({
+ template: _.template($("#request-exclusive-access-tmpl").html()),
+ events: {
+ 'click #btn-request': 'request_access'
+ },
+ initialize: function(){
+ _.bindAll(this);
+ },
+ render: function(){
+
+ // check to see if someone ELSE has exclusive access so we can warn user it will fail
+ var exc = _.find(window.MODEL_DATA.commitments, function(c) { return c.commitment.exclusive && c.consumer != IONUX.SESSION_MODEL.get('actor_id'); });
+ if (exc != null) {
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Exclusive Access',
+ body: 'Another user already has exclusive access to this device.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ });
+
+ return;
+ }
+
+ var resource_name = window.MODEL_DATA.resource.name;
+
+ this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name}));
+ this.modal.modal('show')
+ .on('hide', function() {
+ $('#action-modal').remove();
+ });
+ this.setElement('#action-modal');
+ return this;
+ },
+ request_access: function(e){
+ var expiration = parseInt(this.$('input[name="time"]').val());
+ if (expiration <= 0 || expiration > 12) {
+ this.$('.control-group').addClass('error');
+ this.$('.help-inline').append("Please enter a value between 0 and 12");
+ return;
+ } else {
+ this.$('.control-group').removeClass('error');
+ this.$('.help-inline').empty();
+ }
+
+ var self = this;
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.href + 'request_exclusive_access/',
+ data: {expiration: expiration,
+ org_id: this.options.org_id},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
+ body: 'Your request has been received and will be reviewed by a manager.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+});
+
+IONUX.Views.ReleaseExclusiveAccess = IONUX.Views.ReleaseAccess.extend({
+ template_vars: {
+ header_text: 'Release Exclusive Access',
+ body: 'Press the Release Exclusive Access button below to release access to this resource.',
+ buttons: "<button class='btn-blue' id='btn-release'>Release Access</button><button class='btn-general' data-dismiss='modal'>Cancel</button>",
+ },
+});
+
+IONUX.Views.NegotiationCommands = Backbone.View.extend({
+ template: _.template($("#negotiation-commands-tmpl").html()),
+ events: {
+ 'click #btn-accept': 'accept',
+ 'click #btn-reject': 'reject',
+ },
+ initialize: function(){
+ _.bindAll(this);
+ var row_info_list = this.options.data[0].split("::");
+ this.resource_id = row_info_list[0];
+ this.resource_type = row_info_list[1];
+ },
+ render: function(){
+ this.modal = $(IONUX.Templates.modal_template).html(this.template({negotiation_id: this.resource_id,
+ submitted: this.options.data[1]}));
+ this.modal.modal('show')
+ .on('hide', function() {
+ $('#action-modal').remove();
+ });
+ this.setElement('#action-modal');
+ return this;
+ },
+ accept: function(e){
+ var self = this;
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.protocol + "//" + window.location.host + "/negotiation/accept/",
+ data: {negotiation_id: this.resource_id},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Accepted',
+ body: 'You have accepted this negotiation request.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+ reject: function(e){
+ var self = this;
+ e.preventDefault();
+ $.ajax({
+ type: 'POST',
+ url: window.location.protocol + "//" + window.location.host + "/negotiation/reject/",
+ data: {negotiation_id: this.resource_id},
+ success: function(resp) {
+ self.modal.modal('hide');
+ $(_.template(IONUX.Templates.full_modal_template, {header_text:'Rejected',
+ body: 'You have rejected this negotiation request.',
+ buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
+ .on('hide', function() {
+ $('#action-modal').remove();
+ Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
+ IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
+ });
+ }
+ })
+ },
+});
+
+
View
5 templates/ion_ux.html
@@ -76,6 +76,11 @@
<script type="text/template" id="extent-vertical-tmpl"><![CDATA[{% include 'partials/extent_vertical.html' %}]]></script>
<script type="text/template" id="extent-temporal-tmpl"><![CDATA[{% include 'partials/extent_temporal.html' %}]]></script>
<script type="text/template" id="enroll-request-tmpl"><![CDATA[{% include 'partials/enroll_request.html' %}]]></script>
+ <script type="text/template" id="request-role-tmpl"><![CDATA[{% include 'partials/request_role.html' %}]]></script>
+ <script type="text/template" id="invite-user-tmpl"><![CDATA[{% include 'partials/invite_user.html' %}]]></script>
+ <script type="text/template" id="offer-user-role-tmpl"><![CDATA[{% include 'partials/offer_user_role.html' %}]]></script>
+ <script type="text/template" id="request-exclusive-access-tmpl"><![CDATA[{% include 'partials/request_exclusive_access.html' %}]]></script>
+ <script type="text/template" id="negotiation-commands-tmpl"><![CDATA[{% include 'partials/negotiation_commands.html' %}]]></script>
<script type="text/template" id="error-tmpl"><![CDATA[{% include 'partials/error.html' %}]]></script>
{% block extra_templates %} {% endblock %}
View
11 templates/partials/enroll_request.html
@@ -1,11 +1,14 @@
<div class="modal-header">
<h1>Enroll Request: <%= resource_name %></h1>
</div>
-<div class="modal-body error">
- <div id="request">
- <button id="btn-request" class="btn-blue">Request Enrollment</button>
- </div>
+<div class="modal-body">
+ <p>
+ Press the Request Enrollment button below to initiate your request to enroll in
+ this observatory. A manager must approve your request before you are granted
+ access.
+ </p>
</div>
<div class="modal-footer">
+ <button id="btn-request" class="btn-blue">Request Enrollment</button>
<button class="btn-general" data-dismiss="modal">Cancel</button>
</div>
View
27 templates/partials/invite_user.html
@@ -0,0 +1,27 @@
+<div class="modal-header">
+ <h1>Invite User: <%= resource_name %></h1>
+</div>
+<div class="modal-body">
+ <p>
+ Select a user to invite to become a member. The user will need to accept the invitation
+ in order for it to be approved.
+ </p>
+ <form class="form-horizontal">
+ <% var curmembers = _.pluck(window.MODEL_DATA.members, '_id'); %>
+ <div class="control-group">
+ <label class="control-label" for="user">User</label>
+ <div class="controls">
+ <select name="user" size="6">
+ <% _.each(_.filter(this.model.toJSON(), function(d) { return !_.contains(curmembers, d._id); }), function (user) { %>
+ <option value="<%= user._id %>"><%= user.name %></option>
+ <% }); %>
+ </select>
+ </div>
+ </div>
+ </form>
+</div>
+<div class="modal-footer">
+ <button id="btn-invite" class="btn-blue">Invite User</button>
+ <button class="btn-general" data-dismiss="modal">Cancel</button>
+</div>
+
View
18 templates/partials/negotiation_commands.html
@@ -0,0 +1,18 @@
+<div class="modal-header">
+ <h1>Accept/Reject Negotiation: <%= negotiation_id %></h1>
+</div>
+<div class="modal-body">
+ <p>
+ Please accept or reject this access/role proposal.
+ </p>
+
+ <dl>
+ <dt>Request Submitted</dt>
+ <dd><%= submitted %></dd>
+ </dl>
+</div>
+<div class="modal-footer">
+ <button id="btn-accept" class="btn-blue">Accept</button>
+ <button id="btn-reject" class="btn-blue">Reject</button>
+ <button class="btn-general" data-dismiss="modal">Cancel</button>
+</div>
View
41 templates/partials/offer_user_role.html
@@ -0,0 +1,41 @@
+<div class="modal-header">
+ <h1>Offer User Role: <%= resource_name %></h1>
+</div>
+<div class="modal-body">
+ <p>
+ Select a member to offer a role to, and the role to offer. The user will need to
+ accept the offer in order for it to be approved.
+ </p>
+ <form class="form-horizontal">
+ <div class="control-group">
+ <label class="control-label" for="user">User</label>
+ <div class="controls">
+ <select name="user" size="6">
+ <% _.each(window.MODEL_DATA.members, function (user) { %>
+ <option value="<%= user._id %>"><%= user.name %></option>
+ <% }); %>
+ </select>
+ </div>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label" for="role_name">
+ <img style="display: none" src="/static/img/busy_indicator.gif" alt="loading" id="rolespinner" />
+ Role
+ </label>
+ <div class="controls">
+ <select name="role_name" size="6">
+ <% _.each(window.MODEL_DATA.roles, function (role) { %>
+ <option value="<%= role.governance_name %>"><%= role.name %></option>
+ <% }); %>
+ </select>
+ </div>
+ </div>
+
+ </form>
+</div>
+<div class="modal-footer">
+ <button id="btn-offer" class="btn-blue">Offer Role</button>
+ <button class="btn-general" data-dismiss="modal">Cancel</button>
+</div>
+
View
22 templates/partials/request_exclusive_access.html
@@ -0,0 +1,22 @@
+<div class="modal-header">
+ <h1>Request Exclusive Access: <%= resource_name %></h1>
+</div>
+<div class="modal-body">
+ <p>
+ Select the amount of time (up to 12 hours) you would like exclusive access to this resource.
+ </p>
+ <form class="form-horizontal">
+ <div class="control-group">
+ <label class="control-label" for="time">Reserved Time</label>
+ <div class="controls">
+ <input type="text" name="time" value="12" />
+ <span class="help-inline"></span>
+ </div>
+ </div>
+ </form>
+</div>
+<div class="modal-footer">
+ <button id="btn-request" class="btn-blue">Request Exclusive Access</button>
+ <button class="btn-general" data-dismiss="modal">Cancel</button>
+</div>
+
View
37 templates/partials/request_role.html
@@ -0,0 +1,37 @@
+<div class="modal-header">
+ <h1>Request Role: <%= resource_name %></h1>
+</div>
+<div class="modal-body">
+ <p>
+ Choose the role you would like to request, then press the Request Role button below. Your
+ request will be reviewed by a manager.
+ </p>
+ <form class="form-horizontal">
+ <% var curroles = IONUX.SESSION_MODEL.get('roles')[window.MODEL_DATA.resource.org_governance_name]; %>
+ <div class="control-group">
+ <label class="control-label" for="role_name">Role</label>
+ <div class="controls">
+ <select name="role_name" size="6">
+ <% _.each(_.filter(window.MODEL_DATA.roles, function(d) { return !_.contains(curroles, d.governance_name); }), function(role) { %>
+ <option value="<%= role.governance_name %>"><%= role.name %></option>
+ <% }); %>
+ </select>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="have_roles">Your Roles</label>
+ <div class="controls">
+ <select name="have_roles" size="5" disabled=true>
+ <% _.each(IONUX.SESSION_MODEL.get('roles')[window.MODEL_DATA.resource.org_governance_name], function(role) { %>
+ <option><%= _.findWhere(window.MODEL_DATA.roles, {governance_name:role}).name %></option>
+ <% }); %>
+ </select>
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
+<div class="modal-footer">
+ <button id="btn-request" class="btn-blue">Request Role</button>
+ <button class="btn-general" data-dismiss="modal">Cancel</button>
+</div>
+
Something went wrong with that request. Please try again.