/reviewboard/static/rb/js/accountPrefsPage/views/apiTokensView.js
JavaScript | 605 lines | 348 code | 90 blank | 167 comment | 17 complexity | cdcb25782edc83d9e8355e4e0df6a592 MD5 | raw file
Possible License(s): GPL-2.0
- (function() {
- var APITokenItem,
- APITokenItemCollection,
- APITokenItemView,
- PolicyEditorView,
- SiteAPITokensView,
- POLICY_READ_WRITE = 'rw',
- POLICY_READ_ONLY = 'ro',
- POLICY_CUSTOM = 'custom',
- POLICY_LABELS = {};
- POLICY_LABELS[POLICY_READ_WRITE] = gettext('Full access');
- POLICY_LABELS[POLICY_READ_ONLY] = gettext('Read-only');
- POLICY_LABELS[POLICY_CUSTOM] = gettext('Custom');
- /*
- * Represents an API token in the list.
- *
- * This provides actions for editing the policy type for the token and
- * removing the token.
- */
- APITokenItem = RB.Config.ResourceListItem.extend({
- defaults: _.defaults({
- policyType: POLICY_READ_WRITE,
- localSiteName: null,
- showRemove: true
- }, RB.Config.ResourceListItem.prototype.defaults),
- syncAttrs: ['id', 'note', 'policy', 'tokenValue'],
- /*
- * Initializes the item.
- *
- * This computes the type of policy used, for display, and builds the
- * policy actions menu.
- */
- initialize: function(options) {
- var policy,
- policyType;
- _super(this).initialize.call(this, options);
- this.on('change:policyType', this._onPolicyTypeChanged, this);
- policy = this.get('policy') || {};
- policyType = this._guessPolicyType(policy);
- this._policyMenuAction = {
- id: 'policy',
- label: POLICY_LABELS[policyType],
- children: [
- this._makePolicyAction(POLICY_READ_WRITE),
- this._makePolicyAction(POLICY_READ_ONLY),
- this._makePolicyAction(POLICY_CUSTOM, {
- id: 'policy-custom',
- dispatchOnClick: true
- })
- ]
- };
- this.actions.unshift(this._policyMenuAction);
- this.set('policyType', policyType);
- },
- /*
- * Creates an APIToken resource for the given attributes.
- */
- createResource: function(attrs) {
- return new RB.APIToken(_.defaults({
- userName: RB.UserSession.instance.get('username'),
- localSitePrefix: this.collection.localSitePrefix
- }, attrs));
- },
- /*
- * Sets the provided note on the token and saves it.
- */
- saveNote: function(note, options, context) {
- this._saveAttribute('note', note, options, context);
- },
- /*
- * Sets the provided policy on the token and saves it.
- */
- savePolicy: function(policy, options, context) {
- this._saveAttribute('policy', policy, options, context);
- },
- /*
- * Sets an attribute on the token and saves it.
- *
- * This is a helper function that will set an attribute on the token
- * and save it, but only after the token is ready.
- */
- _saveAttribute: function(attr, value, options, context) {
- this.resource.ready({
- ready: function() {
- this.resource.set(attr, value);
- this.resource.save(options, context);
- }
- }, this);
- },
- /*
- * Guesses the policy type for a given policy definition.
- *
- * This compares the policy against the built-in versions that
- * RB.APIToken provides. If one of them matches, the appropriate
- * policy type will be returned. Otherwise, this assumes it's a
- * custom policy.
- */
- _guessPolicyType: function(policy) {
- if (_.isEqual(policy, RB.APIToken.defaultPolicies.readOnly)) {
- return POLICY_READ_ONLY;
- } else if (_.isEqual(policy, RB.APIToken.defaultPolicies.readWrite)) {
- return POLICY_READ_WRITE;
- } else {
- return POLICY_CUSTOM;
- }
- },
- /*
- * Creates and returns an action for the policy menu.
- *
- * This takes a policy type and any options to include with the
- * action definition. It will then return a suitable action,
- * for display in the policy menu.
- */
- _makePolicyAction: function(policyType, options) {
- return _.defaults({
- label: POLICY_LABELS[policyType],
- type: 'radio',
- name: 'policy-type',
- propName: 'policyType',
- radioValue: policyType
- }, options);
- },
- /*
- * Handler for when the policy type changes.
- *
- * This will set the policy menu's label to that of the selected
- * policy and rebuild the menu.
- *
- * Then, if not using a custom policy, the built-in policy definition
- * matching the selected policy will be saved to the server.
- */
- _onPolicyTypeChanged: function() {
- var policyType = this.get('policyType'),
- newPolicy;
- this._policyMenuAction.label = POLICY_LABELS[policyType];
- this.trigger('actionsChanged');
- if (policyType === POLICY_READ_ONLY) {
- newPolicy = RB.APIToken.defaultPolicies.readOnly;
- } else if (policyType === POLICY_READ_WRITE) {
- newPolicy = RB.APIToken.defaultPolicies.readWrite;
- } else {
- return;
- }
- if (!_.isEqual(newPolicy, this.get('policy'))) {
- this.savePolicy(newPolicy);
- }
- }
- });
- /*
- * A collection of APITokenItems.
- *
- * This works like a standard Backbone.Collection, but can also have
- * a LocalSite URL prefix attached to it, for use in API calls in
- * APITokenItem.
- */
- APITokenItemCollection = Backbone.Collection.extend({
- model: APITokenItem,
- initialize: function(models, options) {
- this.localSitePrefix = options.localSitePrefix;
- }
- });
- /*
- * Renders an APITokenItem to the page, and handles actions.
- *
- * This will display the information on the given token. Specifically,
- * the token value, the note, and the actions.
- *
- * This also handles deleting the token when the Remove action is clicked,
- * and displaying the policy editor when choosing a custom policy.
- */
- APITokenItemView = Djblets.Config.ListItemView.extend({
- EMPTY_NOTE_PLACEHOLDER: gettext('Click to describe this token'),
- template: _.template([
- '<div class="config-api-token-value"><%- tokenValue %></div>',
- '<span class="config-api-token-note"></span>'
- ].join('')),
- actionHandlers: {
- 'delete': '_onRemoveClicked',
- 'policy-custom': '_onCustomPolicyClicked'
- },
- /*
- * Initializes the view.
- */
- initialize: function(options) {
- _super(this).initialize.call(this, options);
- this._$note = null;
- this.listenTo(this.model.resource, 'change:note', this._updateNote);
- },
- /*
- * Renders the view.
- */
- render: function() {
- _super(this).render.call(this);
- this._$note = this.$('.config-api-token-note')
- .inlineEditor({
- editIconClass: 'rb-icon rb-icon-edit'
- })
- .on({
- beginEdit: _.bind(function() {
- this._$note.inlineEditor('setValue',
- this.model.get('note'));
- }, this),
- complete: _.bind(function(e, value) {
- this.model.saveNote(value);
- }, this)
- });
- this._updateNote();
- return this;
- },
- /*
- * Updates the displayed note.
- *
- * If no note is set, then a placeholder will be shown, informing the
- * user that they can edit the note. Otherwise, their note contents
- * will be shown.
- */
- _updateNote: function() {
- var note = this.model.resource.get('note');
- if (note) {
- this._$note
- .removeClass('empty')
- .text(note);
- } else {
- this._$note
- .addClass('empty')
- .text(this.EMPTY_NOTE_PLACEHOLDER);
- }
- },
- /*
- * Handler for when the "Custom" policy action is clicked.
- *
- * This displays the policy editor, allowing the user to edit a
- * custom policy for the token.
- *
- * The previously selected policy type is passed along to the editor,
- * so that the editor can revert to it if the user cancels.
- */
- _onCustomPolicyClicked: function() {
- var view = new PolicyEditorView({
- model: this.model,
- prevPolicyType: this.model.previous('policyType')
- });
- view.render();
- return false;
- },
- /*
- * Handler for when the Remove action is clicked.
- *
- * This will prompt for confirmation before removing the token from
- * the server.
- */
- _onRemoveClicked: function() {
- $('<p/>')
- .html(gettext('This will prevent clients using this token when authenticating.'))
- .modalBox({
- title: gettext('Are you sure you want to remove this token?'),
- buttons: [
- $('<input type="button"/>')
- .val(gettext('Cancel')),
- $('<input type="button" class="danger" />')
- .val(gettext('Remove'))
- .click(_.bind(function() {
- this.model.resource.destroy();
- }, this))
- ]
- });
- }
- });
- /*
- * Provides an editor for constructing or modifying a custom policy definition.
- *
- * This renders as a modalBox with a CodeMirror editor inside of it. The
- * editor is set to allow easy editing of a JSON payload, complete with
- * lintian checking. Only valid policy payloads can be saved to the server.
- */
- PolicyEditorView = Backbone.View.extend({
- id: 'custom_policy_editor',
- template: _.template([
- '<p><%= instructions %></p>',
- '<textarea/>'
- ].join('')),
- /*
- * Initializes the editor.
- */
- initialize: function(options) {
- this.prevPolicyType = options.prevPolicyType;
- this._codeMirror = null;
- this._$policy = null;
- this._$saveButtons = null;
- },
- /*
- * Renders the editor.
- *
- * The CodeMirror editor will be set up and configured, and then the
- * view will be placed inside a modalBox.
- */
- render: function() {
- var policy = this.model.get('policy');
- if (_.isEmpty(this.model.get('policy'))) {
- policy = RB.APIToken.defaultPolicies.custom;
- }
- this.$el.html(this.template({
- instructions: interpolate(
- gettext('You can limit access to the API through a custom policy. See the <a href="%s" target="_blank">documentation</a> on how to write policies.'),
- [MANUAL_URL + 'webapi/2.0/api-token-policy/'])
- }));
- this._$policy = this.$('textarea')
- .val(JSON.stringify(policy, null, ' '));
- this.$el.modalBox({
- title: gettext('Custom Token Access Policy'),
- buttons: [
- $('<input type="button"/>')
- .val(gettext('Cancel'))
- .click(_.bind(this.cancel, this)),
- $('<input type="button" class="save-button"/>')
- .val(gettext('Save and continue editing'))
- .click(_.bind(function() {
- this.save();
- return false;
- }, this)),
- $('<input type="button" class="btn primary save-button"/>')
- .val(gettext('Save'))
- .click(_.bind(function() {
- this.save(true);
- return false;
- }, this))
- ]
- });
- this._$saveButtons = this.$el.modalBox('buttons').find('.save-button');
- this._codeMirror = CodeMirror.fromTextArea(this._$policy[0], {
- mode: 'application/json',
- lineNumbers: true,
- lineWrapping: true,
- matchBrackets: true,
- lint: {
- onUpdateLinting: _.bind(this._onUpdateLinting, this)
- },
- gutters: ['CodeMirror-lint-markers']
- });
- this._codeMirror.focus();
- },
- /*
- * Removes the policy editor from the page.
- */
- remove: function() {
- this.$el.modalBox('destroy');
- },
- /*
- * Cancels the editor.
- *
- * The previously-selected policy type will be set on the model.
- */
- cancel: function() {
- this.model.set('policyType', this.prevPolicyType);
- },
- /*
- * Saves the editor.
- *
- * The policy will be saved to the server for immediate use.
- */
- save: function(closeOnSave) {
- var policyStr = this._codeMirror.getValue().strip(),
- policy;
- try {
- policy = JSON.parse(policyStr);
- } catch (e) {
- alert(interpolate(
- gettext('There is a syntax error in your policy: %s'),
- [e]));
- return false;
- }
- this.model.savePolicy(policy, {
- success: function() {
- if (closeOnSave) {
- this.remove();
- }
- },
- error: function(model, xhr) {
- if (xhr.errorPayload.err.code === 105 &&
- xhr.errorPayload.fields.policy) {
- alert(xhr.errorPayload.fields.policy);
- } else {
- alert(xhr.errorPayload.err.msg);
- }
- }
- }, this);
- return false;
- },
- /*
- * Handler for when lintian checking has run.
- *
- * This will disable the save buttons if there are any lintian errors.
- */
- _onUpdateLinting: function(annotationsNotSorted) {
- this._$saveButtons.prop('disabled', annotationsNotSorted.length > 0);
- }
- });
- /*
- * Renders and manages a list of global or per-LocalSite API tokens.
- *
- * This will display all provided API tokens in a list, optionally labeled
- * by Local Site name. These can be removed or edited, or new tokens generated
- * through a "Generate a new API token" link.
- */
- SiteAPITokensView = Backbone.View.extend({
- className: 'config-site-api-tokens',
- template: _.template([
- '<% if (name) { %>',
- ' <h3><%- name %></h3>',
- '<% } %>',
- '<div class="api-tokens box-recessed">',
- ' <div class="generate-api-token config-forms-list-item">',
- ' <a href="#"><%- generateText %></a>',
- ' </div>',
- '</div>'
- ].join('')),
- events: {
- 'click .generate-api-token': '_onGenerateClicked'
- },
- /*
- * Initializes the view.
- *
- * This will construct the collection of tokens and construct
- * a list for the ListView.
- */
- initialize: function(options) {
- this.localSiteName = options.localSiteName;
- this.localSitePrefix = options.localSitePrefix;
- this.collection = new APITokenItemCollection(options.apiTokens, {
- localSitePrefix: this.localSitePrefix
- });
- this.apiTokensList = new Djblets.Config.List({}, {
- collection: this.collection
- });
- this._listView = null;
- },
- /*
- * Renders the view.
- *
- * This will render the list of API token items, along with a link
- * for generating new tokens.
- */
- render: function() {
- this._listView = new Djblets.Config.ListView({
- ItemView: APITokenItemView,
- animateItems: true,
- model: this.apiTokensList
- });
- this.$el.html(this.template({
- name: this.localSiteName,
- generateText: gettext('Generate a new API token')
- }));
- this._listView.render().$el.prependTo(this.$('.api-tokens'));
- return this;
- },
- /*
- * Handler for when the "Generate a new API token" link is clicked.
- *
- * This creates a new API token on the server and displays it in the list.
- */
- _onGenerateClicked: function() {
- var apiToken = new RB.APIToken({
- localSitePrefix: this.localSitePrefix,
- userName: RB.UserSession.instance.get('username')
- });
- apiToken.save({
- success: function() {
- this.collection.add({
- resource: apiToken
- });
- }
- }, this);
- return false;
- }
- });
- /*
- * Renders and manages a page of API tokens.
- *
- * This will take the provided tokens and group them into SiteAPITokensView
- * instances, one per Local Site and one for the global tokens.
- */
- RB.APITokensView = Backbone.View.extend({
- template: _.template([
- '<div class="api-tokens-list" />'
- ].join('')),
- /*
- * Initializes the view.
- */
- initialize: function(options) {
- this.apiTokens = options.apiTokens;
- this._$listsContainer = null;
- this._apiTokenViews = [];
- },
- /*
- * Renders the view.
- *
- * This will set up the elements and the list of SiteAPITokensViews.
- */
- render: function() {
- this.$el.html(this.template());
- this._$listsContainer = this.$('.api-tokens-list');
- _.each(this.apiTokens, function(info, localSiteName) {
- var view = new SiteAPITokensView({
- localSiteName: localSiteName,
- localSitePrefix: info.localSitePrefix,
- apiTokens: info.tokens
- });
- view.$el.appendTo(this._$listsContainer);
- view.render();
- this._apiTokenViews.push(view);
- }, this);
- return this;
- }
- });
- })();