mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-08 10:22:46 +00:00
add dep admin transfer dep repo
This commit is contained in:
@@ -28,6 +28,8 @@ const propTypes = {
|
||||
canTransferToDept: PropTypes.bool,
|
||||
isOrgAdmin: PropTypes.bool,
|
||||
isSysAdmin: PropTypes.bool,
|
||||
isDepAdminTransfer: PropTypes.bool,
|
||||
|
||||
};
|
||||
|
||||
const TRANS_USER = 'transUser';
|
||||
@@ -41,7 +43,7 @@ class TransferDialog extends React.Component {
|
||||
errorMsg: [],
|
||||
transferToUser: true,
|
||||
transferToGroup: false,
|
||||
activeTab: TRANS_USER
|
||||
activeTab: !this.props.isDepAdminTransfer ? TRANS_USER : TRANS_DEPART
|
||||
};
|
||||
this.options = [];
|
||||
}
|
||||
@@ -131,16 +133,18 @@ class TransferDialog extends React.Component {
|
||||
<Fragment>
|
||||
<div className="transfer-dialog-side">
|
||||
<Nav pills>
|
||||
<NavItem role="tab" aria-selected={activeTab === TRANS_USER} aria-controls="transfer-user-panel">
|
||||
<NavLink
|
||||
className={activeTab === TRANS_USER ? 'active' : ''}
|
||||
onClick={(this.toggle.bind(this, TRANS_USER))}
|
||||
tabIndex="0"
|
||||
onKeyDown={this.onTabKeyDown}
|
||||
>
|
||||
{gettext('Transfer to user')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
{!this.props.isDepAdminTransfer &&
|
||||
<NavItem role="tab" aria-selected={activeTab === TRANS_USER} aria-controls="transfer-user-panel">
|
||||
<NavLink
|
||||
className={activeTab === TRANS_USER ? 'active' : ''}
|
||||
onClick={(this.toggle.bind(this, TRANS_USER))}
|
||||
tabIndex="0"
|
||||
onKeyDown={this.onTabKeyDown}
|
||||
>
|
||||
{gettext('Transfer to user')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
}
|
||||
{isPro &&
|
||||
<NavItem role="tab" aria-selected={activeTab === TRANS_DEPART} aria-controls="transfer-depart-panel">
|
||||
<NavLink
|
||||
|
@@ -15,12 +15,14 @@ import ResetEncryptedRepoPasswordDialog from '../../components/dialog/reset-encr
|
||||
import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog';
|
||||
import Rename from '../rename';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { userAPI } from '../../utils/user-api';
|
||||
import LibHistorySettingDialog from '../dialog/lib-history-setting-dialog';
|
||||
import toaster from '../toast';
|
||||
import RepoAPITokenDialog from '../dialog/repo-api-token-dialog';
|
||||
import RepoShareAdminDialog from '../dialog/repo-share-admin-dialog';
|
||||
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
||||
import { GRID_MODE, LIST_MODE } from '../dir-view-mode/constants';
|
||||
import TransferDialog from '../dialog/transfer-dialog';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -37,6 +39,7 @@ const propTypes = {
|
||||
onItemDelete: PropTypes.func,
|
||||
onMonitorRepo: PropTypes.func,
|
||||
onContextMenu: PropTypes.func.isRequired,
|
||||
onTransferRepo: PropTypes.func
|
||||
};
|
||||
|
||||
class SharedRepoListItem extends React.Component {
|
||||
@@ -55,6 +58,7 @@ class SharedRepoListItem extends React.Component {
|
||||
isHistorySettingDialogShow: false,
|
||||
isDeleteDialogShow: false,
|
||||
isAPITokenDialogShow: false,
|
||||
isTransferDialogShow: false,
|
||||
isRepoShareAdminDialogOpen: false,
|
||||
isRepoDeleted: false,
|
||||
isChangePasswordDialogShow: false,
|
||||
@@ -166,6 +170,9 @@ class SharedRepoListItem extends React.Component {
|
||||
case 'Rename':
|
||||
this.onItemRenameToggle();
|
||||
break;
|
||||
case 'Transfer':
|
||||
this.onTransferToggle();
|
||||
break;
|
||||
case 'Folder Permission':
|
||||
this.onItemFolderPermissionToggle();
|
||||
break;
|
||||
@@ -231,6 +238,33 @@ class SharedRepoListItem extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
onTransferToggle = () => {
|
||||
this.setState({ isTransferDialogShow: !this.state.isTransferDialogShow });
|
||||
};
|
||||
|
||||
onTransferRepo = (user) => {
|
||||
let repoID = this.props.repo.repo_id;
|
||||
let groupID = this.props.currentGroup.id;
|
||||
let email = null;
|
||||
if (Array.isArray(user)) {
|
||||
email = user[0].email;
|
||||
} else {
|
||||
email = user.email;
|
||||
}
|
||||
userAPI.depAdminTransferRepo(repoID, groupID, email).then(res => {
|
||||
this.props.onTransferRepo(repoID, groupID, email);
|
||||
let message = gettext('Successfully transferred the library.');
|
||||
toaster.success(message);
|
||||
}).catch(error => {
|
||||
if (error.response) {
|
||||
toaster.danger(error.response.data.error_msg || gettext('Error'), { duration: 3 });
|
||||
} else {
|
||||
toaster.danger(gettext('Failed. Please check the network.'), { duration: 3 });
|
||||
}
|
||||
});
|
||||
this.onTransferToggle();
|
||||
};
|
||||
|
||||
onRenameConfirm = (name) => {
|
||||
this.props.onItemRename(this.props.repo, name);
|
||||
this.onRenameCancel();
|
||||
@@ -322,6 +356,9 @@ class SharedRepoListItem extends React.Component {
|
||||
case 'Rename':
|
||||
translateResult = gettext('Rename');
|
||||
break;
|
||||
case 'Transfer':
|
||||
translateResult = gettext('Transfer');
|
||||
break;
|
||||
case 'Folder Permission':
|
||||
translateResult = gettext('Folder Permission');
|
||||
break;
|
||||
@@ -389,7 +426,7 @@ class SharedRepoListItem extends React.Component {
|
||||
if (isStaff) {
|
||||
if (repo.owner_email == currentGroup.id + '@seafile_group') {
|
||||
this.isDeparementOnwerGroupMember = true;
|
||||
operations = ['Rename'];
|
||||
operations = ['Rename', 'Transfer'];
|
||||
if (folderPermEnabled) {
|
||||
operations.push('Folder Permission');
|
||||
}
|
||||
@@ -822,6 +859,17 @@ class SharedRepoListItem extends React.Component {
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isTransferDialogShow && (
|
||||
<ModalPortal>
|
||||
<TransferDialog
|
||||
itemName={repo.repo_name}
|
||||
submit={this.onTransferRepo}
|
||||
canTransferToDept={true}
|
||||
toggleDialog={this.onTransferToggle}
|
||||
isDepAdminTransfer={true}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
@@ -131,6 +131,7 @@ class SharedRepoListView extends React.Component {
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
onTransferRepo={this.props.onTransferRepo}
|
||||
onItemUnshare={this.props.onItemUnshare}
|
||||
onItemDelete={this.props.onItemDelete}
|
||||
onItemRename={this.props.onItemRename}
|
||||
|
@@ -14,7 +14,9 @@ const propTypes = {
|
||||
inAllLibs: PropTypes.bool,
|
||||
currentViewMode: PropTypes.string,
|
||||
group: PropTypes.object.isRequired,
|
||||
updateGroup: PropTypes.func.isRequired
|
||||
updateGroup: PropTypes.func.isRequired,
|
||||
onTransferRepo: PropTypes.func.isRequired
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -139,6 +141,7 @@ class GroupItem extends React.Component {
|
||||
onItemDelete={this.onItemDelete}
|
||||
onItemRename={this.onItemRename}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
onTransferRepo={this.props.onTransferRepo}
|
||||
currentViewMode={currentViewMode}
|
||||
/>
|
||||
}
|
||||
|
@@ -149,6 +149,31 @@ class Libraries extends Component {
|
||||
this.setState({ repoList: repoList });
|
||||
};
|
||||
|
||||
onGroupTransferRepo = (repoID, oldGroupID, newOwner) => {
|
||||
let newGroupID = parseInt(newOwner.split('@')[0]);
|
||||
let repoToMove = null;
|
||||
const updatedGroups = this.state.groupList.map(group => {
|
||||
if (group.id === oldGroupID) {
|
||||
group.repos = group.repos.filter(repo => {
|
||||
if (repo.repo_id === repoID) {
|
||||
repoToMove = repo;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return group;
|
||||
});
|
||||
if (repoToMove) {
|
||||
updatedGroups.forEach(group => {
|
||||
if (group.id === newGroupID) {
|
||||
group.repos.push(repoToMove);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.setState({ groupList: updatedGroups });
|
||||
};
|
||||
|
||||
onRenameRepo = (repo, newName) => {
|
||||
let repoList = this.state.repoList.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
@@ -326,6 +351,7 @@ class Libraries extends Component {
|
||||
inAllLibs={true}
|
||||
group={group}
|
||||
updateGroup={this.updateGroup}
|
||||
onTransferRepo={this.onGroupTransferRepo}
|
||||
currentViewMode={currentViewMode}
|
||||
/>
|
||||
);
|
||||
|
@@ -227,7 +227,13 @@ class RepoItem extends React.Component {
|
||||
|
||||
onTransferRepo = (user) => {
|
||||
let repo = this.props.repo;
|
||||
seafileAPI.orgAdminTransferOrgRepo(orgID, repo.repoID, user.email).then(res => {
|
||||
let email = null;
|
||||
if (Array.isArray(user)) {
|
||||
email = user[0].email;
|
||||
} else {
|
||||
email = user.email;
|
||||
}
|
||||
seafileAPI.orgAdminTransferOrgRepo(orgID, repo.repoID, email).then(res => {
|
||||
this.props.transferRepoItem(repo.repoID, user);
|
||||
let msg = gettext('Successfully transferred the library.');
|
||||
toaster.success(msg);
|
||||
|
@@ -47,6 +47,13 @@ class UserAPI {
|
||||
const url = this.server + '/api/v2.1/query-io-status/?task_id=' + task_id;
|
||||
return this.req.get(url);
|
||||
}
|
||||
|
||||
depAdminTransferRepo(repo_id, group_id, email) {
|
||||
const url = this.server + '/api/v2.1/groups/' + group_id + '/group-owned-libraries/' + repo_id + '/transfer/';
|
||||
const formData = new FormData();
|
||||
formData.append('email', email);
|
||||
return this.req.put(url, formData);
|
||||
}
|
||||
}
|
||||
|
||||
let userAPI = new UserAPI();
|
||||
|
@@ -9,7 +9,7 @@ from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from seaserv import seafile_api, ccnet_api
|
||||
from seaserv import seafile_api, ccnet_api, seafserv_threaded_rpc
|
||||
|
||||
from constance import config
|
||||
|
||||
@@ -25,11 +25,12 @@ from seahub.base.templatetags.seahub_tags import email2nickname, \
|
||||
email2contact_email
|
||||
from seahub.base.accounts import User
|
||||
from seahub.organizations.models import OrgAdminSettings, DISABLE_ORG_ENCRYPTED_LIBRARY
|
||||
from seahub.organizations.views import org_user_exists
|
||||
from seahub.signals import repo_created
|
||||
from seahub.group.utils import is_group_admin
|
||||
from seahub.utils import is_valid_dirent_name, is_org_context, \
|
||||
is_pro_version, normalize_dir_path, is_valid_username, \
|
||||
send_perm_audit_msg, is_valid_org_id
|
||||
send_perm_audit_msg, is_valid_org_id, is_valid_email
|
||||
from seahub.utils.repo import (
|
||||
get_library_storages, get_repo_owner, get_available_repo_perms
|
||||
)
|
||||
@@ -1406,3 +1407,137 @@ class GroupOwnedLibraryUserShareInLibrary(APIView):
|
||||
repo_id, '/', permission)
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
|
||||
class GroupOwnedLibraryTransferLibrary(APIView):
|
||||
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, IsProVersion)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
@api_check_group
|
||||
@add_org_context
|
||||
def put(self, request, group_id, repo_id, org_id):
|
||||
""" Transfer a library.
|
||||
|
||||
Permission checking:
|
||||
1. is group admin;
|
||||
"""
|
||||
# argument check
|
||||
new_owner = request.data.get('email', None)
|
||||
if not new_owner:
|
||||
error_msg = 'Email invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if '@seafile_group' not in new_owner:
|
||||
error_msg = 'Email invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if org_id:
|
||||
org_id = int(org_id)
|
||||
if not ccnet_api.get_org_by_id(org_id):
|
||||
error_msg = 'Organization %s not found.' % org_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# permission check
|
||||
if org_id:
|
||||
repo_owner = seafile_api.get_org_repo_owner(repo_id)
|
||||
else:
|
||||
repo_owner = seafile_api.get_repo_owner(repo_id)
|
||||
cur_group_id = int(group_id)
|
||||
username = request.user.username
|
||||
if not is_group_admin(cur_group_id, username):
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
# resource check
|
||||
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)
|
||||
|
||||
# preparation before transfer repo
|
||||
pub_repos = []
|
||||
if org_id:
|
||||
# get repo shared to user/group list
|
||||
shared_users = seafile_api.list_org_repo_shared_to(org_id,
|
||||
repo_owner,
|
||||
repo_id)
|
||||
shared_groups = seafile_api.list_org_repo_shared_group(org_id,
|
||||
repo_owner,
|
||||
repo_id)
|
||||
|
||||
# get all org pub repos
|
||||
pub_repos = seafile_api.list_org_inner_pub_repos_by_owner(
|
||||
org_id, repo_owner)
|
||||
else:
|
||||
# get repo shared to user/group list
|
||||
shared_users = seafile_api.list_repo_shared_to(
|
||||
repo_owner, repo_id)
|
||||
shared_groups = seafile_api.list_repo_shared_group_by_user(
|
||||
repo_owner, repo_id)
|
||||
|
||||
# get all pub repos
|
||||
if not request.cloud_mode:
|
||||
pub_repos = seafile_api.list_inner_pub_repos_by_owner(repo_owner)
|
||||
# transfer repo
|
||||
try:
|
||||
if org_id:
|
||||
group_id = int(new_owner.split('@')[0])
|
||||
seafile_api.org_transfer_repo_to_group(repo_id, org_id, group_id, PERMISSION_READ_WRITE)
|
||||
else:
|
||||
if ccnet_api.get_orgs_by_user(new_owner):
|
||||
# can not transfer library to organization user %s.
|
||||
error_msg = 'Email %s invalid.' % new_owner
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
else:
|
||||
group_id = int(new_owner.split('@')[0])
|
||||
seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
# reshare repo to user
|
||||
for shared_user in shared_users:
|
||||
shared_username = shared_user.user
|
||||
|
||||
if new_owner == shared_username:
|
||||
continue
|
||||
if org_id:
|
||||
seafserv_threaded_rpc.org_add_share(org_id, repo_id,
|
||||
new_owner, shared_username, shared_user.perm)
|
||||
else:
|
||||
seafile_api.share_repo(repo_id, new_owner,
|
||||
shared_username, shared_user.perm)
|
||||
|
||||
# reshare repo to group
|
||||
for shared_group in shared_groups:
|
||||
shared_group_id = shared_group.group_id
|
||||
if shared_group_id == cur_group_id:
|
||||
continue
|
||||
|
||||
if org_id:
|
||||
seafile_api.add_org_group_repo(repo_id, org_id,
|
||||
shared_group_id, new_owner, shared_group.perm)
|
||||
else:
|
||||
seafile_api.set_group_repo(repo_id, shared_group_id,
|
||||
new_owner, shared_group.perm)
|
||||
|
||||
# check if current repo is pub-repo
|
||||
# if YES, reshare current repo to public
|
||||
for pub_repo in pub_repos:
|
||||
if repo_id != pub_repo.id:
|
||||
continue
|
||||
|
||||
if org_id:
|
||||
seafile_api.set_org_inner_pub_repo(org_id, repo_id,
|
||||
pub_repo.permission)
|
||||
else:
|
||||
seafserv_threaded_rpc.set_inner_pub_repo(
|
||||
repo_id, pub_repo.permission)
|
||||
|
||||
break
|
||||
|
||||
return Response({'success': True})
|
||||
|
@@ -37,7 +37,8 @@ from seahub.api2.endpoints.group_libraries import GroupLibraries, GroupLibrary
|
||||
from seahub.api2.endpoints.group_owned_libraries import GroupOwnedLibraries, \
|
||||
GroupOwnedLibrary, GroupOwnedLibraryUserFolderPermission, \
|
||||
GroupOwnedLibraryGroupFolderPermission, GroupOwnedLibraryUserShare, \
|
||||
GroupOwnedLibraryGroupShare, GroupOwnedLibraryUserShareInLibrary
|
||||
GroupOwnedLibraryGroupShare, GroupOwnedLibraryUserShareInLibrary, \
|
||||
GroupOwnedLibraryTransferLibrary
|
||||
from seahub.api2.endpoints.address_book.groups import AddressBookGroupsSubGroups
|
||||
from seahub.api2.endpoints.address_book.members import AddressBookGroupsSearchMember
|
||||
|
||||
@@ -353,6 +354,7 @@ urlpatterns = [
|
||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/libraries/(?P<repo_id>[-0-9a-f]{36})/$', GroupLibrary.as_view(), name='api-v2.1-group-library'),
|
||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/group-owned-libraries/$', GroupOwnedLibraries.as_view(), name='api-v2.1-group-owned-libraries'),
|
||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/group-owned-libraries/(?P<repo_id>[-0-9a-f]{36})/$', GroupOwnedLibrary.as_view(), name='api-v2.1-owned-group-library'),
|
||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/group-owned-libraries/(?P<repo_id>[-0-9a-f]{36})/transfer/$', GroupOwnedLibraryTransferLibrary.as_view(), name='api-v2.1-group-owned-group-library-transfer'),
|
||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/members/$', GroupMembers.as_view(), name='api-v2.1-group-members'),
|
||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/search-member/$', GroupSearchMember.as_view(), name='api-v2.1-group-search-member'),
|
||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/members/bulk/$', GroupMembersBulk.as_view(), name='api-v2.1-group-members-bulk'),
|
||||
|
Reference in New Issue
Block a user