mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 17:33:18 +00:00
Merge pull request #1457 from haiwen/admin-group
udpate admin groups page
This commit is contained in:
107
seahub/api2/endpoints/admin/group_libraries.py
Normal file
107
seahub/api2/endpoints/admin/group_libraries.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
|
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
|
||||||
|
import seaserv
|
||||||
|
from seaserv import seafile_api, ccnet_api
|
||||||
|
|
||||||
|
from seahub.utils import is_org_context
|
||||||
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
|
from seahub.api2.utils import api_error
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def get_group_repo_info(repo):
|
||||||
|
result = {}
|
||||||
|
result['repo_id'] = repo.repo_id
|
||||||
|
result['name'] = repo.repo_name
|
||||||
|
result['size'] = repo.size
|
||||||
|
result['shared_by'] = repo.user
|
||||||
|
result['permission'] = repo.permission
|
||||||
|
result['group_id'] = repo.group_id
|
||||||
|
result['encrypted'] = repo.encrypted
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class AdminGroupLibraries(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
permission_classes = (IsAdminUser,)
|
||||||
|
|
||||||
|
def get(self, request, group_id, format=None):
|
||||||
|
""" List all group repos
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if is_org_context(request):
|
||||||
|
org_id = request.user.org.org_id
|
||||||
|
repos = seafile_api.get_org_group_repos(org_id, group_id)
|
||||||
|
else:
|
||||||
|
repos = seafile_api.get_repos_by_group(group_id)
|
||||||
|
|
||||||
|
group_repos_info = []
|
||||||
|
for repo in repos:
|
||||||
|
repo_info = get_group_repo_info(repo)
|
||||||
|
group_repos_info.append(repo_info)
|
||||||
|
|
||||||
|
group_libraries = {
|
||||||
|
'group_id': group_id,
|
||||||
|
'group_name': group.group_name,
|
||||||
|
'libraries': group_repos_info
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(group_libraries)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminGroupLibrary(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
permission_classes = (IsAdminUser,)
|
||||||
|
|
||||||
|
def delete(self, request, group_id, repo_id, format=None):
|
||||||
|
""" Unshare repo from group
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
repo = seafile_api.get_repo(repo_id)
|
||||||
|
if not repo:
|
||||||
|
error_msg = 'Library %s not found.' % repo_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
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:
|
||||||
|
if is_org_context(request):
|
||||||
|
org_id = request.user.org.org_id
|
||||||
|
seaserv.del_org_group_repo(repo_id, org_id, group_id)
|
||||||
|
else:
|
||||||
|
repo_owner = seafile_api.get_repo_owner(repo_id)
|
||||||
|
seafile_api.unset_group_repo(repo_id, group_id, repo_owner)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
return Response({'success': True})
|
223
seahub/api2/endpoints/admin/group_members.py
Normal file
223
seahub/api2/endpoints/admin/group_members.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
|
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 seaserv import seafile_api, ccnet_api
|
||||||
|
|
||||||
|
from seahub.group.utils import get_group_member_info, is_group_member
|
||||||
|
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
||||||
|
from seahub.base.accounts import User
|
||||||
|
|
||||||
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
|
from seahub.api2.utils import api_error
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminGroupMembers(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
permission_classes = (IsAdminUser,)
|
||||||
|
|
||||||
|
def get(self, request, group_id, format=None):
|
||||||
|
""" List all group members
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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:
|
||||||
|
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)
|
||||||
|
|
||||||
|
group_members_info = []
|
||||||
|
for m in members:
|
||||||
|
member_info = get_group_member_info(request, group_id, m.user_name, avatar_size)
|
||||||
|
group_members_info.append(member_info)
|
||||||
|
|
||||||
|
group_members = {
|
||||||
|
'group_id': group_id,
|
||||||
|
'group_name': group.group_name,
|
||||||
|
'members': group_members_info
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(group_members)
|
||||||
|
|
||||||
|
def post(self, request, group_id):
|
||||||
|
"""
|
||||||
|
Bulk add group members.
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# argument check
|
||||||
|
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)
|
||||||
|
|
||||||
|
emails = request.POST.getlist('email', '')
|
||||||
|
if not emails:
|
||||||
|
error_msg = 'Email invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
result['failed'] = []
|
||||||
|
result['success'] = []
|
||||||
|
emails_need_add = []
|
||||||
|
|
||||||
|
for email in emails:
|
||||||
|
try:
|
||||||
|
User.objects.get(email=email)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'error_msg': 'User %s not found.' % email
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ccnet_api.is_group_user(group_id, email):
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'error_msg': 'User %s is already a group member.' % email
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
emails_need_add.append(email)
|
||||||
|
|
||||||
|
# Add user to group.
|
||||||
|
for email in emails_need_add:
|
||||||
|
try:
|
||||||
|
ccnet_api.group_add_member(group_id, group.creator_name, email)
|
||||||
|
member_info = get_group_member_info(request, group_id, email)
|
||||||
|
result['success'].append(member_info)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'error_msg': 'Internal Server Error'
|
||||||
|
})
|
||||||
|
|
||||||
|
return Response(result)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminGroupMember(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
permission_classes = (IsAdminUser,)
|
||||||
|
|
||||||
|
def put(self, request, group_id, email, format=None):
|
||||||
|
""" update role of a group member
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# argument check
|
||||||
|
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:
|
||||||
|
User.objects.get(email=email)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
error_msg = 'User %s not found.' % email
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not is_group_member(group_id, email):
|
||||||
|
error_msg = 'Email %s invalid.' % email
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
is_admin = request.data.get('is_admin', '')
|
||||||
|
try:
|
||||||
|
# set/unset a specific group member as admin
|
||||||
|
if is_admin.lower() == 'true':
|
||||||
|
ccnet_api.group_set_admin(group_id, email)
|
||||||
|
elif is_admin.lower() == 'false':
|
||||||
|
ccnet_api.group_unset_admin(group_id, email)
|
||||||
|
else:
|
||||||
|
error_msg = 'is_admin invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
member_info = get_group_member_info(request, group_id, email)
|
||||||
|
return Response(member_info)
|
||||||
|
|
||||||
|
def delete(self, request, group_id, email, format=None):
|
||||||
|
""" Delete an user from group
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# argument check
|
||||||
|
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:
|
||||||
|
User.objects.get(email=email)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
error_msg = 'User %s not found.' % email
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
# delete member from group
|
||||||
|
try:
|
||||||
|
if not is_group_member(group_id, email):
|
||||||
|
return Response({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
if group.creator_name == email:
|
||||||
|
error_msg = '%s is group owner, can not be removed.' % email
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ccnet_api.group_remove_member(group_id, group.creator_name, email)
|
||||||
|
# remove repo-group share info of all 'email' owned repos
|
||||||
|
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
return Response({'success': True})
|
@@ -50,7 +50,7 @@ class GroupMembers(APIView):
|
|||||||
error_msg = 'Permission denied.'
|
error_msg = 'Permission denied.'
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
members = seaserv.get_group_members(group_id)
|
members = ccnet_api.get_group_members(group_id)
|
||||||
|
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
@@ -169,13 +169,12 @@ class GroupMember(APIView):
|
|||||||
|
|
||||||
# set/unset a specific group member as admin
|
# set/unset a specific group member as admin
|
||||||
if is_admin.lower() == 'true':
|
if is_admin.lower() == 'true':
|
||||||
seaserv.ccnet_threaded_rpc.group_set_admin(group_id, email)
|
ccnet_api.group_set_admin(group_id, email)
|
||||||
elif is_admin.lower() == 'false':
|
elif is_admin.lower() == 'false':
|
||||||
seaserv.ccnet_threaded_rpc.group_unset_admin(group_id, email)
|
ccnet_api.group_unset_admin(group_id, email)
|
||||||
else:
|
else:
|
||||||
error_msg = 'is_admin invalid.'
|
error_msg = 'is_admin invalid.'
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
@@ -204,7 +203,7 @@ class GroupMember(APIView):
|
|||||||
# user leave group
|
# user leave group
|
||||||
if username == email:
|
if username == email:
|
||||||
try:
|
try:
|
||||||
seaserv.ccnet_threaded_rpc.quit_group(group_id, username)
|
ccnet_api.quit_group(group_id, username)
|
||||||
# remove repo-group share info of all 'email' owned repos
|
# remove repo-group share info of all 'email' owned repos
|
||||||
seafile_api.remove_group_repos_by_owner(group_id, email)
|
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
@@ -217,14 +216,14 @@ class GroupMember(APIView):
|
|||||||
try:
|
try:
|
||||||
if is_group_owner(group_id, username):
|
if is_group_owner(group_id, username):
|
||||||
# group owner can delete all group member
|
# group owner can delete all group member
|
||||||
seaserv.ccnet_threaded_rpc.group_remove_member(group_id, username, email)
|
ccnet_api.group_remove_member(group_id, username, email)
|
||||||
seafile_api.remove_group_repos_by_owner(group_id, email)
|
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
|
|
||||||
elif is_group_admin(group_id, username):
|
elif is_group_admin(group_id, username):
|
||||||
# group admin can NOT delete group owner/admin
|
# group admin can NOT delete group owner/admin
|
||||||
if not is_group_admin_or_owner(group_id, email):
|
if not is_group_admin_or_owner(group_id, email):
|
||||||
seaserv.ccnet_threaded_rpc.group_remove_member(group_id, username, email)
|
ccnet_api.group_remove_member(group_id, username, email)
|
||||||
seafile_api.remove_group_repos_by_owner(group_id, email)
|
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
else:
|
else:
|
||||||
|
@@ -4,6 +4,7 @@ import re
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import seaserv
|
import seaserv
|
||||||
|
from seaserv import ccnet_api
|
||||||
|
|
||||||
from seahub.utils import is_org_context
|
from seahub.utils import is_org_context
|
||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
@@ -44,7 +45,7 @@ def check_group_name_conflict(request, new_group_name):
|
|||||||
if request.cloud_mode:
|
if request.cloud_mode:
|
||||||
checked_groups = seaserv.get_personal_groups_by_user(username)
|
checked_groups = seaserv.get_personal_groups_by_user(username)
|
||||||
else:
|
else:
|
||||||
checked_groups = seaserv.ccnet_threaded_rpc.get_all_groups(-1, -1)
|
checked_groups = ccnet_api.get_all_groups(-1, -1)
|
||||||
|
|
||||||
for g in checked_groups:
|
for g in checked_groups:
|
||||||
if g.group_name == new_group_name:
|
if g.group_name == new_group_name:
|
||||||
@@ -53,13 +54,13 @@ def check_group_name_conflict(request, new_group_name):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def is_group_member(group_id, email):
|
def is_group_member(group_id, email):
|
||||||
return seaserv.is_group_user(group_id, email)
|
return ccnet_api.is_group_user(int(group_id), email)
|
||||||
|
|
||||||
def is_group_admin(group_id, email):
|
def is_group_admin(group_id, email):
|
||||||
return seaserv.check_group_staff(group_id, email)
|
return ccnet_api.check_group_staff(int(group_id), email)
|
||||||
|
|
||||||
def is_group_owner(group_id, email):
|
def is_group_owner(group_id, email):
|
||||||
group = seaserv.get_group(group_id)
|
group = ccnet_api.get_group(int(group_id))
|
||||||
if email == group.creator_name:
|
if email == group.creator_name:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -85,14 +86,23 @@ def get_group_member_info(request, group_id, email, avatar_size=AVATAR_DEFAULT_S
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
avatar_url = get_default_avatar_url()
|
avatar_url = get_default_avatar_url()
|
||||||
|
|
||||||
is_admin = seaserv.check_group_staff(group_id, email)
|
role = 'Member'
|
||||||
|
group = ccnet_api.get_group(int(group_id))
|
||||||
|
is_admin = ccnet_api.check_group_staff(int(group_id), email)
|
||||||
|
if email == group.creator_name:
|
||||||
|
role = 'Owner'
|
||||||
|
elif is_admin:
|
||||||
|
role = 'Admin'
|
||||||
|
|
||||||
member_info = {
|
member_info = {
|
||||||
|
'group_id': group_id,
|
||||||
"name": email2nickname(email),
|
"name": email2nickname(email),
|
||||||
'email': email,
|
'email': email,
|
||||||
"contact_email": Profile.objects.get_contact_email_by_user(email),
|
"contact_email": Profile.objects.get_contact_email_by_user(email),
|
||||||
"login_id": login_id,
|
"login_id": login_id,
|
||||||
"avatar_url": request.build_absolute_uri(avatar_url),
|
"avatar_url": request.build_absolute_uri(avatar_url),
|
||||||
"is_admin": is_admin,
|
"is_admin": is_admin,
|
||||||
|
"role": role,
|
||||||
}
|
}
|
||||||
|
|
||||||
return member_info
|
return member_info
|
||||||
|
@@ -119,7 +119,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% for group in personal_groups %}
|
{% for group in personal_groups %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'sys_admin_group_info' group.id %}">{{ group.group_name }}</a></td>
|
<td><a href="{{ SITE_ROOT }}sysadmin/#groups/{{ group.id }}/libs/">{{ group.group_name }}</a></td>
|
||||||
<td>{{ group.role }}</td>
|
<td>{{ group.role }}</td>
|
||||||
<td>{{ group.timestamp|tsstr_sec }}</td>
|
<td>{{ group.timestamp|tsstr_sec }}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
@@ -598,7 +598,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/template" id="group-item-tmpl">
|
<script type="text/template" id="group-item-tmpl">
|
||||||
<td><a href="{{ SITE_ROOT }}sys/groupadmin/<%= id %>/"><%- name %></a></td>
|
<td><a href="{{ SITE_ROOT }}sysadmin/#groups/<%= id %>/libs/"><%- name %></a></td>
|
||||||
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(owner)); %>/"><%- owner %></a></td>
|
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(owner)); %>/"><%- owner %></a></td>
|
||||||
<td><time title="<%= time %>"><%= time_from_now %></time></td>
|
<td><time title="<%= time %>"><%= time_from_now %></time></td>
|
||||||
<td>
|
<td>
|
||||||
@@ -623,7 +623,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ SITE_ROOT }}sys/groupadmin/<%= group_id %>/" target="_blank"><%- group_name %></a>
|
<a href="{{ SITE_ROOT }}sysadmin/#groups/<%= group_id %>/libs/" target="_blank"><%- group_name %></a>
|
||||||
</td>
|
</td>
|
||||||
<% } %>
|
<% } %>
|
||||||
<td>
|
<td>
|
||||||
@@ -718,3 +718,123 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script type="text/template" id="groups-tabnav-tmpl">
|
||||||
|
<p class="path-bar">
|
||||||
|
<a class="normal" href="#groups/">{% trans "Groups" %}</a>
|
||||||
|
<span class="path-split">/</span>
|
||||||
|
</p>
|
||||||
|
<div class="tabnav ovhd">
|
||||||
|
<ul class="tabnav-tabs fleft">
|
||||||
|
<li class="tabnav-tab<% if (cur_tab == 'libs') { %> tabnav-tab-cur<% } %>">
|
||||||
|
<a href="#groups/<%= group_id %>/libs/">{% trans "Libraries" %}</a>
|
||||||
|
</li>
|
||||||
|
<li class="tabnav-tab<% if (cur_tab == 'members') { %> tabnav-tab-cur<% } %>">
|
||||||
|
<a href="#groups/<%= group_id %>/members/">{% trans "Members" %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<% if (cur_tab == 'members') { %>
|
||||||
|
<div class="fright">
|
||||||
|
<button id="js-add-group-member">{% trans "Add Member" %}</button>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/template" id="add-group-member-form-tmpl">
|
||||||
|
<form id="add-group-member-form" action="" method="post" class="hide">{% csrf_token %}
|
||||||
|
<h3 id="dialogTitle">{% trans "Add Member" %}</h3>
|
||||||
|
<label for="email">{% trans "Email" %}</label><br />
|
||||||
|
<input type="text" name="email" value="" id="email" /><br />
|
||||||
|
<p class="error hide"></p>
|
||||||
|
<input type="submit" class="submit" value="{% trans "Submit" %}" />
|
||||||
|
</form>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/template" id="group-libraries-tmpl">
|
||||||
|
<span class="loading-icon loading-tip"></span>
|
||||||
|
<table class="hide">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="4%"><!--icon--></th>
|
||||||
|
<th width="35%">{% trans "Name" %}</th>
|
||||||
|
<th width="20%">{% trans "Size" %}</th>
|
||||||
|
<th width="26%">{% trans "Shared By" %}</th>
|
||||||
|
<th width="15%">{% trans "Operations" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="empty-tips hide">
|
||||||
|
<h2 class="alc">{% trans "No libraries" %}</h2>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/template" id="group-library-item-tmpl">
|
||||||
|
<td>
|
||||||
|
<img src="<%= icon_url %>" title="<%= icon_title %>" alt="<%= icon_title %>" width="24" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<% if (enable_sys_admin_view_repo && is_pro && !encrypted) { %>
|
||||||
|
<a href="#libs/<%= repo_id %>/"><%- name %></a>
|
||||||
|
<% } else { %>
|
||||||
|
<%- name %>
|
||||||
|
<% } %>
|
||||||
|
</td>
|
||||||
|
<td><%- formatted_size %></td>
|
||||||
|
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(shared_by)); %>/"><%- shared_by %></a></td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="sf2-icon-delete sf2-x repo-unshare-btn op-icon vh" title="{% trans "Unshare" %}" aria-label="{% trans "Unshare" %}"></a>
|
||||||
|
</td>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/template" id="group-members-tmpl">
|
||||||
|
<span class="loading-icon loading-tip"></span>
|
||||||
|
<table class="hide">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="5%"></th>
|
||||||
|
<th width="25%">{% trans "Name" %}</th>
|
||||||
|
<th width="35%">{% trans "Email" %}</th>
|
||||||
|
<th width="25%">{% trans "Role" %}</th>
|
||||||
|
<th width="10%"><!--Operations--></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="empty-tips hide">
|
||||||
|
<h2 class="alc">{% trans "No members" %}</h2>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/template" id="group-member-item-tmpl">
|
||||||
|
<td><img src="<%= avatar_url %>" alt="" width="20" class="avatar" /></td>
|
||||||
|
<td><%- name %></td>
|
||||||
|
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(email)); %>/"><%- email %></a></td>
|
||||||
|
<td>
|
||||||
|
<% if (role == 'Owner') { %>
|
||||||
|
<span class="cur-role">{% trans "Owner" %}</span>
|
||||||
|
<% } else if (role == 'Admin') { %>
|
||||||
|
<span class="cur-role">{% trans "Admin" %}</span>
|
||||||
|
<span title="{% trans "Edit" %}" class="user-role-edit-icon sf2-icon-edit op-icon vh"></span>
|
||||||
|
<select name="role" class="user-role-select hide">
|
||||||
|
<option value="0">{% trans "Member" %}</option>
|
||||||
|
<option value="1" selected="selected">{% trans "Admin" %}</option>
|
||||||
|
</select>
|
||||||
|
<% } else if (role == 'Member') { %>
|
||||||
|
<span class="cur-role">{% trans "Member" %}</span>
|
||||||
|
<span title="{% trans "Edit" %}" class="user-role-edit-icon sf2-icon-edit op-icon vh"></span>
|
||||||
|
<select name="role" class="user-role-select hide">
|
||||||
|
<option value="0" selected="selected">{% trans "Member" %}</option>
|
||||||
|
<option value="1">{% trans "Admin" %}</option>
|
||||||
|
</select>
|
||||||
|
<% } %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<% if (role != 'Owner') { %>
|
||||||
|
<a href="#" class="sf2-icon-delete sf2-x member-delete-btn op-icon vh" title="{% trans "Delete" %}" aria-label="{% trans "Delete" %}"></a>
|
||||||
|
<% } %>
|
||||||
|
</td>
|
||||||
|
</script>
|
||||||
|
@@ -1,90 +0,0 @@
|
|||||||
{% extends "sysadmin/base.html" %}
|
|
||||||
{% load i18n group_avatar_tags avatar_tags seahub_tags %}
|
|
||||||
{% load url from future %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block right_panel %}
|
|
||||||
|
|
||||||
<p class="path-bar">
|
|
||||||
<a class="normal" href="{{ SITE_ROOT }}sysadmin/#groups/">Groups</a>
|
|
||||||
<span class="path-split">/</span>
|
|
||||||
{{ group.group_name }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div id="tabs" class="tab-tabs">
|
|
||||||
<div class="hd ovhd">
|
|
||||||
<ul class="tab-tabs-nav fleft">
|
|
||||||
<li class="tab"><a href="#library" class="a">{% trans "Libraries" %}</a></li>
|
|
||||||
<li class="tab"><a href="#member" class="a">{% trans "Members" %}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="library">
|
|
||||||
{% if repos %}
|
|
||||||
<table class="repo-list">
|
|
||||||
<tr>
|
|
||||||
<th width="4%"><!--icon--></th>
|
|
||||||
<th width="35%">{% trans "Name" %}</th>
|
|
||||||
<th width="20%">{% trans "Size" %}</th>
|
|
||||||
<th width="26%">{% trans "Shared By" %}</th>
|
|
||||||
<th width="15%">{% trans "Operations" %}</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for repo in repos %}
|
|
||||||
<tr>
|
|
||||||
{% if repo.encrypted %}
|
|
||||||
<td><img src="{{MEDIA_URL}}img/sync-folder-encrypt-20.png" title="{% trans "Encrypted"%}" alt="{% trans "library icon" %}" /></td>
|
|
||||||
{% elif repo.permission == 'r' %}
|
|
||||||
<td><img src="{{MEDIA_URL}}img/folder-no-write-20.png" title="{% trans "Read-Only"%}" alt="{% trans "library icon" %}" /></td>
|
|
||||||
{% else %}
|
|
||||||
<td><img src="{{MEDIA_URL}}img/sync-folder-20.png?t=1387267140" title="{% trans "Read-Write" %}" alt="{% trans "library icon" %}" /></td>
|
|
||||||
{% endif %}
|
|
||||||
{% if repo.encrypted %}
|
|
||||||
<td>{{ repo.name }}</td>
|
|
||||||
{% elif enable_sys_admin_view_repo %}
|
|
||||||
<td><a href="{% url 'sys_admin_repo' repo.id %}">{{ repo.name }}</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td>{{ repo.name }}</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>{{ repo.size|filesizeformat }}</td>
|
|
||||||
<td><a href="{% url 'user_info' repo.user %}">{{ repo.user }}</a></td>
|
|
||||||
<td data-id="{{ repo.id }}" data-name="{{repo.name}}">
|
|
||||||
<div><a href="#" data-url="{% url "sys_repo_delete" repo.id %}" data-target="{{ repo.name }}" class="repo-delete-btn op-icon sf2-icon-delete vh" title="{% trans "Delete" %}"></a></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<div class="empty-tips">
|
|
||||||
<h2 class="alc">{% trans "No library has shared to this group" %}</h2>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="member">
|
|
||||||
<ul class="user-list">
|
|
||||||
{% for m in members %}
|
|
||||||
<li class="user ovhd">
|
|
||||||
<a href="{% url 'user_info' m.user_name %}" class="pic fleft">{% avatar m.user_name 48 %}</a>
|
|
||||||
<div class="txt fright">
|
|
||||||
<a class="name" href="{% url 'user_info' m.user_name %}">{{ m.user_name|email2nickname }}</a>{% if m.is_staff %}<span> ({% trans "admin" %})</span>{% endif %}
|
|
||||||
<p>{{ m.user_name }}</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block extra_script %}
|
|
||||||
<script type="text/javascript">
|
|
||||||
addConfirmTo($('.repo-delete-btn'), {
|
|
||||||
'title': "{% trans "Delete Library" %}",
|
|
||||||
'con': "{% trans "Are you sure you want to delete %s ?" %}",
|
|
||||||
'post': true
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@@ -256,7 +256,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% for group in personal_groups %}
|
{% for group in personal_groups %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'sys_admin_group_info' group.id %}">{{ group.group_name }}</a></td>
|
<td><a href="{{ SITE_ROOT }}sysadmin/#groups/{{ group.id }}/libs/">{{ group.group_name }}</a></td>
|
||||||
<td>{{ group.role }}</td>
|
<td>{{ group.role }}</td>
|
||||||
<td>{{ group.timestamp|tsstr_sec }}</td>
|
<td>{{ group.timestamp|tsstr_sec }}</td>
|
||||||
<td><a href="#" data-url="{% url 'group_remove' group.id %}" data-target="{{ group.group_name }}" class="rm-grp op vh">{% trans "Delete" %}</a></td>
|
<td><a href="#" data-url="{% url 'group_remove' group.id %}" data-target="{{ group.group_name }}" class="rm-grp op vh">{% trans "Delete" %}</a></td>
|
||||||
|
@@ -48,6 +48,8 @@ from seahub.api2.endpoints.admin.library_dirents import AdminLibraryDirents, Adm
|
|||||||
from seahub.api2.endpoints.admin.system_library import AdminSystemLibrary
|
from seahub.api2.endpoints.admin.system_library import AdminSystemLibrary
|
||||||
from seahub.api2.endpoints.admin.trash_libraries import AdminTrashLibraries, AdminTrashLibrary
|
from seahub.api2.endpoints.admin.trash_libraries import AdminTrashLibraries, AdminTrashLibrary
|
||||||
from seahub.api2.endpoints.admin.groups import AdminGroups, AdminGroup
|
from seahub.api2.endpoints.admin.groups import AdminGroups, AdminGroup
|
||||||
|
from seahub.api2.endpoints.admin.group_libraries import AdminGroupLibraries, AdminGroupLibrary
|
||||||
|
from seahub.api2.endpoints.admin.group_members import AdminGroupMembers, AdminGroupMember
|
||||||
from seahub.api2.endpoints.admin.shares import AdminShares
|
from seahub.api2.endpoints.admin.shares import AdminShares
|
||||||
|
|
||||||
# Uncomment the next two lines to enable the admin:
|
# Uncomment the next two lines to enable the admin:
|
||||||
@@ -214,6 +216,10 @@ urlpatterns = patterns(
|
|||||||
url(r'^api/v2.1/admin/libraries/(?P<repo_id>[-0-9a-f]{36})/dirents/$', AdminLibraryDirents.as_view(), name='api-v2.1-admin-library-dirents'),
|
url(r'^api/v2.1/admin/libraries/(?P<repo_id>[-0-9a-f]{36})/dirents/$', AdminLibraryDirents.as_view(), name='api-v2.1-admin-library-dirents'),
|
||||||
url(r'^api/v2.1/admin/groups/$', AdminGroups.as_view(), name='api-v2.1-admin-groups'),
|
url(r'^api/v2.1/admin/groups/$', AdminGroups.as_view(), name='api-v2.1-admin-groups'),
|
||||||
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/$', AdminGroup.as_view(), name='api-v2.1-admin-group'),
|
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/$', AdminGroup.as_view(), name='api-v2.1-admin-group'),
|
||||||
|
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/libraries/$', AdminGroupLibraries.as_view(), name='api-v2.1-admin-group-libraries'),
|
||||||
|
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/libraries/(?P<repo_id>[-0-9a-f]{36})/$', AdminGroupLibrary.as_view(), name='api-v2.1-admin-group-library'),
|
||||||
|
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/members/$', AdminGroupMembers.as_view(), name='api-v2.1-admin-group-members'),
|
||||||
|
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/members/(?P<email>[^/]+)/$', AdminGroupMember.as_view(), name='api-v2.1-admin-group-member'),
|
||||||
url(r'^api/v2.1/admin/libraries/(?P<repo_id>[-0-9a-f]{36})/dirent/$', AdminLibraryDirent.as_view(), name='api-v2.1-admin-library-dirent'),
|
url(r'^api/v2.1/admin/libraries/(?P<repo_id>[-0-9a-f]{36})/dirent/$', AdminLibraryDirent.as_view(), name='api-v2.1-admin-library-dirent'),
|
||||||
url(r'^api/v2.1/admin/system-library/$', AdminSystemLibrary.as_view(), name='api-v2.1-admin-system-library'),
|
url(r'^api/v2.1/admin/system-library/$', AdminSystemLibrary.as_view(), name='api-v2.1-admin-system-library'),
|
||||||
url(r'^api/v2.1/admin/trash-libraries/$', AdminTrashLibraries.as_view(), name='api-v2.1-admin-trash-libraries'),
|
url(r'^api/v2.1/admin/trash-libraries/$', AdminTrashLibraries.as_view(), name='api-v2.1-admin-trash-libraries'),
|
||||||
@@ -253,7 +259,6 @@ urlpatterns = patterns(
|
|||||||
url(r'^sys/useradmin/ldap/imported$', sys_user_admin_ldap_imported, name='sys_useradmin_ldap_imported'),
|
url(r'^sys/useradmin/ldap/imported$', sys_user_admin_ldap_imported, name='sys_useradmin_ldap_imported'),
|
||||||
url(r'^sys/useradmin/admins/$', sys_user_admin_admins, name='sys_useradmin_admins'),
|
url(r'^sys/useradmin/admins/$', sys_user_admin_admins, name='sys_useradmin_admins'),
|
||||||
url(r'^sys/groupadmin/export-excel/$', sys_group_admin_export_excel, name='sys_group_admin_export_excel'),
|
url(r'^sys/groupadmin/export-excel/$', sys_group_admin_export_excel, name='sys_group_admin_export_excel'),
|
||||||
url(r'^sys/groupadmin/(?P<group_id>\d+)/$', sys_admin_group_info, name='sys_admin_group_info'),
|
|
||||||
url(r'^sys/orgadmin/$', sys_org_admin, name='sys_org_admin'),
|
url(r'^sys/orgadmin/$', sys_org_admin, name='sys_org_admin'),
|
||||||
url(r'^sys/orgadmin/search/$', sys_org_search, name='sys_org_search'),
|
url(r'^sys/orgadmin/search/$', sys_org_search, name='sys_org_search'),
|
||||||
url(r'^sys/orgadmin/(?P<org_id>\d+)/set_quota/$', sys_org_set_quota, name='sys_org_set_quota'),
|
url(r'^sys/orgadmin/(?P<org_id>\d+)/set_quota/$', sys_org_set_quota, name='sys_org_set_quota'),
|
||||||
|
@@ -1064,26 +1064,6 @@ def sys_group_admin_export_excel(request):
|
|||||||
wb.save(response)
|
wb.save(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@login_required
|
|
||||||
@sys_staff_required
|
|
||||||
def sys_admin_group_info(request, group_id):
|
|
||||||
|
|
||||||
group_id = int(group_id)
|
|
||||||
group = get_group(group_id)
|
|
||||||
org_id = request.GET.get('org_id', None)
|
|
||||||
if org_id:
|
|
||||||
repos = seafile_api.get_org_group_repos(org_id, group_id)
|
|
||||||
else:
|
|
||||||
repos = seafile_api.get_repos_by_group(group_id)
|
|
||||||
members = get_group_members(group_id)
|
|
||||||
|
|
||||||
return render_to_response('sysadmin/sys_admin_group_info.html', {
|
|
||||||
'group': group,
|
|
||||||
'repos': repos,
|
|
||||||
'members': members,
|
|
||||||
'enable_sys_admin_view_repo': ENABLE_SYS_ADMIN_VIEW_REPO,
|
|
||||||
}, context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@sys_staff_required
|
@sys_staff_required
|
||||||
def sys_org_admin(request):
|
def sys_org_admin(request):
|
||||||
|
@@ -167,7 +167,7 @@ define([
|
|||||||
var err_str = '';
|
var err_str = '';
|
||||||
if (data.failed.length > 0) {
|
if (data.failed.length > 0) {
|
||||||
$(data.failed).each(function(index, item) {
|
$(data.failed).each(function(index, item) {
|
||||||
err_str += item.email + ': ' + item.error_msg + '<br />';
|
err_str += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '<br />';
|
||||||
});
|
});
|
||||||
_this.$error.html(err_str).show();
|
_this.$error.html(err_str).show();
|
||||||
}
|
}
|
||||||
|
@@ -270,7 +270,7 @@ define([
|
|||||||
if (data.failed.length > 0) {
|
if (data.failed.length > 0) {
|
||||||
var err_msg = '';
|
var err_msg = '';
|
||||||
$(data.failed).each(function(index, item) {
|
$(data.failed).each(function(index, item) {
|
||||||
err_msg += item.email + ': ' + item.error_msg + '<br />';
|
err_msg += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '<br />';
|
||||||
});
|
});
|
||||||
$error.html(err_msg).removeClass('hide');
|
$error.html(err_msg).removeClass('hide');
|
||||||
Common.enableButton($submitBtn);
|
Common.enableButton($submitBtn);
|
||||||
|
@@ -179,6 +179,10 @@ define([
|
|||||||
case 'admin-library-dirents': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/dirents/';
|
case 'admin-library-dirents': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/dirents/';
|
||||||
case 'admin-groups': return siteRoot + 'api/v2.1/admin/groups/';
|
case 'admin-groups': return siteRoot + 'api/v2.1/admin/groups/';
|
||||||
case 'admin-group': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/';
|
case 'admin-group': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/';
|
||||||
|
case 'admin-group-libraries': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/libraries/';
|
||||||
|
case 'admin-group-library': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/libraries/' + options.repo_id + '/';
|
||||||
|
case 'admin-group-members': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/members/';
|
||||||
|
case 'admin-group-member': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/members/' + options.email+ '/';
|
||||||
case 'admin-system-library': return siteRoot + 'api/v2.1/admin/system-library/';
|
case 'admin-system-library': return siteRoot + 'api/v2.1/admin/system-library/';
|
||||||
case 'admin-trash-libraries': return siteRoot + 'api/v2.1/admin/trash-libraries/';
|
case 'admin-trash-libraries': return siteRoot + 'api/v2.1/admin/trash-libraries/';
|
||||||
case 'admin-trash-library': return siteRoot + 'api/v2.1/admin/trash-libraries/' + options.repo_id + '/';
|
case 'admin-trash-library': return siteRoot + 'api/v2.1/admin/trash-libraries/' + options.repo_id + '/';
|
||||||
@@ -286,6 +290,16 @@ define([
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getLibIconTitle: function(is_encrypted, is_readonly) {
|
||||||
|
if (is_encrypted) {
|
||||||
|
return gettext("Encrypted library");
|
||||||
|
} else if (is_readonly) {
|
||||||
|
return gettext("Read-Only library");
|
||||||
|
} else {
|
||||||
|
return gettext("Read-Write library");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
isHiDPI: function() {
|
isHiDPI: function() {
|
||||||
var pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 1;
|
var pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 1;
|
||||||
if (pixelRatio > 1) {
|
if (pixelRatio > 1) {
|
||||||
|
29
static/scripts/sysadmin-app/collection/group-members.js
Normal file
29
static/scripts/sysadmin-app/collection/group-members.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
define([
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'common',
|
||||||
|
'sysadmin-app/models/group-member'
|
||||||
|
], function(_, Backbone, Common, GroupMemberModel) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GroupMemberCollection = Backbone.Collection.extend({
|
||||||
|
|
||||||
|
model: GroupMemberModel,
|
||||||
|
|
||||||
|
setGroupId: function(group_id) {
|
||||||
|
this.group_id = group_id;
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: function (data) {
|
||||||
|
this.group_name= data.group_name;
|
||||||
|
return data.members; // return the array
|
||||||
|
},
|
||||||
|
|
||||||
|
url: function () {
|
||||||
|
return Common.getUrl({name: 'admin-group-members', group_id: this.group_id});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return GroupMemberCollection;
|
||||||
|
});
|
29
static/scripts/sysadmin-app/collection/group-repos.js
Normal file
29
static/scripts/sysadmin-app/collection/group-repos.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
define([
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'common',
|
||||||
|
'sysadmin-app/models/group-repo'
|
||||||
|
], function(_, Backbone, Common, GroupRepoModel) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GroupRepoCollection = Backbone.Collection.extend({
|
||||||
|
|
||||||
|
model: GroupRepoModel,
|
||||||
|
|
||||||
|
setGroupId: function(group_id) {
|
||||||
|
this.group_id = group_id;
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: function (data) {
|
||||||
|
this.group_name= data.group_name;
|
||||||
|
return data.libraries; // return the array
|
||||||
|
},
|
||||||
|
|
||||||
|
url: function () {
|
||||||
|
return Common.getUrl({name: 'admin-group-libraries', group_id: this.group_id});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return GroupRepoCollection;
|
||||||
|
});
|
12
static/scripts/sysadmin-app/models/group-member.js
Normal file
12
static/scripts/sysadmin-app/models/group-member.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
define([
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'common'
|
||||||
|
], function(_, Backbone, Common) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GroupMember = Backbone.Model.extend({});
|
||||||
|
|
||||||
|
return GroupMember;
|
||||||
|
});
|
25
static/scripts/sysadmin-app/models/group-repo.js
Normal file
25
static/scripts/sysadmin-app/models/group-repo.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
define([
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'common'
|
||||||
|
], function(_, Backbone, Common) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GroupRepo = Backbone.Model.extend({
|
||||||
|
|
||||||
|
getIconUrl: function(size) {
|
||||||
|
var is_encrypted = this.get('encrypted');
|
||||||
|
var is_readonly = this.get('permission') == "r" ? true : false;
|
||||||
|
return Common.getLibIconUrl(is_encrypted, is_readonly, size);
|
||||||
|
},
|
||||||
|
|
||||||
|
getIconTitle: function() {
|
||||||
|
var is_encrypted = this.get('encrypted');
|
||||||
|
var is_readonly = this.get('permission') == "r" ? true : false;
|
||||||
|
return Common.getLibIconTitle(is_encrypted, is_readonly);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return GroupRepo;
|
||||||
|
});
|
@@ -12,14 +12,8 @@ define([
|
|||||||
},
|
},
|
||||||
|
|
||||||
getIconTitle: function() {
|
getIconTitle: function() {
|
||||||
var icon_title = '';
|
var is_encrypted = this.get('encrypted');
|
||||||
if (this.get('encrypted')) {
|
return Common.getLibIconTitle(is_encrypted, false);
|
||||||
icon_title = gettext("Encrypted library");
|
|
||||||
} else {
|
|
||||||
icon_title = gettext("Read-Write library");
|
|
||||||
}
|
|
||||||
|
|
||||||
return icon_title;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -16,12 +16,14 @@ define([
|
|||||||
'sysadmin-app/views/dir',
|
'sysadmin-app/views/dir',
|
||||||
'sysadmin-app/views/groups',
|
'sysadmin-app/views/groups',
|
||||||
'sysadmin-app/views/search-groups',
|
'sysadmin-app/views/search-groups',
|
||||||
|
'sysadmin-app/views/group-repos',
|
||||||
|
'sysadmin-app/views/group-members',
|
||||||
'app/views/account'
|
'app/views/account'
|
||||||
], function($, Backbone, Common, SideNavView, DashboardView,
|
], function($, Backbone, Common, SideNavView, DashboardView,
|
||||||
DesktopDevicesView, MobileDevicesView, DeviceErrorsView,
|
DesktopDevicesView, MobileDevicesView, DeviceErrorsView,
|
||||||
ReposView, SearchReposView, SystemReposView, TrashReposView,
|
ReposView, SearchReposView, SystemReposView, TrashReposView,
|
||||||
SearchTrashReposView, DirView, GroupsView, SearchGroupsView,
|
SearchTrashReposView, DirView, GroupsView, SearchGroupsView,
|
||||||
AccountView) {
|
GroupReposView, GroupMembersView, AccountView) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@@ -40,6 +42,8 @@ define([
|
|||||||
'libs/:repo_id(/*path)': 'showLibraryDir',
|
'libs/:repo_id(/*path)': 'showLibraryDir',
|
||||||
'groups/': 'showGroups',
|
'groups/': 'showGroups',
|
||||||
'search-groups/': 'showSearchGroups',
|
'search-groups/': 'showSearchGroups',
|
||||||
|
'groups/:group_id/libs/': 'showGroupLibraries',
|
||||||
|
'groups/:group_id/members/': 'showGroupMembers',
|
||||||
// Default
|
// Default
|
||||||
'*actions': 'showDashboard'
|
'*actions': 'showDashboard'
|
||||||
},
|
},
|
||||||
@@ -69,6 +73,8 @@ define([
|
|||||||
|
|
||||||
this.groupsView = new GroupsView();
|
this.groupsView = new GroupsView();
|
||||||
this.searchGroupsView = new SearchGroupsView();
|
this.searchGroupsView = new SearchGroupsView();
|
||||||
|
this.groupReposView = new GroupReposView();
|
||||||
|
this.groupMembersView = new GroupMembersView();
|
||||||
|
|
||||||
app.ui.accountView = this.accountView = new AccountView();
|
app.ui.accountView = this.accountView = new AccountView();
|
||||||
|
|
||||||
@@ -205,6 +211,18 @@ define([
|
|||||||
this.searchGroupsView.show({
|
this.searchGroupsView.show({
|
||||||
'name': decodeURIComponent(group_name)
|
'name': decodeURIComponent(group_name)
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showGroupLibraries: function(group_id) {
|
||||||
|
this.switchCurrentView(this.groupReposView);
|
||||||
|
this.sideNavView.setCurTab('groups');
|
||||||
|
this.groupReposView.show(group_id);
|
||||||
|
},
|
||||||
|
|
||||||
|
showGroupMembers: function(group_id) {
|
||||||
|
this.switchCurrentView(this.groupMembersView);
|
||||||
|
this.sideNavView.setCurTab('groups');
|
||||||
|
this.groupMembersView.show(group_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
118
static/scripts/sysadmin-app/views/group-member.js
Normal file
118
static/scripts/sysadmin-app/views/group-member.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'common',
|
||||||
|
'app/views/widgets/hl-item-view'
|
||||||
|
], function($, _, Backbone, Common, HLItemView) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GroupMemberView = HLItemView.extend({
|
||||||
|
|
||||||
|
tagName: 'tr',
|
||||||
|
|
||||||
|
template: _.template($('#group-member-item-tmpl').html()),
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .user-role-edit-icon': 'showEdit',
|
||||||
|
'change .user-role-select': 'editRole',
|
||||||
|
'click .member-delete-btn': 'deleteGroupMember'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
HLItemView.prototype.initialize.call(this);
|
||||||
|
this.listenTo(this.model, 'change', this.render);
|
||||||
|
|
||||||
|
var _this = this;
|
||||||
|
$(document).on('click', function(e) {
|
||||||
|
var target = e.target || event.srcElement;
|
||||||
|
if (!_this.$('.user-role-edit-icon, .user-role-select').is(target)) {
|
||||||
|
_this.$('.cur-role, .user-role-edit-icon').show();
|
||||||
|
_this.$('.user-role-select').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showEdit: function() {
|
||||||
|
this.$('.cur-role, .user-role-edit-icon').hide();
|
||||||
|
this.$('.user-role-select').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': 'admin-group-member',
|
||||||
|
'group_id': _this.model.get('group_id'),
|
||||||
|
'email': _this.model.get('email')
|
||||||
|
}),
|
||||||
|
type: 'put',
|
||||||
|
dataType: 'json',
|
||||||
|
beforeSend: Common.prepareCSRFToken,
|
||||||
|
data: {
|
||||||
|
'is_admin': is_admin
|
||||||
|
},
|
||||||
|
success: function(data) {
|
||||||
|
_this.model.set({
|
||||||
|
'is_admin': data['is_admin'],
|
||||||
|
'role': data['role']
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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(err_msg, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteGroupMember: function() {
|
||||||
|
var _this = this;
|
||||||
|
var email = this.model.get('email');
|
||||||
|
var popupTitle = gettext("Delete Member");
|
||||||
|
var popupContent = gettext("Are you sure you want to delete %s ?").replace('%s', '<span class="op-target ellipsis ellipsis-op-target" title="' + Common.HTMLescape(email) + '">' + Common.HTMLescape(email) + '</span>');
|
||||||
|
var yesCallback = function() {
|
||||||
|
$.ajax({
|
||||||
|
url: Common.getUrl({
|
||||||
|
'name': 'admin-group-member',
|
||||||
|
'group_id': _this.model.get('group_id'),
|
||||||
|
'email': email
|
||||||
|
}),
|
||||||
|
type: 'DELETE',
|
||||||
|
beforeSend: Common.prepareCSRFToken,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function() {
|
||||||
|
_this.$el.remove();
|
||||||
|
var msg = gettext("Successfully deleted member {placeholder}").replace('{placeholder}', email);
|
||||||
|
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() {
|
||||||
|
this.$el.html(this.template(this.model.toJSON()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return GroupMemberView;
|
||||||
|
});
|
187
static/scripts/sysadmin-app/views/group-members.js
Normal file
187
static/scripts/sysadmin-app/views/group-members.js
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'common',
|
||||||
|
'sysadmin-app/views/group-member',
|
||||||
|
'sysadmin-app/collection/group-members'
|
||||||
|
], function($, _, Backbone, Common, GroupMemberView, GroupMemberCollection) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GroupMembersView = Backbone.View.extend({
|
||||||
|
|
||||||
|
id: 'admin-groups',
|
||||||
|
|
||||||
|
tabNavTemplate: _.template($("#groups-tabnav-tmpl").html()),
|
||||||
|
template: _.template($("#group-members-tmpl").html()),
|
||||||
|
addMemberFormTemplate: _.template($('#add-group-member-form-tmpl').html()),
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
this.groupMemberCollection = new GroupMemberCollection();
|
||||||
|
this.listenTo(this.groupMemberCollection, 'add', this.addOne);
|
||||||
|
this.listenTo(this.groupMemberCollection, 'reset', this.reset);
|
||||||
|
},
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click #js-add-group-member': 'addGroupMember'
|
||||||
|
},
|
||||||
|
|
||||||
|
addGroupMember: function () {
|
||||||
|
var $form = $(this.addMemberFormTemplate()),
|
||||||
|
_this = this;
|
||||||
|
|
||||||
|
$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 user or enter email and press Enter")
|
||||||
|
}));
|
||||||
|
|
||||||
|
$form.submit(function() {
|
||||||
|
var group_id = _this.groupMemberCollection.group_id;
|
||||||
|
var emails = $.trim($('[name="email"]', $form).val());
|
||||||
|
var $error = $('.error', $form);
|
||||||
|
var $submitBtn = $('[type="submit"]', $form);
|
||||||
|
|
||||||
|
if (!emails) {
|
||||||
|
$error.html(gettext("Email 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);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: Common.getUrl({
|
||||||
|
'name': 'admin-group-members',
|
||||||
|
'group_id': group_id
|
||||||
|
}),
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
beforeSend: Common.prepareCSRFToken,
|
||||||
|
traditional: true,
|
||||||
|
data: {'email': input_emails},
|
||||||
|
success: function(data) {
|
||||||
|
if (data.success.length > 0) {
|
||||||
|
_this.groupMemberCollection.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) + '<br />';
|
||||||
|
});
|
||||||
|
$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;
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var group_id = this.groupMemberCollection.group_id;
|
||||||
|
this.$el.html(this.tabNavTemplate({'cur_tab': 'members', 'group_id': group_id}) + this.template());
|
||||||
|
|
||||||
|
this.$table = this.$('table');
|
||||||
|
this.$tableBody = $('tbody', this.$table);
|
||||||
|
this.$loadingTip = this.$('.loading-tip');
|
||||||
|
this.$emptyTip = this.$('.empty-tips');
|
||||||
|
},
|
||||||
|
|
||||||
|
initPage: function() {
|
||||||
|
this.$table.hide();
|
||||||
|
this.$tableBody.empty();
|
||||||
|
this.$loadingTip.show();
|
||||||
|
this.$emptyTip.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
this.$el.detach();
|
||||||
|
this.attached = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(group_id) {
|
||||||
|
if (!this.attached) {
|
||||||
|
this.attached = true;
|
||||||
|
$("#right-panel").html(this.$el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// init collection
|
||||||
|
this.groupMemberCollection.setGroupId(group_id);
|
||||||
|
this.render();
|
||||||
|
this.showGroupMembers();
|
||||||
|
},
|
||||||
|
|
||||||
|
showGroupMembers: function() {
|
||||||
|
this.initPage();
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
this.groupMemberCollection.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.");
|
||||||
|
}
|
||||||
|
Common.feedback(err_msg, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.$loadingTip.hide();
|
||||||
|
if (this.groupMemberCollection.length > 0) {
|
||||||
|
this.groupMemberCollection.each(this.addOne, this);
|
||||||
|
this.$table.show();
|
||||||
|
} else {
|
||||||
|
this.$emptyTip.show();
|
||||||
|
}
|
||||||
|
this.$('.path-bar').append(this.groupMemberCollection.group_name);
|
||||||
|
},
|
||||||
|
|
||||||
|
addOne: function(item, collection, options) {
|
||||||
|
var view = new GroupMemberView({model: item});
|
||||||
|
if (options.prepend) {
|
||||||
|
this.$tableBody.prepend(view.render().el);
|
||||||
|
} else {
|
||||||
|
this.$tableBody.append(view.render().el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return GroupMembersView;
|
||||||
|
|
||||||
|
});
|
75
static/scripts/sysadmin-app/views/group-repo.js
Normal file
75
static/scripts/sysadmin-app/views/group-repo.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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($('#group-library-item-tmpl').html()),
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .repo-unshare-btn': 'unshareGroupLibrary'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
HLItemView.prototype.initialize.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
unshareGroupLibrary: function() {
|
||||||
|
var _this = this;
|
||||||
|
var repo_name = this.model.get('name');
|
||||||
|
var popupTitle = gettext("Unshare Library");
|
||||||
|
var popupContent = gettext("Are you sure you want to unshare %s ?").replace('%s', '<span class="op-target ellipsis ellipsis-op-target" title="' + Common.HTMLescape(repo_name) + '">' + Common.HTMLescape(repo_name) + '</span>');
|
||||||
|
var yesCallback = function() {
|
||||||
|
$.ajax({
|
||||||
|
url: Common.getUrl({
|
||||||
|
'name': 'admin-group-library',
|
||||||
|
'group_id': _this.model.get('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 unshared 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() ? 96 : 24,
|
||||||
|
icon_url = this.model.getIconUrl(icon_size);
|
||||||
|
|
||||||
|
data['icon_url'] = icon_url;
|
||||||
|
data['icon_title'] = this.model.getIconTitle();
|
||||||
|
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;
|
||||||
|
});
|
102
static/scripts/sysadmin-app/views/group-repos.js
Normal file
102
static/scripts/sysadmin-app/views/group-repos.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'common',
|
||||||
|
'sysadmin-app/views/group-repo',
|
||||||
|
'sysadmin-app/collection/group-repos'
|
||||||
|
], function($, _, Backbone, Common, GroupRepoView, GroupRepoCollection) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GroupReposView = Backbone.View.extend({
|
||||||
|
|
||||||
|
id: 'admin-groups',
|
||||||
|
|
||||||
|
tabNavTemplate: _.template($("#groups-tabnav-tmpl").html()),
|
||||||
|
template: _.template($("#group-libraries-tmpl").html()),
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
this.groupRepoCollection = new GroupRepoCollection();
|
||||||
|
this.listenTo(this.groupRepoCollection, 'add', this.addOne);
|
||||||
|
this.listenTo(this.groupRepoCollection, 'reset', this.reset);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var group_id = this.groupRepoCollection.group_id;
|
||||||
|
this.$el.html(this.tabNavTemplate({'cur_tab': 'libs', 'group_id': group_id}) + this.template());
|
||||||
|
|
||||||
|
this.$table = this.$('table');
|
||||||
|
this.$tableBody = $('tbody', this.$table);
|
||||||
|
this.$loadingTip = this.$('.loading-tip');
|
||||||
|
this.$emptyTip = this.$('.empty-tips');
|
||||||
|
},
|
||||||
|
|
||||||
|
initPage: function() {
|
||||||
|
this.$table.hide();
|
||||||
|
this.$tableBody.empty();
|
||||||
|
this.$loadingTip.show();
|
||||||
|
this.$emptyTip.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
this.$el.detach();
|
||||||
|
this.attached = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(group_id) {
|
||||||
|
if (!this.attached) {
|
||||||
|
this.attached = true;
|
||||||
|
$("#right-panel").html(this.$el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// init collection
|
||||||
|
this.groupRepoCollection.setGroupId(group_id);
|
||||||
|
this.render();
|
||||||
|
this.showGroupLibraries();
|
||||||
|
},
|
||||||
|
|
||||||
|
showGroupLibraries: function() {
|
||||||
|
this.initPage();
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
Common.feedback(err_msg, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.$loadingTip.hide();
|
||||||
|
if (this.groupRepoCollection.length > 0) {
|
||||||
|
this.groupRepoCollection.each(this.addOne, this);
|
||||||
|
this.$table.show();
|
||||||
|
} else {
|
||||||
|
this.$emptyTip.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$('.path-bar').append(this.groupRepoCollection.group_name);
|
||||||
|
},
|
||||||
|
|
||||||
|
addOne: function(library) {
|
||||||
|
var view = new GroupRepoView({model: library});
|
||||||
|
this.$tableBody.append(view.render().el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return GroupReposView;
|
||||||
|
|
||||||
|
});
|
64
tests/api/endpoints/admin/test_group_libraries.py
Normal file
64
tests/api/endpoints/admin/test_group_libraries.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from seaserv import seafile_api
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from seahub.test_utils import BaseTestCase
|
||||||
|
|
||||||
|
class GroupLibrariesTest(BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user_name = self.user.username
|
||||||
|
self.admin_name = self.admin.username
|
||||||
|
self.group_id = self.group.id
|
||||||
|
self.repo_id = self.repo.id
|
||||||
|
|
||||||
|
seafile_api.set_group_repo(self.repo_id, self.group_id,
|
||||||
|
self.admin.username, 'r')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.remove_group()
|
||||||
|
|
||||||
|
def test_can_get(self):
|
||||||
|
self.login_as(self.admin)
|
||||||
|
url = reverse('api-v2.1-admin-group-libraries', args=[self.group_id])
|
||||||
|
resp = self.client.get(url)
|
||||||
|
|
||||||
|
json_resp = json.loads(resp.content)
|
||||||
|
assert json_resp['libraries'][0]['repo_id'] == self.repo_id
|
||||||
|
assert json_resp['group_id'] == self.group_id
|
||||||
|
|
||||||
|
def test_can_not_get_if_not_admin(self):
|
||||||
|
self.login_as(self.user)
|
||||||
|
url = reverse('api-v2.1-admin-group-libraries', args=[self.group_id])
|
||||||
|
resp = self.client.get(url)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
def test_can_unshare(self):
|
||||||
|
|
||||||
|
# make sure repo is shared to group
|
||||||
|
repos = seafile_api.get_repos_by_group(self.group_id)
|
||||||
|
assert len(repos) == 1
|
||||||
|
|
||||||
|
self.login_as(self.admin)
|
||||||
|
url = reverse('api-v2.1-admin-group-library', args=[self.group_id, self.repo_id])
|
||||||
|
resp = self.client.delete(url)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
|
||||||
|
# make sure repo is unshared
|
||||||
|
repos = seafile_api.get_repos_by_group(self.group_id)
|
||||||
|
assert len(repos) == 0
|
||||||
|
|
||||||
|
def test_can_not_unshare_if_not_admin(self):
|
||||||
|
|
||||||
|
# make sure repo is shared to group
|
||||||
|
repos = seafile_api.get_repos_by_group(self.group_id)
|
||||||
|
assert len(repos) == 1
|
||||||
|
|
||||||
|
self.login_as(self.user)
|
||||||
|
url = reverse('api-v2.1-admin-group-library', args=[self.group_id, self.repo_id])
|
||||||
|
resp = self.client.delete(url)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
# make sure repo is unshared
|
||||||
|
repos = seafile_api.get_repos_by_group(self.group_id)
|
||||||
|
assert len(repos) == 1
|
104
tests/api/endpoints/admin/test_group_members.py
Normal file
104
tests/api/endpoints/admin/test_group_members.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from seaserv import ccnet_api
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from seahub.test_utils import BaseTestCase
|
||||||
|
|
||||||
|
class GroupMembersTest(BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user_name = self.user.username
|
||||||
|
self.admin_name = self.admin.username
|
||||||
|
self.group_id = self.group.id
|
||||||
|
self.repo_id = self.repo.id
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.remove_group()
|
||||||
|
|
||||||
|
def test_can_get(self):
|
||||||
|
self.login_as(self.admin)
|
||||||
|
url = reverse('api-v2.1-admin-group-members',
|
||||||
|
args=[self.group_id])
|
||||||
|
resp = self.client.get(url)
|
||||||
|
|
||||||
|
json_resp = json.loads(resp.content)
|
||||||
|
assert json_resp['members'][0]['email'] == self.user_name
|
||||||
|
assert json_resp['group_id'] == self.group_id
|
||||||
|
|
||||||
|
def test_can_not_get_if_not_admin(self):
|
||||||
|
self.login_as(self.user)
|
||||||
|
url = reverse('api-v2.1-admin-group-members',
|
||||||
|
args=[self.group_id])
|
||||||
|
resp = self.client.get(url)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
def test_can_add(self):
|
||||||
|
self.login_as(self.admin)
|
||||||
|
url = reverse('api-v2.1-admin-group-members',
|
||||||
|
args=[self.group_id])
|
||||||
|
|
||||||
|
data = {'email': self.admin_name}
|
||||||
|
resp = self.client.post(url, data)
|
||||||
|
|
||||||
|
json_resp = json.loads(resp.content)
|
||||||
|
assert json_resp['success'][0]['group_id'] == self.group_id
|
||||||
|
assert json_resp['success'][0]['email'] == self.admin_name
|
||||||
|
|
||||||
|
def test_can_not_add_if_not_admin(self):
|
||||||
|
self.login_as(self.user)
|
||||||
|
url = reverse('api-v2.1-admin-group-members',
|
||||||
|
args=[self.group_id])
|
||||||
|
|
||||||
|
data = {'email': self.admin_name}
|
||||||
|
resp = self.client.post(url, data)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
def test_can_delete_group_member(self):
|
||||||
|
|
||||||
|
ccnet_api.group_add_member(self.group_id, self.user_name,
|
||||||
|
self.admin_name)
|
||||||
|
|
||||||
|
# make sure member in group
|
||||||
|
members = ccnet_api.get_group_members(self.group_id)
|
||||||
|
assert len(members) == 2
|
||||||
|
|
||||||
|
self.login_as(self.admin)
|
||||||
|
url = reverse('api-v2.1-admin-group-member',
|
||||||
|
args=[self.group_id, self.admin_name])
|
||||||
|
resp = self.client.delete(url)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
|
||||||
|
# make sure member is deleted
|
||||||
|
members = ccnet_api.get_group_members(self.group_id)
|
||||||
|
assert len(members) == 1
|
||||||
|
|
||||||
|
def test_can_not_delete_if_not_admin(self):
|
||||||
|
|
||||||
|
ccnet_api.group_add_member(self.group_id, self.user_name,
|
||||||
|
self.admin_name)
|
||||||
|
|
||||||
|
# make sure member in group
|
||||||
|
members = ccnet_api.get_group_members(self.group_id)
|
||||||
|
assert len(members) == 2
|
||||||
|
|
||||||
|
self.login_as(self.user)
|
||||||
|
url = reverse('api-v2.1-admin-group-member',
|
||||||
|
args=[self.group_id, self.admin_name])
|
||||||
|
resp = self.client.delete(url)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
# make sure member is not deleted
|
||||||
|
members = ccnet_api.get_group_members(self.group_id)
|
||||||
|
assert len(members) == 2
|
||||||
|
|
||||||
|
def test_can_not_delete_group_owner(self):
|
||||||
|
|
||||||
|
# make sure member in group
|
||||||
|
members = ccnet_api.get_group_members(self.group_id)
|
||||||
|
assert len(members) == 1
|
||||||
|
|
||||||
|
self.login_as(self.user)
|
||||||
|
url = reverse('api-v2.1-admin-group-member',
|
||||||
|
args=[self.group_id, self.user_name])
|
||||||
|
resp = self.client.delete(url)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
@@ -21,12 +21,6 @@ def test_sudo_mode_required(admin_browser_once):
|
|||||||
'the browser should be redirected back to the previous page'
|
'the browser should be redirected back to the previous page'
|
||||||
)
|
)
|
||||||
|
|
||||||
b.visit('/sys/groupadmin/')
|
|
||||||
assert b.path == '/sys/groupadmin/', (
|
|
||||||
'once the admin enters the password, '
|
|
||||||
'he would not be asked again within a certain time'
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.xfail
|
@pytest.mark.xfail
|
||||||
def test_sudo_mode_rejects_wrong_password(admin_browser_once):
|
def test_sudo_mode_rejects_wrong_password(admin_browser_once):
|
||||||
b = admin_browser_once
|
b = admin_browser_once
|
||||||
|
Reference in New Issue
Block a user