diff --git a/media/css/seahub.css b/media/css/seahub.css
index ec8ba519a2..f57222a95a 100644
--- a/media/css/seahub.css
+++ b/media/css/seahub.css
@@ -3758,3 +3758,22 @@ img.thumbnail {
#group-members .outer-caret {
right:66px;
}
+#group-settings {
+ position:absolute;
+ top:77px;
+ right:0;
+}
+#group-settings .outer-caret {
+ right:106px;
+}
+.group-setting-list {
+ border-bottom:1px solid #e3e3e5;
+}
+.group-setting-item {
+ line-height:31px;
+ cursor:pointer;
+ margin:5px 0;
+}
+#add-group-members-form .submit {
+ margin:0 0 0 5px;
+}
diff --git a/seahub/templates/js/templates.html b/seahub/templates/js/templates.html
index 14b9ccab59..a7958d6a6b 100644
--- a/seahub/templates/js/templates.html
+++ b/seahub/templates/js/templates.html
@@ -66,7 +66,7 @@
<%= size_formatted %> |
<%= mtime_relative %> |
- <%- owner_nickname %> |
+ <%- owner_name %> |
+
+
+
+
+
+
diff --git a/seahub/templates/libraries.html b/seahub/templates/libraries.html
index 96e0fb9769..86025ac746 100644
--- a/seahub/templates/libraries.html
+++ b/seahub/templates/libraries.html
@@ -159,6 +159,20 @@
+
+
+
+
+
+
{% trans "Settings" %}
+
+
+
+
diff --git a/static/scripts/app/router.js b/static/scripts/app/router.js
index 173c7e9d5f..e6b22bc8b9 100644
--- a/static/scripts/app/router.js
+++ b/static/scripts/app/router.js
@@ -26,6 +26,7 @@ define([
'group/:group_id/': 'showGroupRepos',
'group/:group_id/lib/:repo_id(/*path)': 'showGroupRepoDir',
'group/:group_id/members/': 'showGroupMembers',
+ 'group/:group_id/settings/': 'showGroupSettings',
'org/': 'showOrgRepos',
'org/lib/:repo_id(/*path)': 'showOrgRepoDir',
'common/lib/:repo_id(/*path)': 'showCommonDir',
@@ -184,6 +185,11 @@ define([
this.groupView.showMembers();
},
+ showGroupSettings: function(group_id) {
+ this.showGroupRepos(group_id);
+ this.groupView.showSettings();
+ },
+
showOrgRepos: function() {
this.switchCurrentView(this.orgView);
this.orgView.showRepoList();
diff --git a/static/scripts/app/views/group-manage-members.js b/static/scripts/app/views/group-manage-members.js
new file mode 100644
index 0000000000..bf1b6b1b9e
--- /dev/null
+++ b/static/scripts/app/views/group-manage-members.js
@@ -0,0 +1,165 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/collections/group-members',
+ 'app/views/group-member2'
+], function($, _, Backbone, Common, GroupMembers, ItemView) {
+ 'use strict';
+
+ var View = Backbone.View.extend({
+
+ template: _.template($('#group-manage-members-tmpl').html()),
+
+ initialize: function(options) {
+
+ this.group_id = options.group_id;
+ this.group_name = options.group_name;
+ this.is_owner = options.is_owner;
+
+ this.render();
+ this.$el.modal({
+ appendTo: '#main',
+ focus: false,
+ containerCss: {
+ 'width': 560
+ }
+ });
+ this.$modalContainer = $('#simplemodal-container').css({'height':'auto'});
+
+ this.$('[name="user_name"]').select2($.extend({
+ width: '268px',
+ }, Common.contactInputOptionsForSelect2()));
+
+ this.collection = new GroupMembers();
+ this.listenTo(this.collection, 'add', this.addOne);
+ this.renderMemberList();
+ this.setConMaxHeight();
+
+ this.$loadingTip = this.$('.loading-tip');
+ this.$listContainer = this.$('tbody');
+ this.$listError = this.$('.members .error');
+
+ var _this = this;
+ $(window).resize(function() {
+ _this.setConMaxHeight();
+ });
+ // click other place to hide '.role-edit'
+ $(document).click(function(e) {
+ var target = e.target || event.srcElement;
+ var $el = _this.$('.role-edit:visible');
+ var $td = $el.parent();
+ if ($el.length &&
+ !$el.is(target) &&
+ !$el.find('*').is(target) &&
+ !$td.find('.role-edit-icon').is(target)) {
+ $el.hide();
+ $td.find('.cur-role, .role-edit-icon').show();
+ }
+ });
+ },
+
+ render: function() {
+ var title = gettext("{placeholder} Members").replace('{placeholder}', '' + Common.HTMLescape(this.group_name) + '');
+ this.$el.html(this.template({
+ title: title,
+ is_owner: this.is_owner
+ }));
+
+ return this;
+ },
+
+ events: {
+ 'submit form': 'formSubmit'
+ },
+
+ addOne: function(item, collection, options) {
+ var view = new ItemView({
+ model: item,
+ group_id: this.group_id,
+ is_owner: this.is_owner
+ });
+ if (options.prepend) {
+ this.$listContainer.prepend(view.render().el);
+ } else {
+ this.$listContainer.append(view.render().el);
+ }
+ },
+
+ renderMemberList: function() {
+ var _this = this;
+ this.collection.setGroupId(this.group_id);
+ this.collection.fetch({
+ cache: false,
+ data: {'avatar_size': 40},
+ success: function(collection, response, opts) {
+ _this.$loadingTip.hide();
+ },
+ error: function(collection, response, opts) {
+ _this.$loadingTip.hide();
+ var err_msg;
+ if (response.responseText) {
+ if (response['status'] == 401 || response['status'] == 403) {
+ err_msg = gettext("Permission error");
+ } else {
+ err_msg = gettext("Error");
+ }
+ } else {
+ err_msg = gettext('Please check the network.');
+ }
+ _this.$listError.html(err_msg).show();
+ }
+ });
+ },
+
+ formSubmit: function() {
+ var _this = this;
+ var $input = this.$('[name="user_name"]');
+ var input_val = $.trim($input.val());
+ if (!input_val) {
+ return false;
+ }
+ var input_val_list = input_val.split(',');
+ if (input_val_list.length == 1) {
+ this.collection.create({'email': input_val}, {
+ wait: true,
+ validate: true,
+ prepend: true,
+ success: function() {
+ $input.select2('val', '');
+ },
+ error: function(collection, response, options) {
+ var err_msg;
+ if (response.responseText) {
+ err_msg = response.responseJSON.error_msg;
+ } else {
+ err_msg = gettext('Please check the network.');
+ }
+ _this.$listError.html(err_msg).show();
+ }
+ });
+ } else {
+ // TODO: input_val_list.length > 1
+ }
+
+ return false;
+ },
+
+ setConMaxHeight: function() {
+ var $modalContainer = this.$modalContainer;
+ this.$('.members').css({
+ 'max-height': $(window).height()
+ - parseInt($modalContainer.css('top'))
+ - parseInt($modalContainer.css('padding-top'))
+ - parseInt($modalContainer.css('padding-bottom'))
+ - this.$('h3').outerHeight(true)
+ - this.$('form').outerHeight(true),
+ 'overflow': 'auto'
+ });
+ }
+
+ });
+
+ return View;
+});
diff --git a/static/scripts/app/views/group-member2.js b/static/scripts/app/views/group-member2.js
new file mode 100644
index 0000000000..7b1a42fc07
--- /dev/null
+++ b/static/scripts/app/views/group-member2.js
@@ -0,0 +1,116 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common'
+], function($, _, Backbone, Common) {
+ 'use strict';
+
+ var View = Backbone.View.extend({
+ tagName: 'tr',
+
+ template: _.template($('#group-member2-tmpl').html()),
+
+ events: {
+ 'mouseenter': 'highlight',
+ 'mouseleave': 'rmHighlight',
+ 'click .role-edit-icon': 'showEdit',
+ 'change .role-edit': 'editRole',
+ 'click .rm': 'rmMember'
+ },
+
+ initialize: function(options) {
+ this.group_id = options.group_id;
+ this.is_owner = options.is_owner;
+
+ this.listenTo(this.model, 'change', this.render);
+ },
+
+ render: function() {
+ this.$el.html(this.template($.extend(this.model.attributes, {
+ is_owner: this.is_owner,
+ username: app.pageOptions.username
+ })));
+ return this;
+ },
+
+ highlight: function() {
+ this.$el.addClass('hl').find('.op-icon').removeClass('vh');
+ },
+
+ rmHighlight: function() {
+ this.$el.removeClass('hl').find('.op-icon').addClass('vh');
+ },
+
+ showEdit: function() {
+ this.$('.cur-role, .role-edit-icon').hide();
+ this.$('.role-edit').show();
+ },
+
+ editRole: function() {
+ var _this = this;
+
+ // '0': member, '1': admin
+ var val = this.$('[name="role"]').val();
+ var is_admin = val == 1 ? true : false;
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group_member',
+ 'group_id': this.group_id,
+ 'email': encodeURIComponent(this.model.get('email')),
+ }),
+ type: 'put',
+ dataType: 'json',
+ beforeSend: Common.prepareCSRFToken,
+ data: {
+ 'is_admin': is_admin
+ },
+ success: function() {
+ _this.model.set({
+ 'is_admin': is_admin
+ });
+ },
+ error: function(xhr) {
+ var err_msg;
+ if (xhr.responseText) {
+ err_msg = $.parseJSON(xhr.responseText).error_msg;
+ } else {
+ err_msg = gettext("Failed. Please check the network.");
+ }
+ // improve it?
+ Common.feedback(error_msg, 'error');
+ }
+ });
+ },
+
+ rmMember: function() {
+ var _this = this;
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group_member',
+ 'group_id': this.group_id,
+ 'email': encodeURIComponent(this.model.get('email')),
+ }),
+ type: 'delete',
+ dataType: 'json',
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ _this.remove();
+ },
+ error: function(xhr) {
+ var err_msg;
+ if (xhr.responseText) {
+ err_msg = $.parseJSON(xhr.responseText).error_msg;
+ } else {
+ err_msg = gettext("Failed. Please check the network.");
+ }
+ // improve it?
+ Common.feedback(error_msg, 'error');
+ }
+ });
+ }
+
+ });
+
+ return View;
+});
diff --git a/static/scripts/app/views/group-repo.js b/static/scripts/app/views/group-repo.js
index 63505cd62b..ca34ca8f13 100644
--- a/static/scripts/app/views/group-repo.js
+++ b/static/scripts/app/views/group-repo.js
@@ -28,7 +28,11 @@ define([
var obj = this.model.toJSON();
$.extend(obj, {
group_id: this.group_id,
- is_staff: this.is_staff
+ is_staff: this.is_staff,
+ // for '#groups' (no 'share_from_me')
+ share_from_me: app.pageOptions.username == this.model.get('owner') ? true : false,
+ // 'owner_name' for '#groups', 'owner_nickname' for '#group/id/'
+ owner_name: this.model.get('owner_nickname') || this.model.get('owner_name')
});
this.$el.html(this.template(obj));
return this;
diff --git a/static/scripts/app/views/group-settings.js b/static/scripts/app/views/group-settings.js
new file mode 100644
index 0000000000..78ec738a12
--- /dev/null
+++ b/static/scripts/app/views/group-settings.js
@@ -0,0 +1,370 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/views/group-manage-members'
+], function($, _, Backbone, Common, ManageMembersView) {
+ 'use strict';
+
+ var View = Backbone.View.extend({
+ el: '#group-settings',
+
+ template: _.template($('#group-settings-tmpl').html()),
+ renameTemplate: _.template($('#group-rename-form-tmpl').html()),
+ transferTemplate: _.template($('#group-transfer-form-tmpl').html()),
+ importMembersTemplate: _.template($('#group-import-members-form-tmpl').html()),
+
+ initialize: function(options) {
+ // group basic info
+ this.group = {};
+
+ this.$loadingTip = this.$('.loading-tip');
+ this.$listContainer = $('#group-setting-con');
+ this.$error = this.$('.error');
+
+ var _this = this;
+ $(window).resize(function() {
+ _this.setConMaxHeight();
+ });
+ $(document).click(function(e) {
+ var target = e.target || event.srcElement;
+ var $popup = _this.$el,
+ $popup_switch = $('#group-settings-icon');
+
+ if ($('#group-settings:visible').length &&
+ !$popup.is(target) &&
+ !$popup.find('*').is(target) &&
+ !$popup_switch.is(target)) {
+ _this.hide();
+ }
+ });
+ },
+
+ events: {
+ 'click .close': 'hide',
+ 'mouseenter .group-setting-item': 'highlightItem',
+ 'mouseleave .group-setting-item': 'rmHighlightItem',
+ 'click .group-setting-item': 'manageGroup'
+ },
+
+ render: function() {
+ this.$error.hide();
+ this.$listContainer.hide();
+ this.$loadingTip.show();
+
+ // the user's role in this group
+ this.is_owner = false,
+ this.is_admin = false;
+
+ var _this = this;
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group',
+ 'group_id': this.group.id
+ }),
+ cache: false,
+ dataType: 'json',
+ success: function (data) {
+ _this.group = data; // {id, name, owner, created_at, avatar_url, admins}
+
+ var username = app.pageOptions.username;
+ if (username == _this.group.owner) {
+ _this.is_owner = true;
+ } else if ($.inArray(username, _this.group.admins) != -1) {
+ _this.is_admin = true;
+ }
+ _this.$listContainer.html(_this.template({
+ 'is_owner': _this.is_owner,
+ 'is_admin': _this.is_admin
+ })).show();
+ },
+ error: function(xhr) {
+ var err_msg;
+ if (xhr.responseText) {
+ err_msg = gettext('Error');
+ } else {
+ err_msg = gettext("Please check the network.");
+ }
+ _this.$error.html(err_msg).show();
+ },
+ complete: function() {
+ _this.$loadingTip.hide();
+ }
+ });
+ },
+
+ // set max-height for '.popover-con'
+ setConMaxHeight: function() {
+ this.$('.popover-con').css({'max-height': $(window).height() - this.$el.offset().top - this.$('.popover-hd').outerHeight(true) - 2}); // 2: top, bottom border width of $el
+ },
+
+ show: function(options) {
+ this.group.id = options.group_id;
+ this.$el.show();
+ this.setConMaxHeight();
+ this.render();
+ app.router.navigate('group/' + this.group.id + '/settings/');
+ },
+
+ hide: function() {
+ this.$el.hide();
+ app.router.navigate('group/' + this.group.id + '/');
+ },
+
+ highlightItem: function(e) {
+ $(e.currentTarget).addClass('hl');
+ },
+
+ rmHighlightItem: function(e) {
+ $(e.currentTarget).removeClass('hl');
+ },
+
+ manageGroup: function(e) {
+ switch($(e.currentTarget).data('op')) {
+ case 'rename':
+ this.rename();
+ break;
+ case 'transfer':
+ this.transfer();
+ break;
+ case 'import-members':
+ this.importMembers();
+ break;
+ case 'manage-members':
+ this.manageMembers();
+ break;
+ case 'dismiss':
+ this.dismiss();
+ break;
+ case 'leave':
+ this.leave();
+ break;
+ }
+ },
+
+ rename: function() {
+ var _this = this;
+
+ var $form = $(this.renameTemplate());
+ $form.modal({focus:false});
+ $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
+
+ $form.submit(function() {
+ var new_name = $.trim($('[name="new_name"]', $(this)).val());
+ if (!new_name || new_name == _this.group.name) {
+ return false;
+ }
+ var $submitBtn = $('[type="submit"]', $(this));
+ Common.disableButton($submitBtn);
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group',
+ 'group_id': _this.group.id
+ }),
+ type: 'put',
+ dataType: 'json',
+ beforeSend: Common.prepareCSRFToken,
+ data: {
+ 'name': new_name
+ },
+ success: function() {
+ $.modal.close();
+ // TODO: improve
+ //app.router.navigate('group/' + _this.group_id + '/', {trigger: true, replace: true});
+ location.reload(true);
+ },
+ error: function(xhr) {
+ var error_msg;
+ if (xhr.responseText) {
+ error_msg = $.parseJSON(xhr.responseText).error_msg;
+ } else {
+ error_msg = gettext("Failed. Please check the network.");
+ }
+ $('.error', $form).html(error_msg).show();
+ Common.enableButton($submitBtn);
+ }
+ });
+ return false;
+ });
+ },
+
+ transfer: function() {
+ var _this = this;
+
+ var $form = $(this.transferTemplate());
+ $form.modal({focus:false});
+ $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
+
+ $('[name="email"]', $form).select2($.extend(
+ Common.contactInputOptionsForSelect2(), {
+ width: '268px',
+ maximumSelectionSize: 1,
+ placeholder: gettext("Search user or enter email"), // to override 'placeholder' returned by `Common.conta...`
+ formatSelectionTooBig: gettext("You cannot select any more choices")
+ }));
+
+ $form.submit(function() {
+ var email = $.trim($('[name="email"]', $(this)).val());
+ if (!email) {
+ return false;
+ }
+ if (email == _this.group.owner) {
+ return false;
+ }
+
+ var $submitBtn = $('[type="submit"]', $(this));
+ Common.disableButton($submitBtn);
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group',
+ 'group_id': _this.group.id
+ }),
+ type: 'put',
+ dataType: 'json',
+ beforeSend: Common.prepareCSRFToken,
+ data: {
+ 'owner': email
+ },
+ success: function() {
+ // after the transfer, the former owner becomes a common admin of the group.
+ $.modal.close();
+ },
+ error: function(xhr) {
+ var error_msg;
+ if (xhr.responseText) {
+ error_msg = $.parseJSON(xhr.responseText).error_msg;
+ } else {
+ error_msg = gettext("Failed. Please check the network.");
+ }
+ $('.error', $form).html(error_msg).show();
+ Common.enableButton($submitBtn);
+ }
+ });
+ return false;
+ });
+ },
+
+ // TODO: finish it after the backend py is done.
+ importMembers: function() {
+ var _this = this;
+ var $form = $(this.importMembersTemplate());
+ $form.modal({focus:false});
+ $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
+
+ $form.submit(function() {
+ var $fileInput = $('[name=file]', $form)[0];
+ if (!$fileInput.files.length) {
+ $('.error', $form).removeClass('hide');
+ return false;
+ }
+
+ var $submitBtn = $('[type="submit"]', $(this));
+ Common.disableButton($submitBtn);
+
+ var file = $fileInput.files[0];
+ var formData = new FormData();
+ formData.append('file', file);
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group_import_members',
+ 'group_id': _this.group.id
+ }),
+ type: 'post',
+ dataType: 'json',
+ data: formData,
+ processData: false, // tell jQuery not to process the data
+ contentType: false, // tell jQuery not to set contentType
+ beforeSend: Common.prepareCSRFToken,
+ success: function(data) {
+ },
+ error: function () {
+ }
+ });
+ return false;
+ });
+ },
+
+ manageMembers: function() {
+ new ManageMembersView({
+ 'group_id': this.group.id,
+ 'group_name': this.group.name,
+ 'is_owner': this.is_owner
+ });
+ },
+
+ dismiss: function() {
+ var _this = this;
+ var title = gettext('Dismiss Group');
+ var content = gettext('Really want to dismiss this group?');
+ var yesCallback = function () {
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group',
+ 'group_id': _this.group.id
+ }),
+ type: 'delete',
+ dataType: 'json',
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ app.router.navigate('groups/', {trigger: true});
+ // TO update side nav - 'group list'
+ location.reload(true); // improve it ??
+ },
+ error: function(xhr) {
+ var error_msg;
+ if (xhr.responseText) {
+ error_msg = $.parseJSON(xhr.responseText).error_msg;
+ } else {
+ error_msg = gettext("Failed. Please check the network.");
+ }
+ Common.feedback(error_msg, 'error');
+ },
+ complete: function() {
+ $.modal.close();
+ }
+ });
+ };
+ Common.showConfirm(title, content, yesCallback);
+ },
+
+ leave: function() {
+ var _this = this;
+ var title = gettext('Quit Group');
+ var content = gettext('Are you sure you want to quit this group?');
+ var yesCallback = function () {
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'group_member',
+ 'group_id': _this.group.id,
+ 'email': encodeURIComponent(app.pageOptions.username),
+ }),
+ type: 'delete',
+ dataType: 'json',
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ app.router.navigate('groups/', {trigger: true});
+ // TO update side nav - 'group list'
+ location.reload(true); // improve it ??
+ },
+ error: function(xhr) {
+ var err_msg;
+ if (xhr.responseText) {
+ err_msg = $.parseJSON(xhr.responseText).error_msg;
+ } else {
+ err_msg = gettext("Failed. Please check the network.");
+ }
+ Common.feedback(error_msg, 'error');
+ },
+ complete: function() {
+ $.modal.close();
+ }
+ });
+ };
+ Common.showConfirm(title, content, yesCallback);
+ }
+
+ });
+
+ return View;
+});
diff --git a/static/scripts/app/views/group.js b/static/scripts/app/views/group.js
index 018c4f4fce..c1c65595be 100644
--- a/static/scripts/app/views/group.js
+++ b/static/scripts/app/views/group.js
@@ -6,9 +6,10 @@ define([
'app/collections/group-repos',
'app/views/group-repo',
'app/views/add-group-repo',
- 'app/views/group-members'
+ 'app/views/group-members',
+ 'app/views/group-settings'
], function($, _, Backbone, Common, GroupRepos, GroupRepoView,
- AddGroupRepoView, GroupMembersView) {
+ AddGroupRepoView, GroupMembersView, GroupSettingsView) {
'use strict';
var GroupView = Backbone.View.extend({
@@ -18,6 +19,7 @@ define([
reposHdTemplate: _.template($('#shared-repos-hd-tmpl').html()),
events: {
+ 'click #group-settings-icon': 'toggleSettingsPanel',
'click #group-members-icon': 'toggleMembersPanel',
'click .repo-create': 'createRepo',
'click .by-name': 'sortByName',
@@ -39,6 +41,7 @@ define([
this.dirView = options.dirView;
this.membersView = new GroupMembersView();
+ this.settingsView = new GroupSettingsView();
},
addOne: function(repo, collection, options) {
@@ -190,6 +193,21 @@ define([
this.$emptyTip.hide();
},
+ showSettings: function() {
+ this.settingsView.show({
+ 'group_id': this.group_id
+ });
+ },
+
+ toggleSettingsPanel: function() {
+ var panel_id = this.settingsView.el.id;
+ if ($('#' + panel_id + ':visible').length) { // the panel is shown
+ this.settingsView.hide();
+ } else {
+ this.showSettings();
+ }
+ },
+
showMembers: function() {
this.membersView.show({'group_id': this.group_id});
},
diff --git a/static/scripts/common.js b/static/scripts/common.js
index e8bc324224..6e3685e2d4 100644
--- a/static/scripts/common.js
+++ b/static/scripts/common.js
@@ -103,8 +103,10 @@ define([
case 'set_notice_seen_by_id': return siteRoot + 'ajax/set_notice_seen_by_id/';
case 'repo_set_password': return siteRoot + 'repo/set_password/';
case 'groups': return siteRoot + 'api/v2.1/groups/';
- case 'group_repos': return siteRoot + 'api2/groups/' + options.group_id + '/repos/';
+ case 'group': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/';
case 'group_members': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/members/';
+ case 'group_member': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/members/' + options.email + '/';
+ case 'group_repos': return siteRoot + 'api2/groups/' + options.group_id + '/repos/';
case 'group_basic_info': return siteRoot + 'ajax/group/' + options.group_id + '/basic-info/';
case 'toggle_group_modules': return siteRoot + 'ajax/group/' + options.group_id + '/toggle-modules/';
case 'toggle_personal_modules': return siteRoot + 'ajax/toggle-personal-modules/';