diff options
Diffstat (limited to 'server/sonar-web/src/main/js/apps/groups')
22 files changed, 564 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/groups/app.js b/server/sonar-web/src/main/js/apps/groups/app.js new file mode 100644 index 00000000000..55c6dfef534 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/app.js @@ -0,0 +1,47 @@ +define([ + './layout', + './groups', + './header-view', + './search-view', + './list-view', + './list-footer-view' +], function (Layout, Groups, HeaderView, SearchView, ListView, ListFooterView) { + + var App = new Marionette.Application(), + init = function (options) { + // Layout + this.layout = new Layout({ el: options.el }); + this.layout.render(); + + // Collection + this.groups = new Groups(); + + // Header View + this.headerView = new HeaderView({ collection: this.groups }); + this.layout.headerRegion.show(this.headerView); + + // Search View + this.searchView = new SearchView({ collection: this.groups }); + this.layout.searchRegion.show(this.searchView); + + // List View + this.listView = new ListView({ collection: this.groups }); + this.layout.listRegion.show(this.listView); + + // List Footer View + this.listFooterView = new ListFooterView({ collection: this.groups }); + this.layout.listFooterRegion.show(this.listFooterView); + + // Go! + this.groups.fetch(); + }; + + App.on('start', function (options) { + window.requestMessages().done(function () { + init.call(App, options); + }); + }); + + return App; + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/create-view.js b/server/sonar-web/src/main/js/apps/groups/create-view.js new file mode 100644 index 00000000000..8d5cfce55aa --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/create-view.js @@ -0,0 +1,30 @@ +define([ + './group', + './form-view' +], function (Group, FormView) { + + return FormView.extend({ + + sendRequest: function () { + var that = this, + group = new Group({ + name: this.$('#create-group-name').val(), + description: this.$('#create-group-description').val() + }); + this.disableForm(); + return group.save(null, { + statusCode: { + // do not show global error + 400: null + } + }).done(function () { + that.collection.refresh(); + that.close(); + }).fail(function (jqXHR) { + that.enableForm(); + that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/delete-view.js b/server/sonar-web/src/main/js/apps/groups/delete-view.js new file mode 100644 index 00000000000..8fd83d34031 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/delete-view.js @@ -0,0 +1,32 @@ +define([ + 'components/common/modal-form', + './templates' +], function (ModalForm) { + + return ModalForm.extend({ + template: Templates['groups-delete'], + + onFormSubmit: function () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + this.sendRequest(); + }, + + sendRequest: function () { + var that = this, + collection = this.model.collection; + return this.model.destroy({ + wait: true, + statusCode: { + // do not show global error + 400: null + } + }).done(function () { + collection.total--; + that.close(); + }).fail(function (jqXHR) { + that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/form-view.js b/server/sonar-web/src/main/js/apps/groups/form-view.js new file mode 100644 index 00000000000..aa872784e1e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/form-view.js @@ -0,0 +1,25 @@ +define([ + 'components/common/modal-form', + './templates' +], function (ModalForm) { + + return ModalForm.extend({ + template: Templates['groups-form'], + + onRender: function () { + ModalForm.prototype.onRender.apply(this, arguments); + this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' }); + }, + + onClose: function () { + ModalForm.prototype.onClose.apply(this, arguments); + this.$('[data-toggle="tooltip"]').tooltip('destroy'); + }, + + onFormSubmit: function () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + this.sendRequest(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/group.js b/server/sonar-web/src/main/js/apps/groups/group.js new file mode 100644 index 00000000000..406f9ba3a3a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/group.js @@ -0,0 +1,35 @@ +define(function () { + + return Backbone.Model.extend({ + urlRoot: function () { + return baseUrl + '/api/usergroups'; + }, + + sync: function (method, model, options) { + var opts = options || {}; + if (method === 'create') { + _.defaults(opts, { + url: this.urlRoot() + '/create', + type: 'POST', + data: _.pick(model.toJSON(), 'name', 'description') + }); + } + if (method === 'update') { + _.defaults(opts, { + url: this.urlRoot() + '/update', + type: 'POST', + data: _.pick(model.toJSON(), 'id', 'name', 'description') + }); + } + if (method === 'delete') { + _.defaults(opts, { + url: this.urlRoot() + '/delete', + type: 'POST', + data: { id: this.id } + }); + } + return Backbone.ajax(opts); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/groups.js b/server/sonar-web/src/main/js/apps/groups/groups.js new file mode 100644 index 00000000000..9ddd6c9f9a1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/groups.js @@ -0,0 +1,40 @@ +define([ + './group' +], function (Group) { + + return Backbone.Collection.extend({ + model: Group, + + url: function () { + return baseUrl + '/api/usergroups/search'; + }, + + parse: function (r) { + this.total = +r.total; + this.p = +r.p; + this.ps = +r.ps; + return r.groups; + }, + + fetch: function (options) { + var d = (options && options.data) || {}; + this.q = d.q; + return Backbone.Collection.prototype.fetch.apply(this, arguments); + }, + + fetchMore: function () { + var p = this.p + 1; + return this.fetch({ add: true, remove: false, data: { p: p, ps: this.ps, q: this.q } }); + }, + + refresh: function () { + return this.fetch({ reset: true, data: { q: this.q } }); + }, + + hasMore: function () { + return this.total > this.p * this.ps; + } + + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/header-view.js b/server/sonar-web/src/main/js/apps/groups/header-view.js new file mode 100644 index 00000000000..da6f7f60919 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/header-view.js @@ -0,0 +1,25 @@ +define([ + './create-view', + './templates' +], function (CreateView) { + + return Marionette.ItemView.extend({ + template: Templates['groups-header'], + + events: { + 'click #groups-create': 'onCreateClick' + }, + + onCreateClick: function (e) { + e.preventDefault(); + this.createGroup(); + }, + + createGroup: function () { + new CreateView({ + collection: this.collection + }).render(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/layout.js b/server/sonar-web/src/main/js/apps/groups/layout.js new file mode 100644 index 00000000000..a60fb06f35f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/layout.js @@ -0,0 +1,16 @@ +define([ + './templates' +], function () { + + return Marionette.Layout.extend({ + template: Templates['groups-layout'], + + regions: { + headerRegion: '#groups-header', + searchRegion: '#groups-search', + listRegion: '#groups-list', + listFooterRegion: '#groups-list-footer' + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/list-footer-view.js b/server/sonar-web/src/main/js/apps/groups/list-footer-view.js new file mode 100644 index 00000000000..cdad034f24a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/list-footer-view.js @@ -0,0 +1,34 @@ +define([ + './templates' +], function () { + + return Marionette.ItemView.extend({ + template: Templates['groups-list-footer'], + + collectionEvents: { + 'all': 'render' + }, + + events: { + 'click #groups-fetch-more': 'onMoreClick' + }, + + onMoreClick: function (e) { + e.preventDefault(); + this.fetchMore(); + }, + + fetchMore: function () { + this.collection.fetchMore(); + }, + + serializeData: function () { + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + total: this.collection.total, + count: this.collection.length, + more: this.collection.hasMore() + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/list-item-view.js b/server/sonar-web/src/main/js/apps/groups/list-item-view.js new file mode 100644 index 00000000000..43eaa5b0d24 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/list-item-view.js @@ -0,0 +1,59 @@ +define([ + './update-view', + './delete-view', + './users-view', + './templates' +], function (UpdateView, DeleteView, UsersView) { + + return Marionette.ItemView.extend({ + tagName: 'li', + className: 'panel panel-vertical', + template: Templates['groups-list-item'], + + events: { + 'click .js-group-update': 'onUpdateClick', + 'click .js-group-delete': 'onDeleteClick', + 'click .js-group-users': 'onUsersClick' + }, + + onRender: function () { + this.$el.attr('data-id', this.model.id); + this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' }); + }, + + onClose: function () { + this.$('[data-toggle="tooltip"]').tooltip('destroy'); + }, + + onUpdateClick: function (e) { + e.preventDefault(); + this.updateGroup(); + }, + + onDeleteClick: function (e) { + e.preventDefault(); + this.deleteGroup(); + }, + + onUsersClick: function (e) { + e.preventDefault(); + this.showUsers(); + }, + + updateGroup: function () { + new UpdateView({ + model: this.model, + collection: this.model.collection + }).render(); + }, + + deleteGroup: function () { + new DeleteView({ model: this.model }).render(); + }, + + showUsers: function () { + new UsersView({ model: this.model }).render(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/list-view.js b/server/sonar-web/src/main/js/apps/groups/list-view.js new file mode 100644 index 00000000000..138c36b7619 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/list-view.js @@ -0,0 +1,11 @@ +define([ + './list-item-view', + './templates' +], function (ListItemView) { + + return Marionette.CollectionView.extend({ + tagName: 'ul', + itemView: ListItemView + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/search-view.js b/server/sonar-web/src/main/js/apps/groups/search-view.js new file mode 100644 index 00000000000..1540d7eb36e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/search-view.js @@ -0,0 +1,49 @@ +define([ + './templates' +], function () { + + return Marionette.ItemView.extend({ + template: Templates['groups-search'], + + events: { + 'submit #groups-search-form': 'onFormSubmit', + 'search #groups-search-query': 'debouncedOnKeyUp', + 'keyup #groups-search-query': 'debouncedOnKeyUp' + }, + + initialize: function () { + this._bufferedValue = null; + this.debouncedOnKeyUp = _.debounce(this.onKeyUp, 400); + }, + + onRender: function () { + this.delegateEvents(); + }, + + onFormSubmit: function (e) { + e.preventDefault(); + this.debouncedOnKeyUp(); + }, + + onKeyUp: function () { + var q = this.getQuery(); + if (q === this._bufferedValue) { + return; + } + this._bufferedValue = this.getQuery(); + if (this.searchRequest != null) { + this.searchRequest.abort(); + } + this.searchRequest = this.search(q); + }, + + getQuery: function () { + return this.$('#groups-search-query').val(); + }, + + search: function (q) { + return this.collection.fetch({ reset: true, data: { q: q } }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-delete.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-delete.hbs new file mode 100644 index 00000000000..0644817633e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-delete.hbs @@ -0,0 +1,13 @@ +<form id="delete-group-form" autocomplete="off"> + <div class="modal-head"> + <h2>Delete Group</h2> + </div> + <div class="modal-body"> + <div class="js-modal-messages"></div> + Are you sure you want to delete "{{name}}"? + </div> + <div class="modal-foot"> + <button id="delete-group-submit">Delete</button> + <a href="#" class="js-modal-close" id="delete-group-cancel">Cancel</a> + </div> +</form> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-form.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-form.hbs new file mode 100644 index 00000000000..a0927b33a73 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-form.hbs @@ -0,0 +1,24 @@ +<form id="create-group-form" autocomplete="off"> + <div class="modal-head"> + <h2>{{#if id}}Update{{else}}Create{{/if}} Group</h2> + </div> + <div class="modal-body"> + <div class="js-modal-messages"></div> + <div class="modal-field"> + <label for="create-group-name">Name<em class="mandatory">*</em></label> + {{! keep this fake field to hack browser autofill }} + <input id="create-group-name-fake" name="name-fake" type="text" class="hidden"> + <input id="create-group-name" name="name" type="text" size="50" maxlength="255" required value="{{name}}"> + </div> + <div class="modal-field"> + <label for="create-group-description">Description</label> + {{! keep this fake field to hack browser autofill }} + <textarea id="create-group-description-fake" name="description-fake" class="hidden"></textarea> + <textarea id="create-group-description" name="description">{{description}}</textarea> + </div> + </div> + <div class="modal-foot"> + <button id="create-group-submit">{{#if id}}Update{{else}}Create{{/if}}</button> + <a href="#" class="js-modal-close" id="create-group-cancel">Cancel</a> + </div> +</form> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-header.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-header.hbs new file mode 100644 index 00000000000..19ba74febf8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-header.hbs @@ -0,0 +1,9 @@ +<header class="page-header"> + <h1 class="page-title">{{t 'user_groups.page'}}</h1> + <div class="page-actions"> + <div class="button-group"> + <button id="groups-create">Create Group</button> + </div> + </div> + <p class="page-description">{{t 'user_groups.page.description'}}</p> +</header> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-layout.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-layout.hbs new file mode 100644 index 00000000000..4cad08c767e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-layout.hbs @@ -0,0 +1,6 @@ +<div class="page"> + <div id="groups-header"></div> + <div id="groups-search"></div> + <div id="groups-list"></div> + <div id="groups-list-footer"></div> +</div> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-list-footer.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-list-footer.hbs new file mode 100644 index 00000000000..841ab40ecd9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-list-footer.hbs @@ -0,0 +1,6 @@ +<footer class="spacer-top note text-center"> + {{count}}/{{total}} shown + {{#if more}} + <a id="groups-fetch-more" class="spacer-left" href="#">show more</a> + {{/if}} +</footer> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-list-item.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-list-item.hbs new file mode 100644 index 00000000000..611cc382493 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-list-item.hbs @@ -0,0 +1,16 @@ +<div class="pull-right big-spacer-left nowrap"> + <a class="js-group-update icon-edit little-spacer-right" title="Update Details" data-toggle="tooltip" href="#"></a> + <a class="js-group-delete icon-delete" title="Deactivate" data-toggle="tooltip" href="#"></a> +</div> + +<div class="display-inline-block text-top width-20"> + <strong class="js-group-name">{{name}}</strong> +</div> + +<div class="display-inline-block text-top big-spacer-left width-10"> + Members: <a class="js-group-users" href="#">{{membersCount}}</a> +</div> + +<div class="display-inline-block text-top big-spacer-left width-50"> + <span class="js-group-description">{{description}}</span> +</div> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs new file mode 100644 index 00000000000..5e81ec0b32a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs @@ -0,0 +1,6 @@ +<div class="panel panel-vertical bordered-bottom spacer-bottom"> + <form id="groups-search-form" class="search-box"> + <button id="groups-search-submit" class="search-box-submit button-clean"><i class="icon-search"></i></button> + <input id="groups-search-query" class="search-box-input" type="search" name="q" placeholder="Search" maxlength="100"> + </form> +</div> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-users.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-users.hbs new file mode 100644 index 00000000000..eb346c0e31b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-users.hbs @@ -0,0 +1,10 @@ +<div class="modal-head"> + <h2>Update users</h2> +</div> +<div class="modal-body"> + <div class="js-modal-messages"></div> + <div id="groups-users"></div> +</div> +<div class="modal-foot"> + <a href="#" class="js-modal-close" id="groups-users-done">Done</a> +</div> diff --git a/server/sonar-web/src/main/js/apps/groups/update-view.js b/server/sonar-web/src/main/js/apps/groups/update-view.js new file mode 100644 index 00000000000..71383a1793d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/update-view.js @@ -0,0 +1,29 @@ +define([ + './form-view' +], function (FormView) { + + return FormView.extend({ + + sendRequest: function () { + var that = this; + this.model.set({ + name: this.$('#create-group-name').val(), + description: this.$('#create-group-description').val() + }); + this.disableForm(); + return this.model.save(null, { + statusCode: { + // do not show global error + 400: null + } + }).done(function () { + that.collection.refresh(); + that.close(); + }).fail(function (jqXHR) { + that.enableForm(); + that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/groups/users-view.js b/server/sonar-web/src/main/js/apps/groups/users-view.js new file mode 100644 index 00000000000..bd236b67040 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/users-view.js @@ -0,0 +1,42 @@ +define([ + 'components/common/modals', + 'components/common/select-list', + './templates' +], function (Modal) { + + return Modal.extend({ + template: Templates['groups-users'], + + onRender: function () { + Modal.prototype.onRender.apply(this, arguments); + new window.SelectList({ + el: this.$('#groups-users'), + width: '100%', + readOnly: false, + focusSearch: false, + format: function (item) { + return item.name + '<br><span class="note">' + item.login + '</span>'; + }, + queryParam: 'q', + searchUrl: baseUrl + '/api/usergroups/users?ps=100&id=' + this.model.id, + selectUrl: baseUrl + '/api/usergroups/add_user', + deselectUrl: baseUrl + '/api/usergroups/remove_user', + extra: { + groupId: this.model.id + }, + selectParameter: 'userLogin', + selectParameterValue: 'login', + parse: function (r) { + this.more = false; + return r.users; + } + }); + }, + + onClose: function () { + this.model.collection.refresh(); + Modal.prototype.onClose.apply(this, arguments); + } + }); + +}); |