Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Adds support for negotations #10

Merged
merged 11 commits into from over 1 year ago

2 participants

Dave Foster tboteler-ooi
Dave Foster
Owner
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
Dave Foster daf Typo 1c86ac8
Dave Foster daf Request enroll for user working, framework set for others 3e18210
Dave Foster daf Role request for orgs e7c36cc
Dave Foster daf Invite user negotiations for org manager ab16b99
Dave Foster daf Offer user role for orgs 8c87e82
Dave Foster daf Refresh view after any negotiation completion c6c7c3e
Dave Foster daf Fix for request_role template, using wrong attribute b759b0b
Dave Foster daf Add actor_id to session model e31df82
Dave Foster daf Request/Release and Exclusive access to devices c63b1a4
Dave Foster 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
Dave Foster daf Allow users to accept negotiations too 0e41227
tboteler-ooi tboteler-ooi merged commit c422203 into from
Dave Foster daf deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 11 unique commits by 1 author.

Mar 27, 2013
Dave Foster daf Typo 1c86ac8
Dave Foster daf Request enroll for user working, framework set for others 3e18210
Dave Foster daf Role request for orgs e7c36cc
Dave Foster daf Invite user negotiations for org manager ab16b99
Dave Foster daf Offer user role for orgs 8c87e82
Dave Foster daf Refresh view after any negotiation completion c6c7c3e
Mar 28, 2013
Dave Foster daf Fix for request_role template, using wrong attribute b759b0b
Dave Foster daf Add actor_id to session model e31df82
Dave Foster daf Request/Release and Exclusive access to devices c63b1a4
Apr 01, 2013
Dave Foster 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
Dave Foster daf Allow users to accept negotiations too 0e41227
This page is out of date. Refresh to see the latest.
86 main.py
@@ -145,11 +145,89 @@ def unsubscribe_to_resource(resource_type, resource_id):
145 145 @login_required
146 146 def enroll_request(resource_type, resource_id):
147 147 actor_id = session.get('actor_id') if session.has_key('actor_id') else None
148   - print 'zzzzz', actor_id, resource_id
149 148 resp = ServiceApi.enroll_request(resource_id, actor_id)
150   - return jsonify(data=actor_id)
151   - # return render_json_response(resp)
  149 + return render_json_response(resp)
  150 +
  151 +@app.route('/<resource_type>/status/<resource_id>/request_role/', methods=['POST'])
  152 +@app.route('/<resource_type>/face/<resource_id>/request_role/', methods=['POST'])
  153 +@app.route('/<resource_type>/related/<resource_id>/request_role/', methods=['POST'])
  154 +@login_required
  155 +def request_role(resource_type, resource_id):
  156 + actor_id = session.get('actor_id') if session.has_key('actor_id') else None
  157 + role_name = request.form.get('role_name', None)
  158 +
  159 + resp = ServiceApi.request_role(resource_id, actor_id, role_name)
  160 + return render_json_response(resp)
  161 +
  162 +@app.route('/<resource_type>/status/<resource_id>/invite_user/', methods=['POST'])
  163 +@app.route('/<resource_type>/face/<resource_id>/invite_user/', methods=['POST'])
  164 +@app.route('/<resource_type>/related/<resource_id>/invite_user/', methods=['POST'])
  165 +@login_required
  166 +def invite_user(resource_type, resource_id):
  167 + user_id = request.form.get('user_id', None)
  168 +
  169 + resp = ServiceApi.invite_user(resource_id, user_id)
  170 + return render_json_response(resp)
  171 +
  172 +@app.route('/<resource_type>/status/<resource_id>/offer_user_role/', methods=['POST'])
  173 +@app.route('/<resource_type>/face/<resource_id>/offer_user_role/', methods=['POST'])
  174 +@app.route('/<resource_type>/related/<resource_id>/offer_user_role/', methods=['POST'])
  175 +@login_required
  176 +def offer_user_role(resource_type, resource_id):
  177 + user_id = request.form.get('user_id', None)
  178 + role_name = request.form.get('role_name', None)
  179 +
  180 + resp = ServiceApi.offer_user_role(resource_id, user_id, role_name)
  181 + return render_json_response(resp)
  182 +
  183 +@app.route('/<resource_type>/status/<resource_id>/request_access/', methods=['POST'])
  184 +@app.route('/<resource_type>/face/<resource_id>/request_access/', methods=['POST'])
  185 +@app.route('/<resource_type>/related/<resource_id>/request_access/', methods=['POST'])
  186 +@login_required
  187 +def request_access(resource_type, resource_id):
  188 + org_id = request.form.get('org_id', None)
  189 + actor_id = session.get('actor_id') if session.has_key('actor_id') else None
152 190
  191 + resp = ServiceApi.request_access(resource_id, actor_id, org_id)
  192 + return render_json_response(resp)
  193 +
  194 +@app.route('/<resource_type>/status/<resource_id>/release_access/', methods=['POST'])
  195 +@app.route('/<resource_type>/face/<resource_id>/release_access/', methods=['POST'])
  196 +@app.route('/<resource_type>/related/<resource_id>/release_access/', methods=['POST'])
  197 +@login_required
  198 +def release_access(resource_type, resource_id):
  199 + commitment_id = request.form.get('commitment_id', None)
  200 +
  201 + resp = ServiceApi.release_access(commitment_id)
  202 + return render_json_response(resp)
  203 +
  204 +@app.route('/<resource_type>/status/<resource_id>/request_exclusive_access/', methods=['POST'])
  205 +@app.route('/<resource_type>/face/<resource_id>/request_exclusive_access/', methods=['POST'])
  206 +@app.route('/<resource_type>/related/<resource_id>/request_exclusive_access/', methods=['POST'])
  207 +@login_required
  208 +def request_exclusive_access(resource_type, resource_id):
  209 + expiration = int(request.form.get('expiration', None))
  210 + curtime = int(round(time.time() * 1000))
  211 + full_expiration = curtime + (expiration * 60 * 60 * 1000) # in ms
  212 + actor_id = session.get('actor_id') if session.has_key('actor_id') else None
  213 + org_id = request.form.get('org_id', None)
  214 +
  215 + resp = ServiceApi.request_exclusive_access(resource_id, actor_id, org_id, full_expiration)
  216 + return render_json_response(resp)
  217 +
  218 +@app.route('/negotiation/accept/', methods=['POST'])
  219 +@login_required
  220 +def accept_negotiation():
  221 + negotiation_id = request.form.get('negotiation_id', None)
  222 + resp = ServiceApi.accept_negotiation(negotiation_id)
  223 + return render_json_response(resp)
  224 +
  225 +@app.route('/negotiation/reject/', methods=['POST'])
  226 +@login_required
  227 +def reject_negotiation():
  228 + negotiation_id = request.form.get('negotiation_id', None)
  229 + resp = ServiceApi.reject_negotiation(negotiation_id)
  230 + return render_json_response(resp)
153 231
154 232 @app.route('/<resource_type>/status/<resource_id>/transition/', methods=['POST'])
155 233 @app.route('/<resource_type>/face/<resource_id>/transition/', methods=['POST'])
@@ -480,7 +558,7 @@ def session_info():
480 558 session_values = {'user_id': None, 'roles': None, 'is_registered': False, 'is_logged_in': False, 'ui_mode': UI_MODE, 'version': version }
481 559
482 560 if session.has_key('user_id'):
483   - session_values.update({'name': session['name'], 'user_id': session['user_id'], 'roles': session['roles'], 'is_registered': session['is_registered'], 'is_logged_in': True})
  561 + 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})
484 562 return jsonify(data=session_values)
485 563
486 564
100 service_api.py
@@ -101,11 +101,105 @@ def delete_user_subscription(notification_id):
101 101
102 102 @staticmethod
103 103 def enroll_request(resource_id, actor_id):
104   - print 'zzzzz', resource_id, actor_id
105   - return True
106   - # return service_gateway_post('user_notification', 'delete_notification', params={'notification_id': notification_id})
  104 + sap = {'type_': 'EnrollmentProposal',
  105 + 'originator': 1,
  106 + 'consumer': actor_id,
  107 + 'provider': resource_id,
  108 + 'proposal_status': 1 }
  109 + return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
107 110
  111 + @staticmethod
  112 + def request_role(resource_id, actor_id, role_name):
  113 + sap = {'type_': 'RequestRoleProposal',
  114 + 'originator': 1,
  115 + 'consumer': actor_id,
  116 + 'provider': resource_id,
  117 + 'proposal_status': 1,
  118 + 'role_name': role_name }
  119 +
  120 + return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
  121 +
  122 + @staticmethod
  123 + def invite_user(resource_id, user_id):
  124 + # look up actor id from user id
  125 + actor_id = service_gateway_get('resource_registry', 'find_subjects', params={'predicate': 'hasInfo', 'object': user_id, 'id_only': True})[0]
  126 +
  127 + sap = {'type_': 'EnrollmentProposal',
  128 + 'originator': 2,
  129 + 'consumer': actor_id,
  130 + 'provider': resource_id,
  131 + 'proposal_status': 1 }
  132 +
  133 + return service_gateway_post('org_management', 'negotiate', params={'negotiation_type': 2,
  134 + 'sap':sap})
  135 +
  136 + @staticmethod
  137 + def offer_user_role(resource_id, user_id, role_name):
  138 + # look up actor id from user id
  139 + actor_id = service_gateway_get('resource_registry', 'find_subjects', params={'predicate': 'hasInfo', 'object': user_id, 'id_only': True})[0]
  140 +
  141 + sap = {'type_': 'RequestRoleProposal',
  142 + 'originator': 2,
  143 + 'consumer': actor_id,
  144 + 'provider': resource_id,
  145 + 'proposal_status': 1,
  146 + 'role_name': role_name }
  147 +
  148 + return service_gateway_post('org_management', 'negotiate', params={'negotiation_type': 2,
  149 + 'sap':sap})
  150 +
  151 + @staticmethod
  152 + def request_access(resource_id, actor_id, org_id):
  153 + sap = {'type_': 'AcquireResourceProposal',
  154 + 'originator': 1,
  155 + 'consumer': actor_id,
  156 + 'provider': org_id,
  157 + 'proposal_status': 1,
  158 + 'resource_id': resource_id }
  159 +
  160 + return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
108 161
  162 + @staticmethod
  163 + def release_access(commitment_id):
  164 + return service_gateway_post('org_management', 'release_commitment', params={'commitment_id':commitment_id})
  165 +
  166 + @staticmethod
  167 + def request_exclusive_access(resource_id, actor_id, org_id, expiration):
  168 + sap = {'type_': 'AcquireResourceExclusiveProposal',
  169 + 'originator': 1,
  170 + 'consumer': actor_id,
  171 + 'provider': org_id,
  172 + 'proposal_status': 1,
  173 + 'resource_id': resource_id,
  174 + 'expiration': expiration}
  175 +
  176 + return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
  177 +
  178 + @staticmethod
  179 + def _accept_reject_negotiation(negotiation_id, accept_value):
  180 + if not accept_value in [3,4]:
  181 + return error_message("Unknown accept_value %s" % accept_value)
  182 +
  183 + # read the negotiation first so we can determine the sap to send
  184 + negotiation = service_gateway_get('resource_registry', 'read', params={'object_id': negotiation_id})
  185 +
  186 + sap = {'type_': negotiation['proposals'][0]['type_'],
  187 + 'negotiation_id': negotiation_id,
  188 + 'sequence_num': int(negotiation['proposals'][0]['sequence_num']) + 1,
  189 + 'originator': 2,
  190 + 'proposal_status': accept_value,
  191 + 'consumer': negotiation['proposals'][0]['consumer'],
  192 + 'provider': negotiation['proposals'][0]['provider']}
  193 +
  194 + return service_gateway_post('org_management', 'negotiate', params={'sap':sap})
  195 +
  196 + @staticmethod
  197 + def accept_negotiation(negotiation_id):
  198 + return ServiceApi._accept_reject_negotiation(negotiation_id, 3) # 3 == accepted
  199 +
  200 + @staticmethod
  201 + def reject_negotiation(negotiation_id):
  202 + return ServiceApi._accept_reject_negotiation(negotiation_id, 4) # 4 == rejected
109 203
110 204 @staticmethod
111 205 def get_event_types():
2  static/js/ion-ux.js
@@ -37,7 +37,7 @@ IONUX = {
37 37 },
38 38 is_owner: function(){
39 39 var user_id = IONUX.SESSION_MODEL.get('user_id');
40   - var owner_match = _.findWhere(MODEL_DATA.owners[0], {_id: user_id}) ? true : false;
  40 + var owner_match = _.findWhere(MODEL_DATA.owners, {_id: user_id}) ? true : false;
41 41 return owner_match;
42 42 }
43 43 }
2  static/js/ux-models.js
@@ -64,7 +64,7 @@ IONUX.Models.Session = Backbone.Model.extend({
64 64 return this.get('is_registered');
65 65 },
66 66 is_resource_owner: function(){
67   - return _.findWhere(MODEL_DATA.owners[0], {_id: this.get('user_id')}) ? true : false;
  67 + return _.findWhere(MODEL_DATA.owners, {_id: this.get('user_id')}) ? true : false;
68 68 }
69 69 });
70 70
33 static/js/ux-router.js
@@ -194,6 +194,28 @@ function replace_url_with_html_links(text) {
194 194 return text.replace(exp,"<a class='external' target='_blank' href='$1'>$1</a>");
195 195 };
196 196
  197 +// Filter method for open negotiations shown in a data table
  198 +// If this method returns true, there should be a column indicating
  199 +// the view will be shown on row click.
  200 +function negotiation_show_controls(row_data) {
  201 + // row data first column should always be of form resource_id/resource_type
  202 + // use this info to go and look up the real item, because UI columns may change
  203 + var neg = _.findWhere(window.MODEL_DATA.open_negotiations, {_id:row_data[0].split("::")[0]});
  204 + if (neg &&
  205 + window.MODEL_DATA.resource_type == "Org" &&
  206 + neg.proposals[0].originator == 1 && // originator == user proposed (1)
  207 + _.contains(IONUX.SESSION_MODEL.get('roles')[window.MODEL_DATA.resource.org_governance_name], 'ORG_MANAGER'))
  208 + return true;
  209 +
  210 + if (neg &&
  211 + window.MODEL_DATA.resource_type == "UserInfo" &&
  212 + neg.proposals[0].originator == 2 && // originator == org proposed (2)
  213 + neg.proposals[0].consumer == IONUX.SESSION_MODEL.get('actor_id')) // this is likely redundant
  214 + return true;
  215 +
  216 + return false;
  217 +};
  218 +
197 219 // Returns a displayable resource type for the resource_type given.
198 220 // If the type is not displayable, traverse up the heirarchy of resources
199 221 // until one is found.
@@ -287,7 +309,13 @@ function render_page(resource_type, resource_id, model) {
287 309 var data_path = $(el).data('path');
288 310 var raw_table_data = get_descendant_properties(window.MODEL_DATA, data_path);
289 311 if (!_.isEmpty(raw_table_data)) {
290   - var table = new IONUX.Views.DataTable({el: $(el), data: raw_table_data});
  312 + var opts = {el: $(el), data: raw_table_data}
  313 + if (data_path == "open_negotiations") {
  314 + _.extend(opts, {popup_view: IONUX.Views.NegotiationCommands,
  315 + popup_label: "Accept/Reject",
  316 + popup_filter_method: negotiation_show_controls});
  317 + }
  318 + var table = new IONUX.Views.DataTable(opts);
291 319 } else {
292 320 var table = new IONUX.Views.DataTable({el: $(el), data: []});
293 321 };
@@ -359,9 +387,6 @@ function render_page(resource_type, resource_id, model) {
359 387 case 'Recent Events':
360 388 new IONUX.Views.EventActions({el:$(el)});
361 389 break;
362   - case 'Participants':
363   - new IONUX.Views.NegotiationActions({el: $(el)});
364   - break;
365 390 default:
366 391 new IONUX.Views.GroupActions({el:$(el)});
367 392 };
7 static/js/ux-templates.js
@@ -3,4 +3,9 @@
3 3
4 4 IONUX.Templates = {
5 5 modal_template: '<div id="action-modal" class="modal modal-ooi"></div>',
6   -}
  6 + full_modal_template: '<div id="action-modal" class="modal modal-ooi">' +
  7 + '<div class="modal-header"><h1><%= header_text %></h1></div>' +
  8 + '<div class="modal-body"><%= body %></div>' +
  9 + '<div class="modal-footer"><%= buttons %></div>' +
  10 + '</div>',
  11 +}
87 static/js/ux-views-actionmenu.js
@@ -64,7 +64,64 @@ IONUX.Views.ViewActions = IONUX.Views.ActionMenu.extend({
64 64 modal_template: '<div id="action-modal" class="modal hide fade modal-ooi">',
65 65 initialize: function() {
66 66 _.bindAll(this);
67   - this.interaction_items = INTERACTIONS_OBJECT.view_interactions;
  67 + this.interaction_items = INTERACTIONS_OBJECT.view_interactions.slice(0); // ensure clone
  68 +
  69 + // append resource-specific items here
  70 + if (window.MODEL_DATA.resource_type == 'Org') {
  71 +
  72 + // ENROLLMENT
  73 + if (IONUX.is_logged_in()) {
  74 + if (IONUX.is_owner()) {
  75 + // INVITE USER
  76 + this.interaction_items.push("Invite User");
  77 + this.on("action__invite_user", this.action_org__invite_user);
  78 +
  79 + // INVITE ROLE
  80 + this.interaction_items.push("Offer User Role");
  81 + this.on("action__offer_user_role", this.action_org__offer_user_role);
  82 +
  83 + } else {
  84 + if (!_.some(window.MODEL_DATA.members, function(x) { return x._id == IONUX.SESSION_MODEL.get("user_id") })) {
  85 + // REQUEST ENROLLMENT
  86 + this.interaction_items.push("Enroll");
  87 + this.on("action__enroll", this.action_org__enroll);
  88 + } else {
  89 + // REQUEST ROLE
  90 + this.interaction_items.push("Request Role");
  91 + this.on("action__request_role", this.action_org__request_role);
  92 + }
  93 + }
  94 + }
  95 + } else if (_.contains(['PlatformDevice', 'InstrumentDevice', 'DataProduct'], window.MODEL_DATA.resource_type)) {
  96 + if (IONUX.is_logged_in() && !IONUX.is_owner()) {
  97 + // check commitments on current object for resource commitments for the current owner
  98 + var resource_commitments = _.filter(window.MODEL_DATA.commitments, function (c) { return c.commitment.type_ == "ResourceCommitment" && c.consumer == IONUX.SESSION_MODEL.get('actor_id'); });
  99 +
  100 + if (resource_commitments.length > 0) {
  101 + // we have access to this instrument, add item to release it
  102 + this.interaction_items.push("Release Resource");
  103 + // @TODO: assumption, only one commitment?
  104 + this.on("action__release_resource", _.partial(this.action__release_resource, resource_commitments[0]._id));
  105 +
  106 + // exclusive access?
  107 + var exc = _.find(resource_commitments, function(c) { return c.commitment.exclusive; });
  108 + if (exc == null) {
  109 + this.interaction_items.push("Request Exclusive Access");
  110 + // @TODO: assumption, only one commitment?
  111 + this.on("action__request_exclusive_access", _.partial(this.action__request_exclusive_access, resource_commitments[0].provider));
  112 +
  113 + } else {
  114 + this.interaction_items.push("Release Exclusive Access");
  115 + this.on("action__release_exclusive_access", _.partial(this.action__release_exclusive_access, exc._id));
  116 + }
  117 +
  118 + } else {
  119 + this.interaction_items.push("Request Access");
  120 + this.on("action__request_access", this.action__request_access);
  121 + }
  122 + }
  123 + }
  124 +
68 125 this.create_actionmenu();
69 126 this.on("action__subscribe", this.action__subscribe);
70 127 this.on("action__lifecycle", this.action__lifecycle);
@@ -143,6 +200,34 @@ IONUX.Views.ViewActions = IONUX.Views.ActionMenu.extend({
143 200 alert("Download not available for this resource.");
144 201 };
145 202 },
  203 + action_org__invite_user: function(e) {
  204 + var model = new IONUX.Collections.Resources(null, {resource_type: 'UserInfo'});
  205 + model.fetch()
  206 + .done(function(data) {
  207 + new IONUX.Views.InviteUser({model: model}).render().el;
  208 + });
  209 + },
  210 + action_org__offer_user_role: function(e) {
  211 + new IONUX.Views.OfferUserRole().render().el;
  212 + },
  213 + action_org__enroll: function(e) {
  214 + new IONUX.Views.Enroll().render().el;
  215 + },
  216 + action_org__request_role: function(e) {
  217 + new IONUX.Views.RequestRole().render().el;
  218 + },
  219 + action__request_access: function(e) {
  220 + new IONUX.Views.RequestAccess().render().el;
  221 + },
  222 + action__release_resource: function(commitment_id, e) {
  223 + new IONUX.Views.ReleaseAccess({commitment_id: commitment_id}).render().el;
  224 + },
  225 + action__request_exclusive_access: function(org_id, e) {
  226 + new IONUX.Views.RequestExclusiveAccess({org_id: org_id}).render().el;
  227 + },
  228 + action__release_exclusive_access: function(commitment_id, e) {
  229 + new IONUX.Views.ReleaseExclusiveAccess({commitment_id: commitment_id}).render().el;
  230 + },
146 231 });
147 232
148 233
28 static/js/ux-views-datatable.js
@@ -45,7 +45,7 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
45 45 "click .show-hide-filters":"show_hide_filters",
46 46 "click .filters-apply":"filters_apply",
47 47 "click .filters-reset":"filters_reset",
48   - "click table tbody tr":"table_row_click"
  48 + "click table tbody tr":"table_row_click",
49 49 },
50 50
51 51 template: _.template($('#datatable-tmpl').html()),
@@ -61,6 +61,7 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
61 61 this.$el.html(this.template());
62 62 var header_data = this.header_data();
63 63 var table_data = this.table_data(this.options.data);
  64 + var self = this;
64 65 this.datatable = this.$el.find(".datatable-container table").dataTable({
65 66 "sDom":"Rlfrtip",
66 67 "aaData":table_data,
@@ -76,9 +77,10 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
76 77 $('td', nRow).each(function() {
77 78 $(this).attr('title', $(this).text());
78 79 });
79   - }
  80 + },
80 81 });
81 82 if (this.options.data.length == 0){this.$el.find(".dataTables_scrollBody").css("overflow", "hidden")};
  83 +
82 84 return this;
83 85 },
84 86
@@ -102,6 +104,12 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
102 104 data_item["sClass"] = "center"; //TODO choose dependant on 'item[0]'
103 105 data.push(data_item);
104 106 });
  107 +
  108 + // blank column for popup column
  109 + if (this.options.hasOwnProperty('popup_view')) {
  110 + data.push({sTitle: "", sType: "title", sClass: "center"});
  111 + }
  112 +
105 113 return data;
106 114 },
107 115
@@ -128,6 +136,16 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
128 136 data_row.push(value);
129 137 });
130 138
  139 + // insert false column for popup
  140 + if (self.options.hasOwnProperty('popup_view')) {
  141 + if (self.options.popup_filter_method(data_row)) {
  142 + var label = self.options.popup_label || "Options";
  143 + data_row.push(label);
  144 + } else {
  145 + data_row.push('');
  146 + }
  147 + }
  148 +
131 149 // Temp fix to block associations from appearing in search results.
132 150 // Need to sub-class this view eventually.
133 151 if (resource_type != 'Association') data.push(data_row);
@@ -333,7 +351,11 @@ IONUX.Views.DataTable = IONUX.Views.Base.extend({
333 351 var resource_type = row_info_list[1];
334 352
335 353 if (resource_type.match(/Event$/)) return false;
336   -
  354 +
  355 + if (this.options.hasOwnProperty('popup_view') && this.options.popup_filter_method(table_row_data)) {
  356 + new this.options.popup_view({data:table_row_data, datatable:this.datatable}).render().el;
  357 + return;
  358 + }
337 359 // Check for type_ attachment and pop modal to let user
338 360 // choose between downloading an attachment or viewing
339 361 // its metadata on a face page.
443 static/js/ux-views-negotiations.js
@@ -9,7 +9,7 @@ IONUX.Views.Enroll = Backbone.View.extend({
9 9 },
10 10 render: function(){
11 11 var resource_name = window.MODEL_DATA.resource.name;
12   - $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
  12 + this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
13 13 .on('hide', function(){
14 14 $('#action-modal').remove();
15 15 });
@@ -17,14 +17,449 @@ IONUX.Views.Enroll = Backbone.View.extend({
17 17 return this;
18 18 },
19 19 request_enrollment: function(e){
  20 + var self = this;
20 21 e.preventDefault();
21 22 $.ajax({
22 23 type: 'POST',
23 24 url: window.location.href + 'enroll/',
24   - data: 'Word',
  25 + data: {},
25 26 success: function(resp) {
26   -
  27 + self.modal.modal('hide');
  28 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
  29 + body: 'Your request to enroll has been received and will be reviewed by a manager.',
  30 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  31 + .on('hide', function() {
  32 + $('#action-modal').remove();
  33 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  34 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  35 + });
27 36 }
28 37 })
29 38 },
30   -});
  39 +});
  40 +
  41 +IONUX.Views.RequestRole = Backbone.View.extend({
  42 + el: '#action-modal',
  43 + template: _.template($('#request-role-tmpl').html()),
  44 + events: {
  45 + 'click #btn-request': 'request_role'
  46 + },
  47 + initialize: function(){
  48 + _.bindAll(this);
  49 + },
  50 + render: function(){
  51 + var resource_name = window.MODEL_DATA.resource.name;
  52 + this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
  53 + .on('hide', function(){
  54 + $('#action-modal').remove();
  55 + });
  56 + this.setElement('#action-modal');
  57 + return this;
  58 + },
  59 + request_role: function(e){
  60 + var self = this;
  61 + var role_name = self.$('select[name="role_name"]').val();
  62 + e.preventDefault();
  63 + $.ajax({
  64 + type: 'POST',
  65 + url: window.location.href + 'request_role/',
  66 + data: {role_name: role_name},
  67 + success: function(resp) {
  68 + self.modal.modal('hide');
  69 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
  70 + body: 'Your request has been received and will be reviewed by a manager.',
  71 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  72 + .on('hide', function() {
  73 + $('#action-modal').remove();
  74 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  75 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  76 + });
  77 + }
  78 + })
  79 + },
  80 +
  81 +});
  82 +
  83 +IONUX.Views.InviteUser = Backbone.View.extend({
  84 + el: '#action-modal',
  85 + template: _.template($('#invite-user-tmpl').html()),
  86 + events: {
  87 + 'click #btn-invite': 'invite_user'
  88 + },
  89 + initialize: function(){
  90 + _.bindAll(this);
  91 + },
  92 + render: function(){
  93 + var resource_name = window.MODEL_DATA.resource.name;
  94 + this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
  95 + .on('hide', function(){
  96 + $('#action-modal').remove();
  97 + });
  98 + this.setElement('#action-modal');
  99 + return this;
  100 + },
  101 + invite_user: function(e){
  102 + var self = this;
  103 + var user = self.$('select[name="user"]').val();
  104 + e.preventDefault();
  105 + $.ajax({
  106 + type: 'POST',
  107 + url: window.location.href + 'invite_user/',
  108 + data: {user_id: user},
  109 + success: function(resp) {
  110 + self.modal.modal('hide');
  111 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Invite Received',
  112 + body: 'Your invite has been received and will be reviewed by the user.',
  113 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  114 + .on('hide', function() {
  115 + $('#action-modal').remove();
  116 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  117 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  118 + });
  119 + }
  120 + })
  121 + },
  122 +});
  123 +
  124 +IONUX.Views.OfferUserRole = Backbone.View.extend({
  125 + el: '#action-modal',
  126 + template: _.template($('#offer-user-role-tmpl').html()),
  127 + events: {
  128 + 'click #btn-offer': 'offer_user_role',
  129 + 'change select[name="user"]': 'user_changed'
  130 + },
  131 + initialize: function(){
  132 + _.bindAll(this);
  133 + this.user_cache = {}
  134 + },
  135 + render: function(){
  136 + var resource_name = window.MODEL_DATA.resource.name;
  137 + this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name})).modal()
  138 + .on('hide', function(){
  139 + $('#action-modal').remove();
  140 + });
  141 + this.setElement('#action-modal');
  142 + return this;
  143 + },
  144 + user_changed: function(e) {
  145 + var user_id = this.$('select[name="user"]').val();
  146 + var self = this;
  147 +
  148 + if (user_id == null) {
  149 + e.preventDefault();
  150 + return;
  151 + }
  152 +
  153 + if (this.user_cache.hasOwnProperty(user_id)) {
  154 + var user_roles = this.user_cache[user_id];
  155 + this.update_displayed_roles(user_roles);
  156 + } else {
  157 +
  158 + self.$('#rolespinner').show();
  159 + self.$('select[name="role_name"]').empty();
  160 +
  161 + $.ajax({
  162 + url: window.location.protocol + "//" + window.location.host + "/UserInfo/extension/" + user_id + "/",
  163 + success: function(resp) {
  164 + self.$('#rolespinner').hide();
  165 +
  166 + var user_roles = _.pluck(_.where(resp.data.roles, { org_governance_name: window.MODEL_DATA.resource.org_governance_name }), 'governance_name');
  167 + self.user_cache[user_id] = user_roles;
  168 +
  169 + self.update_displayed_roles(user_roles);
  170 + }
  171 + });
  172 + }
  173 + },
  174 + update_displayed_roles: function(user_roles) {
  175 + var role = this.$('select[name="role_name"]');
  176 +
  177 + var new_roles = _.filter(window.MODEL_DATA.roles, function(r) { return !_.contains(user_roles, r.governance_name); });
  178 +
  179 + role.empty();
  180 + _.each(new_roles, function(r) {
  181 + role.append("<option value='" + r.governance_name + "'>" + r.name + "</option>");
  182 + });
  183 + },
  184 + offer_user_role: function(e){
  185 + var self = this;
  186 + var user = self.$('select[name="user"]').val();
  187 + var role_name = self.$('select[name="role_name"]').val();
  188 +
  189 + if (user == null || role_name == null)
  190 + return;
  191 +
  192 + e.preventDefault();
  193 + $.ajax({
  194 + type: 'POST',
  195 + url: window.location.href + 'offer_user_role/',
  196 + data: {user_id: user,
  197 + role_name: role_name},
  198 + success: function(resp) {
  199 + self.modal.modal('hide');
  200 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Role Offer Received',
  201 + body: 'Your role offer has been received and will be reviewed by the user.',
  202 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  203 + .on('hide', function() {
  204 + $('#action-modal').remove();
  205 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  206 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  207 + });
  208 + }
  209 + })
  210 + },
  211 +});
  212 +
  213 +IONUX.Views.RequestAccess = Backbone.View.extend({
  214 + events: {
  215 + 'click #btn-request': 'request_access'
  216 + },
  217 + initialize: function(){
  218 + _.bindAll(this);
  219 + },
  220 + render: function(){
  221 + // devices etc have a list of orgs they are a part of
  222 + // the user has a list of orgs/roles they have
  223 + // need to find where they intersect (as INSTRUMENT_OPERATOR)
  224 + var org_id = null;
  225 + var roles = IONUX.SESSION_MODEL.get('roles');
  226 +
  227 + // @TODO: there's got to be a better way than this
  228 + var user_orgs = _.map(_.filter(_.pairs(roles), function(r) { return _.contains(r[1], "INSTRUMENT_OPERATOR") } ), function(v) { return v[0] })
  229 +
  230 + this.matching_orgs = _.filter(window.MODEL_DATA.orgs, function(o) { return _.contains(user_orgs, o.org_governance_name); });
  231 +
  232 + var resource_name = window.MODEL_DATA.resource.name;
  233 +
  234 + var cancel_button = "<button class='btn-general' data-dismiss='modal'>Cancel</button>";
  235 + var template_vars = {header_text: 'Request Access: ' + resource_name,
  236 + body: 'Press the Request Access button below to request access to this resource. All requests must be approved by a manager.',
  237 + buttons: "<button class='btn-blue' id='btn-request'>Request Access</button>" + cancel_button}
  238 +
  239 + // sub out message/buttons if we don't have perms
  240 + if (this.matching_orgs.length == 0) {
  241 + template_vars.body = "You must have the instrument operator role in the associated org to request access to this device.";
  242 + template_vars.buttons = cancel_button;
  243 + }
  244 +
  245 + this.modal = $(_.template(IONUX.Templates.full_modal_template, template_vars));
  246 + this.modal.modal('show')
  247 + .on('hide', function() {
  248 + $('#action-modal').remove();
  249 + });
  250 + this.setElement('#action-modal');
  251 + return this;
  252 + },
  253 + request_access: function(e){
  254 + if (this.matching_orgs.length == 0) {
  255 + alert("Could not find an Org that you are an instrument operator of and this device belongs to!");
  256 + return;
  257 + } else if (this.matching_orgs.length > 1) {
  258 + console.warn("More than one matching Org found (" + this.matching_orgs.length + "), using first: " + this.matching_orgs[0]);
  259 + }
  260 +
  261 + org_id = this.matching_orgs[0]._id;
  262 +
  263 + var self = this;
  264 + e.preventDefault();
  265 + $.ajax({
  266 + type: 'POST',
  267 + url: window.location.href + 'request_access/',
  268 + data: {org_id: org_id},
  269 + success: function(resp) {
  270 + self.modal.modal('hide');
  271 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
  272 + body: 'Your request has been received and will be reviewed by a manager.',
  273 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  274 + .on('hide', function() {
  275 + $('#action-modal').remove();
  276 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  277 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  278 + });
  279 + }
  280 + })
  281 + },
  282 +});
  283 +
  284 +IONUX.Views.ReleaseAccess = Backbone.View.extend({
  285 + events: {
  286 + 'click #btn-release': 'release_access'
  287 + },
  288 + initialize: function(){
  289 + _.bindAll(this);
  290 + },
  291 + template_vars: {
  292 + header_text: 'Release Access',
  293 + body: 'Press the Release Access button below to release access to this resource.',
  294 + buttons: "<button class='btn-blue' id='btn-release'>Release Access</button><button class='btn-general' data-dismiss='modal'>Cancel</button>",
  295 + },
  296 + render: function(){
  297 + this.modal = $(_.template(IONUX.Templates.full_modal_template, this.template_vars));
  298 + this.modal.modal('show')
  299 + .on('hide', function() {
  300 + $('#action-modal').remove();
  301 + });
  302 + this.setElement('#action-modal');
  303 + return this;
  304 + },
  305 + release_access: function(e){
  306 + var self = this;
  307 + e.preventDefault();
  308 + $.ajax({
  309 + type: 'POST',
  310 + url: window.location.href + 'release_access/',
  311 + data: {commitment_id: this.options.commitment_id},
  312 + success: function(resp) {
  313 + self.modal.modal('hide');
  314 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Release Received',
  315 + body: 'Your release has been received.',
  316 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  317 + .on('hide', function() {
  318 + $('#action-modal').remove();
  319 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  320 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  321 + });
  322 + }
  323 + })
  324 + },
  325 +});
  326 +
  327 +IONUX.Views.RequestExclusiveAccess = Backbone.View.extend({
  328 + template: _.template($("#request-exclusive-access-tmpl").html()),
  329 + events: {
  330 + 'click #btn-request': 'request_access'
  331 + },
  332 + initialize: function(){
  333 + _.bindAll(this);
  334 + },
  335 + render: function(){
  336 +
  337 + // check to see if someone ELSE has exclusive access so we can warn user it will fail
  338 + var exc = _.find(window.MODEL_DATA.commitments, function(c) { return c.commitment.exclusive && c.consumer != IONUX.SESSION_MODEL.get('actor_id'); });
  339 + if (exc != null) {
  340 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Exclusive Access',
  341 + body: 'Another user already has exclusive access to this device.',
  342 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  343 + .on('hide', function() {
  344 + $('#action-modal').remove();
  345 + });
  346 +
  347 + return;
  348 + }
  349 +
  350 + var resource_name = window.MODEL_DATA.resource.name;
  351 +
  352 + this.modal = $(IONUX.Templates.modal_template).html(this.template({resource_name: resource_name}));
  353 + this.modal.modal('show')
  354 + .on('hide', function() {
  355 + $('#action-modal').remove();
  356 + });
  357 + this.setElement('#action-modal');
  358 + return this;
  359 + },
  360 + request_access: function(e){
  361 + var expiration = parseInt(this.$('input[name="time"]').val());
  362 + if (expiration <= 0 || expiration > 12) {
  363 + this.$('.control-group').addClass('error');
  364 + this.$('.help-inline').append("Please enter a value between 0 and 12");
  365 + return;
  366 + } else {
  367 + this.$('.control-group').removeClass('error');
  368 + this.$('.help-inline').empty();
  369 + }
  370 +
  371 + var self = this;
  372 + e.preventDefault();
  373 + $.ajax({
  374 + type: 'POST',
  375 + url: window.location.href + 'request_exclusive_access/',
  376 + data: {expiration: expiration,
  377 + org_id: this.options.org_id},
  378 + success: function(resp) {
  379 + self.modal.modal('hide');
  380 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Request Received',
  381 + body: 'Your request has been received and will be reviewed by a manager.',
  382 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  383 + .on('hide', function() {
  384 + $('#action-modal').remove();
  385 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  386 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  387 + });
  388 + }
  389 + })
  390 + },
  391 +});
  392 +
  393 +IONUX.Views.ReleaseExclusiveAccess = IONUX.Views.ReleaseAccess.extend({
  394 + template_vars: {
  395 + header_text: 'Release Exclusive Access',
  396 + body: 'Press the Release Exclusive Access button below to release access to this resource.',
  397 + buttons: "<button class='btn-blue' id='btn-release'>Release Access</button><button class='btn-general' data-dismiss='modal'>Cancel</button>",
  398 + },
  399 +});
  400 +
  401 +IONUX.Views.NegotiationCommands = Backbone.View.extend({
  402 + template: _.template($("#negotiation-commands-tmpl").html()),
  403 + events: {
  404 + 'click #btn-accept': 'accept',
  405 + 'click #btn-reject': 'reject',
  406 + },
  407 + initialize: function(){
  408 + _.bindAll(this);
  409 + var row_info_list = this.options.data[0].split("::");
  410 + this.resource_id = row_info_list[0];
  411 + this.resource_type = row_info_list[1];
  412 + },
  413 + render: function(){
  414 + this.modal = $(IONUX.Templates.modal_template).html(this.template({negotiation_id: this.resource_id,
  415 + submitted: this.options.data[1]}));
  416 + this.modal.modal('show')
  417 + .on('hide', function() {
  418 + $('#action-modal').remove();
  419 + });
  420 + this.setElement('#action-modal');
  421 + return this;
  422 + },
  423 + accept: function(e){
  424 + var self = this;
  425 + e.preventDefault();
  426 + $.ajax({
  427 + type: 'POST',
  428 + url: window.location.protocol + "//" + window.location.host + "/negotiation/accept/",
  429 + data: {negotiation_id: this.resource_id},
  430 + success: function(resp) {
  431 + self.modal.modal('hide');
  432 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Accepted',
  433 + body: 'You have accepted this negotiation request.',
  434 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  435 + .on('hide', function() {
  436 + $('#action-modal').remove();
  437 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  438 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  439 + });
  440 + }
  441 + })
  442 + },
  443 + reject: function(e){
  444 + var self = this;
  445 + e.preventDefault();
  446 + $.ajax({
  447 + type: 'POST',
  448 + url: window.location.protocol + "//" + window.location.host + "/negotiation/reject/",
  449 + data: {negotiation_id: this.resource_id},
  450 + success: function(resp) {
  451 + self.modal.modal('hide');
  452 + $(_.template(IONUX.Templates.full_modal_template, {header_text:'Rejected',
  453 + body: 'You have rejected this negotiation request.',
  454 + buttons: "<button class='btn-blue' data-dismiss='modal'>OK</button>"})).modal()
  455 + .on('hide', function() {
  456 + $('#action-modal').remove();
  457 + Backbone.history.fragment = null; // Clear history fragment to allow for page "refresh".
  458 + IONUX.ROUTER.navigate(window.location.pathname, {trigger: true});
  459 + });
  460 + }
  461 + })
  462 + },
  463 +});
  464 +
  465 +
5 templates/ion_ux.html
@@ -76,6 +76,11 @@
76 76 <script type="text/template" id="extent-vertical-tmpl"><![CDATA[{% include 'partials/extent_vertical.html' %}]]></script>
77 77 <script type="text/template" id="extent-temporal-tmpl"><![CDATA[{% include 'partials/extent_temporal.html' %}]]></script>
78 78 <script type="text/template" id="enroll-request-tmpl"><![CDATA[{% include 'partials/enroll_request.html' %}]]></script>
  79 + <script type="text/template" id="request-role-tmpl"><![CDATA[{% include 'partials/request_role.html' %}]]></script>
  80 + <script type="text/template" id="invite-user-tmpl"><![CDATA[{% include 'partials/invite_user.html' %}]]></script>
  81 + <script type="text/template" id="offer-user-role-tmpl"><![CDATA[{% include 'partials/offer_user_role.html' %}]]></script>
  82 + <script type="text/template" id="request-exclusive-access-tmpl"><![CDATA[{% include 'partials/request_exclusive_access.html' %}]]></script>
  83 + <script type="text/template" id="negotiation-commands-tmpl"><![CDATA[{% include 'partials/negotiation_commands.html' %}]]></script>
79 84 <script type="text/template" id="error-tmpl"><![CDATA[{% include 'partials/error.html' %}]]></script>
80 85 {% block extra_templates %} {% endblock %}
81 86
11 templates/partials/enroll_request.html
... ... @@ -1,11 +1,14 @@
1 1 <div class="modal-header">
2 2 <h1>Enroll Request: <%= resource_name %></h1>
3 3 </div>
4   -<div class="modal-body error">
5   - <div id="request">
6   - <button id="btn-request" class="btn-blue">Request Enrollment</button>
7   - </div>
  4 +<div class="modal-body">
  5 + <p>
  6 + Press the Request Enrollment button below to initiate your request to enroll in
  7 + this observatory. A manager must approve your request before you are granted
  8 + access.
  9 + </p>
8 10 </div>
9 11 <div class="modal-footer">
  12 + <button id="btn-request" class="btn-blue">Request Enrollment</button>
10 13 <button class="btn-general" data-dismiss="modal">Cancel</button>
11 14 </div>
27 templates/partials/invite_user.html
... ... @@ -0,0 +1,27 @@
  1 +<div class="modal-header">
  2 + <h1>Invite User: <%= resource_name %></h1>
  3 +</div>
  4 +<div class="modal-body">
  5 + <p>
  6 + Select a user to invite to become a member. The user will need to accept the invitation
  7 + in order for it to be approved.
  8 + </p>
  9 + <form class="form-horizontal">
  10 + <% var curmembers = _.pluck(window.MODEL_DATA.members, '_id'); %>
  11 + <div class="control-group">
  12 + <label class="control-label" for="user">User</label>
  13 + <div class="controls">
  14 + <select name="user" size="6">
  15 + <% _.each(_.filter(this.model.toJSON(), function(d) { return !_.contains(curmembers, d._id); }), function (user) { %>
  16 + <option value="<%= user._id %>"><%= user.name %></option>
  17 + <% }); %>
  18 + </select>
  19 + </div>
  20 + </div>
  21 + </form>
  22 +</div>
  23 +<div class="modal-footer">
  24 + <button id="btn-invite" class="btn-blue">Invite User</button>
  25 + <button class="btn-general" data-dismiss="modal">Cancel</button>
  26 +</div>
  27 +
18 templates/partials/negotiation_commands.html
... ... @@ -0,0 +1,18 @@
  1 +<div class="modal-header">
  2 + <h1>Accept/Reject Negotiation: <%= negotiation_id %></h1>
  3 +</div>
  4 +<div class="modal-body">
  5 + <p>
  6 + Please accept or reject this access/role proposal.
  7 + </p>
  8 +
  9 + <dl>
  10 + <dt>Request Submitted</dt>
  11 + <dd><%= submitted %></dd>
  12 + </dl>
  13 +</div>
  14 +<div class="modal-footer">
  15 + <button id="btn-accept" class="btn-blue">Accept</button>
  16 + <button id="btn-reject" class="btn-blue">Reject</button>
  17 + <button class="btn-general" data-dismiss="modal">Cancel</button>
  18 +</div>
41 templates/partials/offer_user_role.html
... ... @@ -0,0 +1,41 @@
  1 +<div class="modal-header">
  2 + <h1>Offer User Role: <%= resource_name %></h1>
  3 +</div>
  4 +<div class="modal-body">
  5 + <p>
  6 + Select a member to offer a role to, and the role to offer. The user will need to
  7 + accept the offer in order for it to be approved.
  8 + </p>
  9 + <form class="form-horizontal">
  10 + <div class="control-group">
  11 + <label class="control-label" for="user">User</label>
  12 + <div class="controls">
  13 + <select name="user" size="6">
  14 + <% _.each(window.MODEL_DATA.members, function (user) { %>
  15 + <option value="<%= user._id %>"><%= user.name %></option>
  16 + <% }); %>
  17 + </select>
  18 + </div>
  19 + </div>
  20 +
  21 + <div class="control-group">
  22 + <label class="control-label" for="role_name">
  23 + <img style="display: none" src="/static/img/busy_indicator.gif" alt="loading" id="rolespinner" />
  24 + Role
  25 + </label>
  26 + <div class="controls">
  27 + <select name="role_name" size="6">
  28 + <% _.each(window.MODEL_DATA.roles, function (role) { %>
  29 + <option value="<%= role.governance_name %>"><%= role.name %></option>
  30 + <% }); %>
  31 + </select>
  32 + </div>
  33 + </div>
  34 +
  35 + </form>
  36 +</div>
  37 +<div class="modal-footer">
  38 + <button id="btn-offer" class="btn-blue">Offer Role</button>
  39 + <button class="btn-general" data-dismiss="modal">Cancel</button>
  40 +</div>
  41 +
22 templates/partials/request_exclusive_access.html
... ... @@ -0,0 +1,22 @@
  1 +<div class="modal-header">
  2 + <h1>Request Exclusive Access: <%= resource_name %></h1>
  3 +</div>
  4 +<div class="modal-body">
  5 + <p>
  6 + Select the amount of time (up to 12 hours) you would like exclusive access to this resource.
  7 + </p>
  8 + <form class="form-horizontal">
  9 + <div class="control-group">
  10 + <label class="control-label" for="time">Reserved Time</label>
  11 + <div class="controls">
  12 + <input type="text" name="time" value="12" />
  13 + <span class="help-inline"></span>
  14 + </div>
  15 + </div>
  16 + </form>
  17 +</div>
  18 +<div class="modal-footer">
  19 + <button id="btn-request" class="btn-blue">Request Exclusive Access</button>
  20 + <button class="btn-general" data-dismiss="modal">Cancel</button>
  21 +</div>
  22 +
37 templates/partials/request_role.html
... ... @@ -0,0 +1,37 @@
  1 +<div class="modal-header">
  2 + <h1>Request Role: <%= resource_name %></h1>
  3 +</div>
  4 +<div class="modal-body">