From f0a60418e4006f1d7a6e93f4756bb4650d7e2145 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Sat, 23 Dec 2017 12:01:57 +0800 Subject: [PATCH 1/6] [address book] Add admin api --- .../endpoints/admin/address_book/__init__.py | 0 .../endpoints/admin/address_book/group.py | 133 ++++++++++++++++++ .../endpoints/admin/address_book/groups.py | 110 +++++++++++++++ seahub/urls.py | 6 + .../admin/address_book/test_group.py | 66 +++++++++ .../admin/address_book/test_groups.py | 61 ++++++++ 6 files changed, 376 insertions(+) create mode 100644 seahub/api2/endpoints/admin/address_book/__init__.py create mode 100644 seahub/api2/endpoints/admin/address_book/group.py create mode 100644 seahub/api2/endpoints/admin/address_book/groups.py create mode 100644 tests/api/endpoints/admin/address_book/test_group.py create mode 100644 tests/api/endpoints/admin/address_book/test_groups.py diff --git a/seahub/api2/endpoints/admin/address_book/__init__.py b/seahub/api2/endpoints/admin/address_book/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/seahub/api2/endpoints/admin/address_book/group.py b/seahub/api2/endpoints/admin/address_book/group.py new file mode 100644 index 0000000000..f56d90a3ed --- /dev/null +++ b/seahub/api2/endpoints/admin/address_book/group.py @@ -0,0 +1,133 @@ +import logging + +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAdminUser +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status + +from django.utils.translation import ugettext as _ + +from seaserv import seafile_api, ccnet_api +from pysearpc import SearpcError + +from seahub.api2.utils import to_python_boolean +from seahub.avatar.settings import AVATAR_DEFAULT_SIZE +from seahub.base.accounts import User +from seahub.utils import is_valid_username +from seahub.utils.timeutils import timestamp_to_isoformat_timestr +from seahub.group.utils import get_group_member_info +from seahub.group.utils import is_group_member, is_group_admin, \ + validate_group_name, check_group_name_conflict +from seahub.admin_log.signals import admin_operation +from seahub.admin_log.models import GROUP_CREATE, GROUP_DELETE, GROUP_TRANSFER +from seahub.api2.utils import api_error +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.authentication import TokenAuthentication + +logger = logging.getLogger(__name__) + +def address_book_group_to_dict(group): + if isinstance(group, int): + group = ccnet_api.get_group(group) + + return { + "id": group.id, + "name": group.group_name, + "owner": group.creator_name, + "created_at": timestamp_to_isoformat_timestr(group.timestamp), + "parent_group_id": group.parent_group_id, + } + +class AdminAddressBookGroup(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def get(self, request, group_id): + """List child groups and members in an address book group.""" + group_id = int(group_id) + + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %d not found.' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + avatar_size = int(request.GET.get('avatar_size', + AVATAR_DEFAULT_SIZE)) + except ValueError: + avatar_size = AVATAR_DEFAULT_SIZE + + try: + return_ancestors = to_python_boolean(request.GET.get( + 'return_ancestors', 'f')) + except ValueError: + return_ancestors = False + + ret_dict = address_book_group_to_dict(group) + ret_groups = [] + ret_members = [] + + groups = ccnet_api.get_child_groups(group_id) + for group in groups: + ret_groups.append(address_book_group_to_dict(group)) + + try: + members = ccnet_api.get_group_members(group_id) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + for m in members: + member_info = get_group_member_info(request, group_id, m.user_name, + avatar_size) + ret_members.append(member_info) + + ret_dict['groups'] = ret_groups + ret_dict['members'] = ret_members + + if return_ancestors: + # get ancestor groups and remove last group which is self + ancestor_groups = ccnet_api.get_ancestor_groups(group_id)[:-1] + ret_dict['ancestor_groups'] = [address_book_group_to_dict(grp) + for grp in ancestor_groups] + else: + ret_dict['ancestor_groups'] = [] + + return Response(ret_dict) + + def delete(self, request, group_id): + """Dismiss a specific group.""" + group_id = int(group_id) + + group = ccnet_api.get_group(group_id) + if not group: + return Response({'success': True}) + + group_owner = group.creator_name + group_name = group.group_name + + try: + ret_code = ccnet_api.remove_group(group_id) + if ret_code == -1: + error_msg = 'Failed to remove: this group has child groups.' + return api_error(status.HTTP_400_BAD_REQUEST, + error_msg) + + seafile_api.remove_group_repos(group_id) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + # send admin operation log signal + admin_op_detail = { + "id": group_id, + "name": group_name, + "owner": group_owner, + } + admin_operation.send(sender=None, admin_name=request.user.username, + operation=GROUP_DELETE, detail=admin_op_detail) + + return Response({'success': True}) diff --git a/seahub/api2/endpoints/admin/address_book/groups.py b/seahub/api2/endpoints/admin/address_book/groups.py new file mode 100644 index 0000000000..33c8b5ced3 --- /dev/null +++ b/seahub/api2/endpoints/admin/address_book/groups.py @@ -0,0 +1,110 @@ +import logging + +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAdminUser +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status + +from django.utils.translation import ugettext as _ + +from seaserv import seafile_api, ccnet_api +from pysearpc import SearpcError + +from seahub.base.accounts import User +from seahub.utils import is_valid_username +from seahub.utils.timeutils import timestamp_to_isoformat_timestr +from seahub.group.utils import is_group_member, is_group_admin, \ + validate_group_name, check_group_name_conflict +from seahub.admin_log.signals import admin_operation +from seahub.admin_log.models import GROUP_CREATE, GROUP_DELETE, GROUP_TRANSFER +from seahub.api2.utils import api_error +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.authentication import TokenAuthentication + +logger = logging.getLogger(__name__) + +def address_book_group_to_dict(group): + if isinstance(group, int): + group = ccnet_api.get_group(group) + + return { + "id": group.id, + "name": group.group_name, + "owner": group.creator_name, + "created_at": timestamp_to_isoformat_timestr(group.timestamp), + "parent_group_id": group.parent_group_id, + } + +class AdminAddressBookGroups(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def get(self, request): + """List top groups in address book.""" + return_results = [] + + groups = ccnet_api.get_top_groups() + for group in groups: + return_results.append(address_book_group_to_dict(group)) + + return Response({"data": return_results}) + + def post(self, request): + """Add a group in address book. + + parent_group: -1 - no parent group; + > 0 - have parent group. + group_owner: default to system admin + group_staff: default to system admin + """ + group_name = request.data.get('group_name', '').strip() + if not group_name: + error_msg = 'group_name %s invalid.' % group_name + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # Check whether group name is validate. + if not validate_group_name(group_name): + error_msg = _(u'Group name can only contain letters, numbers, blank, hyphen or underscore') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # Check whether group name is duplicated. + if check_group_name_conflict(request, group_name): + error_msg = _(u'There is already a group with that name.') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + group_owner = request.data.get('group_owner', '') + if group_owner: + try: + User.objects.get(email=group_owner) + except User.DoesNotExist: + error_msg = 'User %s not found.' % group_owner + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + else: + group_owner = request.user.username + + try: + parent_group = int(request.data.get('parent_group', -1)) + except ValueError: + error_msg = 'parent_group invalid' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if parent_group < 0 and parent_group != -1: + error_msg = 'parent_group invalid' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # TODO: check parent group exists + + try: + group_id = ccnet_api.create_group(group_name, group_owner, + parent_group_id=parent_group) + except SearpcError as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + # get info of new group + group_info = address_book_group_to_dict(group_id) + + return Response(group_info, status=status.HTTP_200_OK) diff --git a/seahub/urls.py b/seahub/urls.py index 3c1aad739c..2fba156293 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -91,6 +91,8 @@ from seahub.api2.endpoints.admin.invitations import InvitationsView as AdminInvi from seahub.api2.endpoints.admin.library_history import AdminLibraryHistoryLimit from seahub.api2.endpoints.admin.login_bg_image import AdminLoginBgImage from seahub.api2.endpoints.admin.admin_role import AdminAdminRole +from seahub.api2.endpoints.admin.address_book.groups import AdminAddressBookGroups +from seahub.api2.endpoints.admin.address_book.group import AdminAddressBookGroup # Uncomment the next two lines to enable the admin: #from django.contrib import admin @@ -384,6 +386,10 @@ urlpatterns = patterns( url(r'^invite/', include('seahub.invitations.urls', app_name='invitations', namespace='invitations')), url(r'^terms/', include('termsandconditions.urls')), + ## admin::address book + url(r'^api/v2.1/admin/address-book/groups/$', AdminAddressBookGroups.as_view(), name='api-v2.1-admin-address-book-groups'), + url(r'^api/v2.1/admin/address-book/groups/(?P\d+)/$', AdminAddressBookGroup.as_view(), name='api-v2.1-admin-address-book-group'), + ### system admin ### url(r'^sysadmin/$', sysadmin, name='sysadmin'), url(r'^sys/settings/$', sys_settings, name='sys_settings'), diff --git a/tests/api/endpoints/admin/address_book/test_group.py b/tests/api/endpoints/admin/address_book/test_group.py new file mode 100644 index 0000000000..80ae72d675 --- /dev/null +++ b/tests/api/endpoints/admin/address_book/test_group.py @@ -0,0 +1,66 @@ +import json + +from django.core.urlresolvers import reverse +from seaserv import ccnet_api + +from seahub.test_utils import BaseTestCase +from tests.common.utils import randstring + +class GroupsTest(BaseTestCase): + + def setUp(self): + self.user_name = self.user.username + self.admin_name = self.admin.username + + group_name = 'top group xxx' + self.top_group_id = ccnet_api.create_group(group_name, self.admin_name, + parent_group_id=-1) + self.login_as(self.admin) + self.url = reverse('api-v2.1-admin-address-book-group', + args=[self.top_group_id]) + + def tearDown(self): + self.remove_group(self.top_group_id) + + def test_can_list_child_groups(self): + child_group_id = ccnet_api.create_group('child group xxx', + self.user.username, + parent_group_id=self.top_group_id) + + resp = self.client.get(self.url) + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp['groups']) >= 1 + assert len(json_resp['members']) >= 1 + assert len(json_resp['ancestor_groups']) == 0 + assert json_resp['id'] == self.top_group_id + self.remove_group(child_group_id) + + def test_can_ancestor_groups(self): + child_group_id = ccnet_api.create_group('child group xxx', + self.user.username, + parent_group_id=self.top_group_id) + + url = reverse('api-v2.1-admin-address-book-group', + args=[child_group_id]) + '?return_ancestors=true' + resp = self.client.get(url) + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp['groups']) == 0 + assert len(json_resp['ancestor_groups']) >= 1 + assert json_resp['ancestor_groups'][-1]['id'] == self.top_group_id + self.remove_group(child_group_id) + + def test_can_delete_group(self): + resp = self.client.delete(self.url) + self.assertEqual(200, resp.status_code) + + def test_cannot_delete_group_with_child(self): + child_group_id = ccnet_api.create_group('child group xxx', + self.user.username, + parent_group_id=self.top_group_id) + + resp = self.client.delete(self.url) + self.assertEqual(400, resp.status_code) + + self.remove_group(child_group_id) diff --git a/tests/api/endpoints/admin/address_book/test_groups.py b/tests/api/endpoints/admin/address_book/test_groups.py new file mode 100644 index 0000000000..4fc98cad17 --- /dev/null +++ b/tests/api/endpoints/admin/address_book/test_groups.py @@ -0,0 +1,61 @@ +import json + +from django.core.urlresolvers import reverse +from seaserv import ccnet_api + +from seahub.test_utils import BaseTestCase +from tests.common.utils import randstring + + +class GroupsTest(BaseTestCase): + + def setUp(self): + self.user_name = self.user.username + self.admin_name = self.admin.username + + self.login_as(self.admin) + self.url = reverse('api-v2.1-admin-address-book-groups') + + def test_can_list_top_groups(self): + top_group_id = ccnet_api.create_group('top group xxx', self.user.username, + parent_group_id=-1) + + resp = self.client.get(self.url) + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert len(json_resp['data']) >= 1 + + self.remove_group(top_group_id) + + def test_can_create_top_group(self): + resp = self.client.post(self.url, { + 'group_name': randstring(10), + 'parent_group': -1, + 'group_owner': self.user.username + }) + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert len(json_resp['name']) == 10 + assert json_resp['parent_group_id'] == -1 + + self.remove_group(json_resp['id']) + + def test_can_create_child_group(self): + top_group_id = ccnet_api.create_group('top group xxx', self.user.username, + parent_group_id=-1) + + resp = self.client.post(self.url, { + 'group_name': randstring(10), + 'parent_group': top_group_id, + 'group_owner': self.user.username + }) + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert len(json_resp['name']) == 10 + assert json_resp['parent_group_id'] == top_group_id + + self.remove_group(json_resp['id']) + self.remove_group(top_group_id) From 5d2aa8af05b6a9e0b6e127195b5d825998fd1cae Mon Sep 17 00:00:00 2001 From: llj Date: Thu, 4 Jan 2018 17:06:34 +0800 Subject: [PATCH 2/6] [system admin] added 'address book' --- media/css/seahub.css | 13 + seahub/templates/js/sysadmin-templates.html | 93 ++++++ seahub/templates/sysadmin/base.html | 6 + static/scripts/common.js | 3 + .../collection/address-book-group.js | 28 ++ .../collection/address-book-groups.js | 20 ++ static/scripts/sysadmin-app/router.js | 47 ++- .../views/address-book-group-item.js | 70 ++++ .../sysadmin-app/views/address-book-group.js | 298 ++++++++++++++++++ .../sysadmin-app/views/address-book.js | 171 ++++++++++ .../sysadmin-app/views/group-members.js | 12 +- 11 files changed, 749 insertions(+), 12 deletions(-) create mode 100644 static/scripts/sysadmin-app/collection/address-book-group.js create mode 100644 static/scripts/sysadmin-app/collection/address-book-groups.js create mode 100644 static/scripts/sysadmin-app/views/address-book-group-item.js create mode 100644 static/scripts/sysadmin-app/views/address-book-group.js create mode 100644 static/scripts/sysadmin-app/views/address-book.js diff --git a/media/css/seahub.css b/media/css/seahub.css index 12ca68e463..7c6158bd61 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -680,6 +680,12 @@ textarea:-moz-placeholder {/* for FF */ background:none; text-decoration:underline; } +.small-hd { + padding:3px 10px; + background:#f7f7f7; + border-radius:2px; + margin:15px 0; +} /*******ui widgets**************/ /**** path ****/ @@ -4107,3 +4113,10 @@ img.thumbnail { color:#eb8205; text-decoration:none; } + +/* address book */ +#address-book-group .empty-tip { + color:#a4a4a4; + text-align:center; + margin-bottom:30px; +} diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index 2e21478522..ff86fe2269 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -47,6 +47,12 @@ {% endif %} + {% if is_pro and user.admin_permissions.can_manage_group %} +
  • + {% trans "Address Book" %} +
  • + {% endif %} + {% if multi_tenancy and is_default_admin %}
  • {% trans "Organizations" %} @@ -1004,3 +1010,90 @@ + +{# address book #} + + + + diff --git a/seahub/templates/sysadmin/base.html b/seahub/templates/sysadmin/base.html index bedb20cef1..f760f04f77 100644 --- a/seahub/templates/sysadmin/base.html +++ b/seahub/templates/sysadmin/base.html @@ -58,6 +58,12 @@
  • {% endif %} + {% if is_pro and user.admin_permissions.can_manage_group %} +
  • + {% trans "Address Book" %} +
  • + {% endif %} + {% if multi_tenancy and is_default_admin %}
  • {% trans "Organizations" %} diff --git a/static/scripts/common.js b/static/scripts/common.js index 63681d3995..2c6e84a92f 100644 --- a/static/scripts/common.js +++ b/static/scripts/common.js @@ -200,6 +200,9 @@ define([ case 'admin-operation-logs': return siteRoot + 'api/v2.1/admin/admin-logs/'; case 'admin-login-logs': return siteRoot + 'api/v2.1/admin/admin-login-logs/'; + case 'admin-address-book-groups': return siteRoot + 'api/v2.1/admin/address-book/groups/'; + case 'admin-address-book-group': return siteRoot + 'api/v2.1/admin/address-book/groups/' + options.group_id + '/'; + case 'license': return siteRoot + 'api/v2.1/admin/license/'; } }, diff --git a/static/scripts/sysadmin-app/collection/address-book-group.js b/static/scripts/sysadmin-app/collection/address-book-group.js new file mode 100644 index 0000000000..fae902ffae --- /dev/null +++ b/static/scripts/sysadmin-app/collection/address-book-group.js @@ -0,0 +1,28 @@ +define([ + 'underscore', + 'backbone', + 'common' +], function(_, Backbone, Common) { + 'use strict'; + + var collection = Backbone.Collection.extend({ + setOptions: function(options) { + this.options = options; + }, + + url: function() { + return Common.getUrl({ + name: 'admin-address-book-group', + group_id: this.options.group_id + }); + }, + + parse: function(data) { + this.data = data; + return data.groups; + } + + }); + + return collection; +}); diff --git a/static/scripts/sysadmin-app/collection/address-book-groups.js b/static/scripts/sysadmin-app/collection/address-book-groups.js new file mode 100644 index 0000000000..b86db627b4 --- /dev/null +++ b/static/scripts/sysadmin-app/collection/address-book-groups.js @@ -0,0 +1,20 @@ +define([ + 'underscore', + 'backbone', + 'common' +], function(_, Backbone, Common) { + 'use strict'; + + var collection = Backbone.Collection.extend({ + url: function() { + return Common.getUrl({name: 'admin-address-book-groups'}); + }, + + parse: function(data) { + return data.data; // return the array + } + + }); + + return collection; +}); diff --git a/static/scripts/sysadmin-app/router.js b/static/scripts/sysadmin-app/router.js index 573fdaa299..bbaf95a67f 100644 --- a/static/scripts/sysadmin-app/router.js +++ b/static/scripts/sysadmin-app/router.js @@ -8,16 +8,22 @@ define([ 'sysadmin-app/views/desktop-devices', 'sysadmin-app/views/mobile-devices', 'sysadmin-app/views/device-errors', + 'sysadmin-app/views/repos', 'sysadmin-app/views/search-repos', 'sysadmin-app/views/system-repo', 'sysadmin-app/views/trash-repos', 'sysadmin-app/views/search-trash-repos', 'sysadmin-app/views/dir', + + 'sysadmin-app/views/address-book', + 'sysadmin-app/views/address-book-group', + 'sysadmin-app/views/groups', 'sysadmin-app/views/search-groups', 'sysadmin-app/views/group-repos', 'sysadmin-app/views/group-members', + 'sysadmin-app/views/admin-operation-logs', 'sysadmin-app/views/admin-login-logs', 'sysadmin-app/views/device-trusted-ipaddresses', @@ -25,9 +31,11 @@ define([ ], function($, Backbone, Common, SideNavView, DashboardView, DesktopDevicesView, MobileDevicesView, DeviceErrorsView, ReposView, SearchReposView, SystemReposView, TrashReposView, - SearchTrashReposView, DirView, GroupsView, SearchGroupsView, - GroupReposView, GroupMembersView, AdminOperationLogsview, AdminLoginLogsView, - DeviceTrustedIPView, AccountView) { + SearchTrashReposView, DirView, + AddressBookView, AddressBookGroupView, + GroupsView, SearchGroupsView, GroupReposView, GroupMembersView, + AdminOperationLogsview, AdminLoginLogsView, DeviceTrustedIPView, + AccountView) { "use strict"; @@ -39,17 +47,23 @@ define([ 'mobile-devices/': 'showMobileDevices', 'device-errors/': 'showDeviceErrors', 'device-trusted-ip/': 'showDeviceTrustedIP', + 'all-libs/': 'showLibraries', 'search-libs/': 'showSearchLibraries', 'system-lib/': 'showSystemLibrary', 'trash-libs/': 'showTrashLibraries', 'search-trash-libs/': 'showSearchTrashLibraries', 'libs/:repo_id(/*path)': 'showLibraryDir', + + 'address-book/': 'showAddressBook', + 'address-book/groups/:group_id/': 'showAddressBookGroup', + 'groups/': 'showGroups', 'search-groups/': 'showSearchGroups', 'groups/:group_id/': 'showGroupLibraries', 'groups/:group_id/libs/': 'showGroupLibraries', 'groups/:group_id/members/': 'showGroupMembers', + 'admin-operation-logs/': 'showAdminOperationLogs', 'admin-login-logs/': 'showAdminLoginLogs', // Default @@ -79,6 +93,9 @@ define([ this.searchTrashReposView = new SearchTrashReposView(); this.dirView = new DirView(); + this.addressBookView = new AddressBookView(); + this.addressBookGroupView = new AddressBookGroupView(); + this.groupsView = new GroupsView(); this.searchGroupsView = new SearchGroupsView(); this.groupReposView = new GroupReposView(); @@ -314,6 +331,30 @@ define([ this.groupMembersView.show(group_id); }, + showAddressBook: function() { + if (!app.pageOptions.is_pro || + !app.pageOptions.admin_permissions.can_manage_group) { + this.showDashboard(); + return false; + } + + this.switchCurrentView(this.addressBookView); + this.sideNavView.setCurTab('address-book'); + this.addressBookView.show(); + }, + + showAddressBookGroup: function(group_id) { + if (!app.pageOptions.is_pro || + !app.pageOptions.admin_permissions.can_manage_group) { + this.showDashboard(); + return false; + } + + this.switchCurrentView(this.addressBookGroupView); + this.sideNavView.setCurTab('address-book'); + this.addressBookGroupView.show({'group_id': group_id}); + }, + showAdminOperationLogs: function() { if (!app.pageOptions.admin_permissions.can_view_admin_log) { this.showDashboard(); diff --git a/static/scripts/sysadmin-app/views/address-book-group-item.js b/static/scripts/sysadmin-app/views/address-book-group-item.js new file mode 100644 index 0000000000..e78cf773f9 --- /dev/null +++ b/static/scripts/sysadmin-app/views/address-book-group-item.js @@ -0,0 +1,70 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'moment', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, Moment, HLItemView) { + 'use strict'; + + var GroupView = HLItemView.extend({ + tagName: 'tr', + + template: _.template($('#address-book-group-item-tmpl').html()), + + events: { + 'click .group-delete-btn': 'deleteGroup' + }, + + initialize: function() { + HLItemView.prototype.initialize.call(this); + }, + + deleteGroup: function() { + var _this = this; + var group_name = this.model.get('name'); + var popupTitle = gettext("Delete Group"); + var popupContent = gettext("Are you sure you want to delete %s ?").replace('%s', '' + Common.HTMLescape(group_name) + ''); + var yesCallback = function() { + $.ajax({ + url: Common.getUrl({ + 'name':'admin-address-book-group', + 'group_id': _this.model.get('id') + }), + type: 'DELETE', + cache: false, + beforeSend: Common.prepareCSRFToken, + dataType: 'json', + success: function() { + _this.$el.remove(); + Common.feedback(gettext("Successfully deleted 1 item."), 'success'); + }, + error: function(xhr, textStatus, errorThrown) { + Common.ajaxErrorHandler(xhr, textStatus, errorThrown); + }, + complete: function() { + $.modal.close(); + } + }); + }; + Common.showConfirm(popupTitle, popupContent, yesCallback); + return false; + }, + + render: function() { + var data = this.model.toJSON(), + created_at = Moment(data['created_at']); + + data['time'] = created_at.format('LLLL'); + data['time_from_now'] = Common.getRelativeTimeStr(created_at); + + this.$el.html(this.template(data)); + + return this; + } + + }); + + return GroupView; +}); diff --git a/static/scripts/sysadmin-app/views/address-book-group.js b/static/scripts/sysadmin-app/views/address-book-group.js new file mode 100644 index 0000000000..4e528e3d40 --- /dev/null +++ b/static/scripts/sysadmin-app/views/address-book-group.js @@ -0,0 +1,298 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'sysadmin-app/views/address-book-group-item', + 'sysadmin-app/views/group-member', + 'sysadmin-app/collection/address-book-group' +], function($, _, Backbone, Common, GroupItemView, MemberItemView, GroupCollection) { + 'use strict'; + + var view = Backbone.View.extend({ + + id: 'address-book-group', + + template: _.template($("#address-book-group-tmpl").html()), + pathTemplate: _.template($("#address-book-group-path-tmpl").html()), + groupAddFormTemplate: _.template($("#group-add-form-tmpl").html()), + addMemberFormTemplate: _.template($('#add-group-member-form-tmpl').html()), + + initialize: function() { + this.groupCollection = new GroupCollection(); + this.listenTo(this.groupCollection, 'add', this.addOne); + this.listenTo(this.groupCollection, 'reset', this.reset); + + // members + this.memberCollection = new Backbone.Collection(); + this.listenTo(this.memberCollection, 'add', this.addMember); + + this.render(); + }, + + render: function() { + this.$el.append(this.template()); + + this.$path = this.$('.group-path'); + + this.$groups = this.$('.groups'); + this.$groupsTable = $('table', this.$groups); + this.$groupsTableBody = $('tbody', this.$groupsTable); + this.$groupsEmptyTip = $('.empty-tip', this.$groups); + + this.$members = this.$('.members'); + this.$membersTable = $('table', this.$members); + this.$membersTableBody = $('tbody', this.$membersTable); + + this.$loadingTip = this.$('.loading-tip'); + this.$error = this.$('.error'); + }, + + events: { + 'click .js-add-group': 'addGroup', + 'click .js-add-member': 'newMember' + }, + + initPage: function() { + this.$loadingTip.show(); + + this.$path.empty(); + + this.$groups.hide(); + this.$groupsTable.hide(); + this.$groupsTableBody.empty(); + this.$groupsEmptyTip.hide(); + + this.$members.hide(); + this.$membersTable.hide(); + this.$membersTableBody.empty(); + + this.$error.hide(); + }, + + addGroup: function() { + var _this = this; + + var $form = $(this.groupAddFormTemplate()); + $form.modal(); + $('#simplemodal-container').css({'height':'auto'}); + + $('[name="group_owner"]', $form).select2($.extend( + Common.contactInputOptionsForSelect2(), { + width: '268px', + containerCss: {'margin-bottom': '5px'}, + maximumSelectionSize: 1, + placeholder: gettext("Search user or enter email and press Enter"), // to override 'placeholder' returned by `Common.conta...` + formatSelectionTooBig: gettext("You cannot select any more choices") + })); + + $form.submit(function() { + var group_name = $.trim($('[name="group_name"]', $form).val()); + var group_owner = $.trim($('[name="group_owner"]', $form).val()); + var $error = $('.error', $form); + var $submitBtn = $('[type="submit"]', $form); + + if (!group_name) { + $error.html(gettext("Name is required.")).show(); + return false; + } + + $error.hide(); + Common.disableButton($submitBtn); + + $.ajax({ + url: Common.getUrl({ + 'name': 'admin-address-book-groups' + }), + type: 'POST', + cache: false, + data: { + 'parent_group': _this.groupCollection.data.id, + 'group_name': group_name, + 'group_owner': group_owner + }, + beforeSend: Common.prepareCSRFToken, + success: function(data) { + _this.groupCollection.add(data, {prepend: true}); + if (_this.groupCollection.length == 1) { + _this.$groupsEmptyTip.hide(); + _this.$groupsTable.show(); + } + Common.closeModal(); + }, + error: function(xhr) { + var err_msg; + if (xhr.responseText) { + err_msg = xhr.responseJSON.error_msg; + } else { + err_msg = gettext('Please check the network.'); + } + $error.html(err_msg).show(); + Common.enableButton($submitBtn); + } + }); + return false; + }); + return false; + }, + + newMember: function() { + var _this = this; + + var $form = $(this.addMemberFormTemplate()); + $form.modal(); + $('#simplemodal-container').css({'height':'auto', 'width':'auto'}); + + $('[name="email"]', $form).select2($.extend( + Common.contactInputOptionsForSelect2(), { + width: '275px', + containerCss: {'margin-bottom': '5px'}, + placeholder: gettext("Search users or enter emails and press Enter") + })); + + $form.submit(function() { + var emails = $.trim($('[name="email"]', $form).val()); + var $error = $('.error', $form); + var $submitBtn = $('[type="submit"]', $form); + + if (!emails) { + $error.html(gettext("It is required.")).show(); + return false; + } + + $error.hide(); + Common.disableButton($submitBtn); + + $.ajax({ + url: Common.getUrl({ + 'name': 'admin-group-members', + 'group_id': _this.options.group_id + }), + type: 'POST', + dataType: 'json', + data: {'email': emails.split(',')}, + traditional: true, + beforeSend: Common.prepareCSRFToken, + success: function(data) { + if (data.success.length > 0) { + _this.memberCollection.add(data.success, {prepend: true}); + } + + var err_str = ''; + if (data.failed.length > 0) { + $(data.failed).each(function(index, item) { + err_str += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '
    '; + }); + $error.html(err_str).show(); + Common.enableButton($submitBtn); + } else { + Common.closeModal(); + } + + }, + error: function(jqXHR, textStatus, errorThrown){ + var err_msg; + if (jqXHR.responseText) { + err_msg = jqXHR.responseJSON.error_msg; + } else { + err_msg = gettext('Please check the network.'); + } + $error.html(err_msg).show(); + Common.enableButton($submitBtn); + } + }); + return false; + }); + return false; + }, + + hide: function() { + this.$el.detach(); + this.attached = false; + }, + + show: function(options) { + this.options = options; + if (!this.attached) { + this.attached = true; + $("#right-panel").html(this.$el); + } + this.getContent(); + }, + + getContent: function() { + this.initPage(); + var _this = this; + this.groupCollection.setOptions(this.options); + this.groupCollection.fetch({ + data: {'return_ancestors': true}, + cache: false, + reset: true, + error: function(collection, response, opts) { + var err_msg; + if (response.responseText) { + if (response['status'] == 401 || response['status'] == 403) { + err_msg = gettext("Permission error"); + } else { + err_msg = $.parseJSON(response.responseText).error_msg; + } + } else { + err_msg = gettext("Failed. Please check the network."); + } + _this.$error.html(err_msg).show(); + }, + complete:function() { + _this.$loadingTip.hide(); + } + }); + }, + + renderPath: function() { + this.$path.html(this.pathTemplate({ + ancestor_groups: this.groupCollection.data.ancestor_groups, + name: this.groupCollection.data.name + })); + }, + + reset: function() { + this.initPage(); + this.$loadingTip.hide(); + this.renderPath(); + + if (this.groupCollection.length > 0) { + this.groupCollection.each(this.addOne, this); + this.$groupsTable.show(); + } else { + this.$groupsEmptyTip.show(); + } + this.$groups.show(); + + // There is at least 1 member, the owner. + this.memberCollection.reset(this.groupCollection.data.members); + this.memberCollection.each(this.addMember, this); + this.$membersTable.show(); + this.$members.show(); + }, + + addMember: function(item, collection, options) { + var view = new MemberItemView({model: item}); + if (options.prepend) { + this.$membersTableBody.prepend(view.render().el); + } else { + this.$membersTableBody.append(view.render().el); + } + }, + + addOne: function(item, collection, options) { + var view = new GroupItemView({model: item}); + if (options.prepend) { + this.$groupsTableBody.prepend(view.render().el); + } else { + this.$groupsTableBody.append(view.render().el); + } + } + }); + + return view; + +}); diff --git a/static/scripts/sysadmin-app/views/address-book.js b/static/scripts/sysadmin-app/views/address-book.js new file mode 100644 index 0000000000..5c6acaded3 --- /dev/null +++ b/static/scripts/sysadmin-app/views/address-book.js @@ -0,0 +1,171 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'sysadmin-app/views/address-book-group-item', + 'sysadmin-app/collection/address-book-groups' +], function($, _, Backbone, Common, GroupView, GroupCollection) { + 'use strict'; + + var view = Backbone.View.extend({ + + id: 'address-book', + + template: _.template($("#address-book-tmpl").html()), + groupAddFormTemplate: _.template($("#group-add-form-tmpl").html()), + + initialize: function() { + this.groupCollection = new GroupCollection(); + this.listenTo(this.groupCollection, 'add', this.addOne); + this.listenTo(this.groupCollection, 'reset', this.reset); + this.render(); + }, + + render: function() { + this.$el.append(this.template()); + + this.$table = this.$('table'); + this.$tableBody = $('tbody', this.$table); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); + this.$error = this.$('.error'); + }, + + events: { + 'click .js-add-group': 'addGroup' + }, + + initPage: function() { + this.$loadingTip.show(); + this.$table.hide(); + this.$tableBody.empty(); + this.$emptyTip.hide(); + this.$error.hide(); + }, + + addGroup: function () { + var $form = $(this.groupAddFormTemplate()), + groups = this.groupCollection, + _this = this; + + $form.modal(); + $('#simplemodal-container').css({'height':'auto'}); + + $('[name="group_owner"]', $form).select2($.extend( + Common.contactInputOptionsForSelect2(), { + width: '268px', + containerCss: {'margin-bottom': '5px'}, + maximumSelectionSize: 1, + placeholder: gettext("Search user or enter email and press Enter"), // to override 'placeholder' returned by `Common.conta...` + formatSelectionTooBig: gettext("You cannot select any more choices") + })); + + $form.submit(function() { + var group_name = $.trim($('[name="group_name"]', $form).val()); + var group_owner = $.trim($('[name="group_owner"]', $form).val()); + var $error = $('.error', $form); + var $submitBtn = $('[type="submit"]', $form); + + if (!group_name) { + $error.html(gettext("Name is required.")).show(); + return false; + } + + $error.hide(); + Common.disableButton($submitBtn); + + groups.create({ + 'parent_group': -1, + 'group_name': group_name, + 'group_owner': group_owner + }, { + prepend: true, + wait: true, + success: function() { + if (groups.length == 1) { + groups.reset(groups.models); + } + Common.closeModal(); + }, + 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.'); + } + $error.html(err_msg).show(); + Common.enableButton($submitBtn); + } + }); + return false; + }); + return false; + }, + + hide: function() { + this.$el.detach(); + this.attached = false; + }, + + show: function(option) { + this.option = option; + if (!this.attached) { + this.attached = true; + $("#right-panel").html(this.$el); + } + this.getContent(); + }, + + getContent: function() { + this.initPage(); + var _this = this; + this.groupCollection.fetch({ + cache: false, + reset: true, + error: function(collection, response, opts) { + var err_msg; + if (response.responseText) { + if (response['status'] == 401 || response['status'] == 403) { + err_msg = gettext("Permission error"); + } else { + err_msg = $.parseJSON(response.responseText).error_msg; + } + } else { + err_msg = gettext("Failed. Please check the network."); + } + _this.$error.html(err_msg).show(); + }, + complete:function() { + _this.$loadingTip.hide(); + } + }); + }, + + reset: function() { + this.initPage(); + + this.$loadingTip.hide(); + if (this.groupCollection.length > 0) { + this.groupCollection.each(this.addOne, this); + this.$table.show(); + } else { + this.$emptyTip.show(); + } + }, + + addOne: function(group, collection, options) { + var view = new GroupView({model: group}); + if (options.prepend) { + this.$tableBody.prepend(view.render().el); + } else { + this.$tableBody.append(view.render().el); + } + + } + }); + + return view; + +}); diff --git a/static/scripts/sysadmin-app/views/group-members.js b/static/scripts/sysadmin-app/views/group-members.js index 79d2bcb66f..d540459523 100644 --- a/static/scripts/sysadmin-app/views/group-members.js +++ b/static/scripts/sysadmin-app/views/group-members.js @@ -38,7 +38,7 @@ define([ Common.contactInputOptionsForSelect2(), { width: '275px', containerCss: {'margin-bottom': '5px'}, - placeholder: gettext("Search user or enter email and press Enter") + placeholder: gettext("Search users or enter emails and press Enter") })); $form.submit(function() { @@ -48,16 +48,10 @@ define([ var $submitBtn = $('[type="submit"]', $form); if (!emails) { - $error.html(gettext("Email is required.")).show(); + $error.html(gettext("It is required.")).show(); return false; } - var input_emails = []; - var emails_list = emails.split(','); - for (var i = 0; i < emails_list.length; i++) { - input_emails.push(emails_list[i]); - } - $error.hide(); Common.disableButton($submitBtn); @@ -70,7 +64,7 @@ define([ dataType: 'json', beforeSend: Common.prepareCSRFToken, traditional: true, - data: {'email': input_emails}, + data: {'email': emails.split(',')}, success: function(data) { if (data.success.length > 0) { _this.groupMemberCollection.add(data.success, {prepend: true}); From 30c063679162005bc46bf82f5928694f10431825 Mon Sep 17 00:00:00 2001 From: llj Date: Fri, 20 Apr 2018 11:22:26 +0800 Subject: [PATCH 3/6] [address book] modification: nav icon, group owner --- .../endpoints/admin/address_book/groups.py | 9 +------ seahub/templates/js/sysadmin-templates.html | 27 ++++++++++++------- seahub/templates/sysadmin/base.html | 2 +- .../sysadmin-app/views/address-book-group.js | 13 ++------- .../sysadmin-app/views/address-book.js | 13 ++------- 5 files changed, 23 insertions(+), 41 deletions(-) diff --git a/seahub/api2/endpoints/admin/address_book/groups.py b/seahub/api2/endpoints/admin/address_book/groups.py index 33c8b5ced3..2e3fead3dc 100644 --- a/seahub/api2/endpoints/admin/address_book/groups.py +++ b/seahub/api2/endpoints/admin/address_book/groups.py @@ -74,15 +74,8 @@ class AdminAddressBookGroups(APIView): error_msg = _(u'There is already a group with that name.') return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + # Group owner is 'system admin' group_owner = request.data.get('group_owner', '') - if group_owner: - try: - User.objects.get(email=group_owner) - except User.DoesNotExist: - error_msg = 'User %s not found.' % group_owner - return api_error(status.HTTP_404_NOT_FOUND, error_msg) - else: - group_owner = request.user.username try: parent_group = int(request.data.get('parent_group', -1)) diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index ff86fe2269..cb3112b39b 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -49,7 +49,7 @@ {% if is_pro and user.admin_permissions.can_manage_group %}
  • - {% trans "Address Book" %} + {% trans "Address Book" %}
  • {% endif %} @@ -683,6 +683,16 @@ + + + + + diff --git a/static/scripts/common.js b/static/scripts/common.js index 2c6e84a92f..65c8d05c76 100644 --- a/static/scripts/common.js +++ b/static/scripts/common.js @@ -149,6 +149,10 @@ define([ 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_member_bulk': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/members/bulk/'; + + case 'group_owned_libraries': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/group-owned-libraries/'; + case 'group_owned_library': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/group-owned-libraries/' + options.repo_id + '/'; + case 'group_import_members': return siteRoot + 'ajax/group/' + options.group_id + '/members/import/'; case 'group_repos': return siteRoot + 'api2/groups/' + options.group_id + '/repos/'; case 'group_discussions': return siteRoot + 'api2/groups/' + options.group_id + '/discussions/'; diff --git a/static/scripts/sysadmin-app/views/address-book-group-item.js b/static/scripts/sysadmin-app/views/address-book-group-item.js index e78cf773f9..c2415a75b5 100644 --- a/static/scripts/sysadmin-app/views/address-book-group-item.js +++ b/static/scripts/sysadmin-app/views/address-book-group-item.js @@ -4,21 +4,26 @@ define([ 'backbone', 'common', 'moment', + 'simplemodal', 'app/views/widgets/hl-item-view' -], function($, _, Backbone, Common, Moment, HLItemView) { +], function($, _, Backbone, Common, Moment, Simplemodal, HLItemView) { 'use strict'; var GroupView = HLItemView.extend({ tagName: 'tr', template: _.template($('#address-book-group-item-tmpl').html()), + setQuotaFormTemplate: _.template($('#address-book-group-quota-set-form-tmpl').html()), events: { - 'click .group-delete-btn': 'deleteGroup' + 'click .group-delete-btn': 'deleteGroup', + 'click .quota-edit-icon': 'setQuota' }, initialize: function() { HLItemView.prototype.initialize.call(this); + + this.listenTo(this.model, "change", this.render); }, deleteGroup: function() { @@ -52,6 +57,54 @@ define([ return false; }, + setQuota: function() { + var model = this.model; + + var $form = $(this.setQuotaFormTemplate()); + $form.modal(); + $('#simplemodal-container').css({'width':'auto', 'height':'auto'}); + + $form.on('submit', function() { + var $error = $('.error', $form); + var $submitBtn = $('[type="submit"]', $form); + var quota = $.trim($('[name="quota"]', $form).val()); + + if (!quota) { + $error.html(gettext("It is required.")).show(); + return false; + } + + Common.disableButton($submitBtn); + $.ajax({ + url: Common.getUrl({ + 'name':'admin-address-book-group', + 'group_id': model.get('id') + }), + type: 'PUT', + cache: false, + beforeSend: Common.prepareCSRFToken, + data: {'quota': quota == -2 ? -2 : quota * 1000000}, + dataType: 'json', + success: function(data) { + model.set({'quota': data.quota}); + $.modal.close(); + }, + 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."); + } + $error.html(err_msg).show(); + Common.enableButton($submitBtn); + } + }); + + return false; + }); + }, + render: function() { var data = this.model.toJSON(), created_at = Moment(data['created_at']); @@ -59,6 +112,8 @@ define([ data['time'] = created_at.format('LLLL'); data['time_from_now'] = Common.getRelativeTimeStr(created_at); + data['quota_shown'] = data['quota'] == -2 ? '--' : Common.quotaSizeFormat(data['quota']); + this.$el.html(this.template(data)); return this; diff --git a/static/scripts/sysadmin-app/views/address-book-group-library.js b/static/scripts/sysadmin-app/views/address-book-group-library.js new file mode 100644 index 0000000000..e1d60f9de8 --- /dev/null +++ b/static/scripts/sysadmin-app/views/address-book-group-library.js @@ -0,0 +1,78 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, HLItemView) { + + 'use strict'; + + var GroupRepoView = HLItemView.extend({ + + tagName: 'tr', + + template: _.template($('#address-book-group-library-item-tmpl').html()), + + events: { + 'click .repo-delete-btn': 'del' + }, + + initialize: function(options) { + HLItemView.prototype.initialize.call(this); + + this.group_id = options.group_id; + }, + + del: function() { + var _this = this; + var repo_name = this.model.get('repo_name'); + var popupTitle = gettext("Delete Library"); + var popupContent = gettext("Are you sure you want to delete %s ?").replace('%s', '' + Common.HTMLescape(repo_name) + ''); + var yesCallback = function() { + $.ajax({ + url: Common.getUrl({ + 'name': 'group_owned_library', + 'group_id': _this.group_id, + 'repo_id': _this.model.get('repo_id') + }), + type: 'DELETE', + beforeSend: Common.prepareCSRFToken, + dataType: 'json', + success: function() { + _this.$el.remove(); + var msg = gettext("Successfully deleted library {placeholder}").replace('{placeholder}', repo_name); + Common.feedback(msg, 'success'); + }, + error: function(xhr, textStatus, errorThrown) { + Common.ajaxErrorHandler(xhr, textStatus, errorThrown); + }, + complete: function() { + $.modal.close(); + } + }); + }; + Common.showConfirm(popupTitle, popupContent, yesCallback); + return false; + }, + + render: function() { + var data = this.model.toJSON(), + icon_size = Common.isHiDPI() ? 48 : 24, + icon_url = this.model.getIconUrl(icon_size); + + data['icon_url'] = icon_url; + data['icon_title'] = this.model.getIconTitle(); + data['name'] = data.name || data.repo_name; + data['formatted_size'] = Common.fileSizeFormat(data['size'], 1), + data['enable_sys_admin_view_repo'] = app.pageOptions.enable_sys_admin_view_repo; + data['is_pro'] = app.pageOptions.is_pro; + this.$el.html(this.template(data)); + + return this; + } + + }); + + return GroupRepoView; +}); diff --git a/static/scripts/sysadmin-app/views/address-book-group.js b/static/scripts/sysadmin-app/views/address-book-group.js index 4263bbae76..91ff4c6a9e 100644 --- a/static/scripts/sysadmin-app/views/address-book-group.js +++ b/static/scripts/sysadmin-app/views/address-book-group.js @@ -5,8 +5,11 @@ define([ 'common', 'sysadmin-app/views/address-book-group-item', 'sysadmin-app/views/group-member', - 'sysadmin-app/collection/address-book-group' -], function($, _, Backbone, Common, GroupItemView, MemberItemView, GroupCollection) { + 'sysadmin-app/views/address-book-group-library', + 'sysadmin-app/collection/address-book-group', + 'sysadmin-app/collection/group-repos' +], function($, _, Backbone, Common, GroupItemView, MemberItemView, + LibItemView, GroupCollection, GroupRepoCollection) { 'use strict'; var view = Backbone.View.extend({ @@ -17,6 +20,7 @@ define([ pathTemplate: _.template($("#address-book-group-path-tmpl").html()), groupAddFormTemplate: _.template($("#address-book-group-add-form-tmpl").html()), addMemberFormTemplate: _.template($('#add-group-member-form-tmpl').html()), + addLibFormTemplate: _.template($("#address-book-library-add-form-tmpl").html()), initialize: function() { this.groupCollection = new GroupCollection(); @@ -27,6 +31,11 @@ define([ this.memberCollection = new Backbone.Collection(); this.listenTo(this.memberCollection, 'add', this.addMember); + // libraries + this.groupRepoCollection = new GroupRepoCollection(); + this.listenTo(this.groupRepoCollection, 'reset', this.resetLibraries); + this.listenTo(this.groupRepoCollection, 'add', this.addLibrary); + this.render(); }, @@ -43,6 +52,12 @@ define([ this.$members = this.$('.members'); this.$membersTable = $('table', this.$members); this.$membersTableBody = $('tbody', this.$membersTable); + this.$membersEmptyTip = $('.empty-tip', this.$members); + + this.$libs = this.$('.libraries'); + this.$libsTable = $('table', this.$libs); + this.$libsTableBody = $('tbody', this.$libsTable); + this.$libsEmptyTip = $('.empty-tip', this.$libs); this.$loadingTip = this.$('.loading-tip'); this.$error = this.$('.error'); @@ -50,7 +65,8 @@ define([ events: { 'click .js-add-group': 'addGroup', - 'click .js-add-member': 'newMember' + 'click .js-add-member': 'newMember', + 'click .js-add-library': 'newLibrary' }, initPage: function() { @@ -66,6 +82,12 @@ define([ this.$members.hide(); this.$membersTable.hide(); this.$membersTableBody.empty(); + this.$membersEmptyTip.hide(); + + this.$libs.hide(); + this.$libsTable.hide(); + this.$libsTableBody.empty(); + this.$libsEmptyTip.hide(); this.$error.hide(); }, @@ -167,6 +189,10 @@ define([ success: function(data) { if (data.success.length > 0) { _this.memberCollection.add(data.success, {prepend: true}); + if (_this.memberCollection.length == 1) { + _this.$membersEmptyTip.hide(); + _this.$membersTable.show(); + } } var err_str = ''; @@ -197,6 +223,60 @@ define([ return false; }, + newLibrary: function() { + var _this = this; + + var $form = $(this.addLibFormTemplate()); + $form.modal(); + $('#simplemodal-container').css({'height':'auto', 'width':'auto'}); + + $form.submit(function() { + var name = $.trim($('[name="library_name"]', $form).val()); + var $error = $('.error', $form); + var $submitBtn = $('[type="submit"]', $form); + + if (!name) { + $error.html(gettext("It is required.")).show(); + return false; + } + + $error.hide(); + Common.disableButton($submitBtn); + + $.ajax({ + url: Common.getUrl({ + 'name': 'group_owned_libraries', + 'group_id': _this.options.group_id + }), + type: 'POST', + dataType: 'json', + data: {'repo_name': name}, + traditional: true, + beforeSend: Common.prepareCSRFToken, + success: function(data) { + _this.groupRepoCollection.add(data, {prepend: true}); + if (_this.groupRepoCollection.length == 1) { + _this.$libsEmptyTip.hide(); + _this.$libsTable.show(); + } + Common.closeModal(); + }, + error: function(jqXHR, textStatus, errorThrown){ + var err_msg; + if (jqXHR.responseText) { + err_msg = jqXHR.responseJSON.error_msg; + } else { + err_msg = gettext('Please check the network.'); + } + $error.html(err_msg).show(); + Common.enableButton($submitBtn); + } + }); + return false; + }); + return false; + }, + hide: function() { this.$el.detach(); this.attached = false; @@ -219,6 +299,9 @@ define([ data: {'return_ancestors': true}, cache: false, reset: true, + success: function() { + _this.getLibs(); + }, error: function(collection, response, opts) { var err_msg; if (response.responseText) { @@ -238,6 +321,47 @@ define([ }); }, + getLibs: function() { + var _this = this; + this.groupRepoCollection.setGroupId(this.options.group_id); + this.groupRepoCollection.fetch({ + cache: false, + reset: true, + error: function(collection, response, opts) { + var err_msg; + if (response.responseText) { + if (response['status'] == 401 || response['status'] == 403) { + err_msg = gettext("Permission error"); + } else { + err_msg = $.parseJSON(response.responseText).error_msg; + } + } else { + err_msg = gettext("Failed. Please check the network."); + } + _this.$error.html(err_msg).show(); + } + }); + }, + + resetLibraries: function() { + if (this.groupRepoCollection.length > 0) { + this.groupRepoCollection.each(this.addLibrary, this); + this.$libsTable.show(); + } else { + this.$libsEmptyTip.show(); + } + this.$libs.show(); + }, + + addLibrary: function(item, collection, options) { + var view = new LibItemView({model: item, group_id: this.options.group_id}); + if (options.prepend) { + this.$libsTableBody.prepend(view.render().el); + } else { + this.$libsTableBody.append(view.render().el); + } + }, + renderPath: function() { this.$path.html(this.pathTemplate({ ancestor_groups: this.groupCollection.data.ancestor_groups, @@ -258,10 +382,13 @@ define([ } this.$groups.show(); - // There is at least 1 member, the owner. this.memberCollection.reset(this.groupCollection.data.members); - this.memberCollection.each(this.addMember, this); - this.$membersTable.show(); + if (this.memberCollection.length > 0) { + this.memberCollection.each(this.addMember, this); + this.$membersTable.show(); + } else { + this.$membersEmptyTip.show(); + } this.$members.show(); }, From 5e5813072cc0d2c9c73081d92cacd377abbabb92 Mon Sep 17 00:00:00 2001 From: llj Date: Wed, 25 Apr 2018 16:19:15 +0800 Subject: [PATCH 6/6] [address book] modification --- seahub/templates/js/sysadmin-templates.html | 37 +++++++++---------- seahub/templates/sysadmin/base.html | 4 +- static/scripts/common.js | 6 +-- .../views/address-book-group-item.js | 13 ++++++- .../views/address-book-group-library.js | 4 +- .../sysadmin-app/views/address-book-group.js | 2 +- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index aa1d99e167..cf4319c11c 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -49,13 +49,13 @@ {% if is_pro and user.admin_permissions.can_manage_group %}
  • - {% trans "Organization" %} + {% trans "Departments" %}
  • {% endif %} {% if multi_tenancy and is_default_admin %}
  • - {% trans "Multi-Tenancy" %} + {% trans "Organizations" %}
  • {% endif %} @@ -683,16 +683,6 @@ - - +