1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-11 20:01:10 +00:00

Add department transfer function to the administrator interface (#6014)

* Add the function of transferring departments in the administrator interface

* fix admin get all departments

* update

* Update departments.py

* update

* update

* add department repo transfer

* Update groups.py

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
This commit is contained in:
awu0403
2024-04-25 12:08:13 +08:00
committed by GitHub
parent 7a04bfa928
commit db6cee68e9
11 changed files with 362 additions and 51 deletions

View File

@@ -1,19 +1,34 @@
import React from 'react'; import React, {Fragment} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; import {
Button,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
Nav,
NavItem,
NavLink,
TabContent,
TabPane
} from 'reactstrap';
import makeAnimated from 'react-select/animated'; import makeAnimated from 'react-select/animated';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { gettext, isPro } from '../../utils/constants'; import {gettext, isPro, orgID} from '../../utils/constants';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import toaster from '../toast'; import toaster from '../toast';
import UserSelect from '../user-select'; import UserSelect from '../user-select';
import { SeahubSelect } from '../common/select'; import { SeahubSelect } from '../common/select';
import '../../css/transfer-dialog.css';
const propTypes = { const propTypes = {
itemName: PropTypes.string.isRequired, itemName: PropTypes.string.isRequired,
toggleDialog: PropTypes.func.isRequired, toggleDialog: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired, submit: PropTypes.func.isRequired,
canTransferToDept: PropTypes.bool canTransferToDept: PropTypes.bool,
isOrgAdmin: PropTypes.bool,
isSysAdmin: PropTypes.bool,
}; };
class TransferDialog extends React.Component { class TransferDialog extends React.Component {
@@ -23,23 +38,54 @@ class TransferDialog extends React.Component {
selectedOption: null, selectedOption: null,
errorMsg: [], errorMsg: [],
transferToUser: true, transferToUser: true,
transferToGroup: false,
activeTab: 'transUser'
}; };
this.options = []; this.options = [];
} }
handleSelectChange = (option) => { handleSelectChange = (option) => {
this.setState({selectedOption: option}); this.setState({ selectedOption: option });
}; };
submit = () => { submit = () => {
let user = this.state.selectedOption; let user = this.state.selectedOption;
this.props.submit(user); this.props.submit(user);
}; };
componentDidMount() { componentDidMount() {
if (isPro) { if (this.props.isOrgAdmin) {
seafileAPI.orgAdminListDepartments(orgID).then((res) => {
for (let i = 0; i < res.data.length; i++) {
let obj = {};
obj.value = res.data[i].name;
obj.email = res.data[i].email;
obj.label = res.data[i].name;
this.options.push(obj);
}
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
else if (this.props.isSysAdmin) {
seafileAPI.sysAdminListDepartments().then((res) => {
for (let i = 0; i < res.data.length; i++) {
let obj = {};
obj.value = res.data[i].name;
obj.email = res.data[i].email;
obj.label = res.data[i].name;
this.options.push(obj);
}
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
else{
seafileAPI.listDepartments().then((res) => { seafileAPI.listDepartments().then((res) => {
for (let i = 0 ; i < res.data.length; i++) { for (let i = 0; i < res.data.length; i++) {
let obj = {}; let obj = {};
obj.value = res.data[i].name; obj.value = res.data[i].name;
obj.email = res.data[i].email; obj.email = res.data[i].email;
@@ -56,33 +102,53 @@ class TransferDialog extends React.Component {
onClick = () => { onClick = () => {
this.setState({ this.setState({
transferToUser: !this.state.transferToUser, transferToUser: !this.state.transferToUser,
}); });
}; };
render() { toggle = (tab) => {
if (this.state.activeTab !== tab) {
this.setState({ activeTab: tab });
}
};
renderTransContent = () => {
let activeTab = this.state.activeTab;
let canTransferToDept = true; let canTransferToDept = true;
if (this.props.canTransferToDept != undefined) { if (this.props.canTransferToDept != undefined) {
canTransferToDept = this.props.canTransferToDept; canTransferToDept = this.props.canTransferToDept;
} }
const { itemName: repoName } = this.props;
let title = gettext('Transfer Library {library_name}');
title = title.replace('{library_name}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(repoName) + '</span>');
return ( return (
<Modal isOpen={true} toggle={this.props.toggleDialog}> <Fragment>
<ModalHeader toggle={this.props.toggleDialog}> <div className="transfer-dialog-side">
<span dangerouslySetInnerHTML={{__html: title}} className="d-flex mw-100"></span> <Nav pills>
</ModalHeader> <NavItem role="tab" aria-selected={activeTab === 'transUser'} aria-controls="transfer-user-panel">
<ModalBody> <NavLink className={activeTab === 'transUser' ? 'active' : ''} onClick={(this.toggle.bind(this, 'transUser'))} tabIndex="0" onKeyDown={this.onTabKeyDown}>
{this.state.transferToUser ? {gettext('Transfer to user')}
</NavLink>
</NavItem>
{isPro &&
<NavItem role="tab" aria-selected={activeTab === 'transDepart'} aria-controls="transfer-depart-panel">
<NavLink className={activeTab === 'transDepart' ? 'active' : ''} onClick={this.toggle.bind(this, 'transDepart')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
{gettext('Transfer to department')}
</NavLink>
</NavItem>}
</Nav>
</div>
<div className="transfer-dialog-main">
<TabContent activeTab={this.state.activeTab}>
<Fragment>
<TabPane tabId="transUser" role="tabpanel" id="transfer-user-panel">
<UserSelect <UserSelect
ref="userSelect" ref="userSelect"
isMulti={false} isMulti={false}
className="reviewer-select" className="reviewer-select"
placeholder={gettext('Select a user')} placeholder={gettext('Select a user')}
onSelectChange={this.handleSelectChange} onSelectChange={this.handleSelectChange}
/> : />
</TabPane>
{isPro && canTransferToDept &&
<TabPane tabId="transDepart" role="tabpanel" id="transfer-depart-panel">
<SeahubSelect <SeahubSelect
isClearable isClearable
maxMenuHeight={200} maxMenuHeight={200}
@@ -93,12 +159,28 @@ class TransferDialog extends React.Component {
onChange={this.handleSelectChange} onChange={this.handleSelectChange}
value={this.state.selectedOption} value={this.state.selectedOption}
/> />
} </TabPane>}
{isPro && canTransferToDept && </Fragment>
<span role="button" tabIndex="0" className="action-link" onClick={this.onClick} onKeyDown={Utils.onKeyDown}>{this.state.transferToUser ? </TabContent>
gettext('Transfer to department'): gettext('Transfer to user')} </div>
</span> </Fragment>
} );
};
render() {
const { itemName: repoName } = this.props;
let title = gettext('Transfer Library {library_name}');
title = title.replace('{library_name}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(repoName) + '</span>');
return (
<Modal isOpen={true} style={{maxWidth: '720px'}} toggle={this.props.toggleDialog} className="transfer-dialog">
<ModalHeader toggle={this.props.toggleDialog}>
<span dangerouslySetInnerHTML={{ __html: title }} className="d-flex mw-100"></span>
</ModalHeader>
<ModalBody className="transfer-dialog-content" role="tablist">
{this.renderTransContent()}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button> <Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>

View File

@@ -0,0 +1,52 @@
.transfer-dialog .transfer-dialog-content {
padding: 0;
min-height: 22.5rem;
display: flex;
flex-direction: column;
}
@media (min-width: 768px) {
.transfer-dialog .transfer-dialog-content {
flex-direction: row;
}
}
.transfer-dialog-content .transfer-dialog-side {
flex-basis: 29%;
padding: 1rem;
border-bottom: 1px solid #eee;
}
.transfer-dialog .nav .nav-item .nav-link {
padding: 0.3125rem 0.25rem;
}
@media (min-width: 768px) {
.transfer-dialog-content .transfer-dialog-side {
padding: 12px 8px;
border: 0;
border-right: 1px solid #eee;
}
.transfer-dialog-side .nav {
flex-direction: column;
}
.transfer-dialog-side .nav-pills .nav-item .nav-link {
width: 100%;
padding: 0.3125rem 0.5rem;
margin: 0;
}
}
.transfer-dialog-content .transfer-dialog-main {
display: flex;
flex-basis: 78%;
padding: 1rem;
}
.transfer-dialog-content .transfer-dialog-main .tab-content {
flex: 1;
}
.transfer-dialog-content .transfer-dialog-main .tab-pane {
height: 100%;
}

View File

@@ -200,7 +200,8 @@ class RepoItem extends React.Component {
highlight: false, highlight: false,
showMenu: false, showMenu: false,
isItemMenuShow: false, isItemMenuShow: false,
isTransferDialogShow: false isTransferDialogShow: false,
orgAdmin: true
}; };
} }
@@ -291,7 +292,7 @@ class RepoItem extends React.Component {
render() { render() {
let { repo } = this.props; let { repo } = this.props;
let isOperationMenuShow = this.state.showMenu && !repo.isDepartmentRepo; let isOperationMenuShow = this.state.showMenu;
return ( return (
<Fragment> <Fragment>
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
@@ -326,6 +327,7 @@ class RepoItem extends React.Component {
itemName={repo.repoName} itemName={repo.repoName}
submit={this.onTransferRepo} submit={this.onTransferRepo}
toggleDialog={this.toggleTransfer} toggleDialog={this.toggleTransfer}
isOrgAdmin={true}
/> />
</ModalPortal> </ModalPortal>
)} )}

View File

@@ -280,6 +280,12 @@ class Item extends Component {
getOperations = () => { getOperations = () => {
const { repo } = this.props; const { repo } = this.props;
let operations = ['Delete', 'Transfer']; let operations = ['Delete', 'Transfer'];
const index = repo.owner_email.indexOf('@seafile_group');
let isGroupOwnedRepo = index != -1;
if (isGroupOwnedRepo) {
operations = ['Transfer'];
return operations;
}
if (!repo.encrypted) { if (!repo.encrypted) {
operations.push('Share'); operations.push('Share');
} }
@@ -319,7 +325,7 @@ class Item extends Component {
} }
</td> </td>
<td> <td>
{(!isGroupOwnedRepo && isOpIconShown) && {(isOpIconShown) &&
<OpMenu <OpMenu
operations={this.getOperations()} operations={this.getOperations()}
translateOperations={this.translateOperations} translateOperations={this.translateOperations}
@@ -359,8 +365,8 @@ class Item extends Component {
<TransferDialog <TransferDialog
itemName={repo.name} itemName={repo.name}
submit={this.onTransferRepo} submit={this.onTransferRepo}
canTransferToDept={false}
toggleDialog={this.toggleTransferDialog} toggleDialog={this.toggleTransferDialog}
isSysAdmin={true}
/> />
</ModalPortal> </ModalPortal>
} }

View File

@@ -11,8 +11,11 @@ from django.utils.translation import gettext as _
from seaserv import seafile_api, ccnet_api from seaserv import seafile_api, ccnet_api
from pysearpc import SearpcError from pysearpc import SearpcError
from seahub.avatar.settings import GROUP_AVATAR_DEFAULT_SIZE
from seahub.avatar.templatetags.group_avatar_tags import get_default_group_avatar_url, api_grp_avatar_url
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.base.templatetags.seahub_tags import email2nickname from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.settings import CLOUD_MODE, MULTI_TENANCY
from seahub.utils import is_valid_username, is_pro_version from seahub.utils import is_valid_username, is_pro_version
from seahub.utils.timeutils import timestamp_to_isoformat_timestr from seahub.utils.timeutils import timestamp_to_isoformat_timestr
from seahub.group.utils import is_group_member, is_group_admin, \ from seahub.group.utils import is_group_member, is_group_admin, \
@@ -351,3 +354,47 @@ class AdminSearchGroup(APIView):
result.append(group_info) result.append(group_info)
return Response({"group_list": result}) return Response({"group_list": result})
class AdminDepartments(APIView):
"""
List all departments
"""
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAdminUser,)
throttle_classes = (UserRateThrottle,)
def get(self, request):
try:
all_groups = ccnet_api.list_all_departments()
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', GROUP_AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = GROUP_AVATAR_DEFAULT_SIZE
result = []
for group in all_groups:
try:
avatar_url, is_default, date_uploaded = api_grp_avatar_url(group.id, avatar_size)
except:
avatar_url = get_default_group_avatar_url()
created_at = timestamp_to_isoformat_timestr(group.timestamp)
department_info = {
"id": group.id,
"email": '%s@seafile_group' % str(group.id),
"parent_group_id": group.parent_group_id,
"name": group.group_name,
"owner": group.creator_name,
"created_at": created_at,
"avatar_url": request.build_absolute_uri(avatar_url),
}
result.append(department_info)
return Response(result)

View File

@@ -8,6 +8,7 @@ from rest_framework.views import APIView
from rest_framework import status from rest_framework import status
from django.template.defaultfilters import filesizeformat from django.template.defaultfilters import filesizeformat
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
import seaserv
from seaserv import ccnet_api, seafile_api from seaserv import ccnet_api, seafile_api
from seahub.api2.authentication import TokenAuthentication from seahub.api2.authentication import TokenAuthentication
@@ -26,6 +27,7 @@ from seahub.utils import is_valid_dirent_name, is_valid_email
from seahub.utils.timeutils import timestamp_to_isoformat_timestr from seahub.utils.timeutils import timestamp_to_isoformat_timestr
from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner
from seahub.constants import PERMISSION_READ_WRITE
try: try:
from seahub.settings import MULTI_TENANCY from seahub.settings import MULTI_TENANCY
@@ -342,7 +344,7 @@ class AdminLibrary(APIView):
new_owner = request.data.get('owner', None) new_owner = request.data.get('owner', None)
if new_owner: if new_owner:
if not is_valid_email(new_owner): if not is_valid_email(new_owner) and '@seafile_group' not in new_owner:
error_msg = 'owner invalid.' error_msg = 'owner invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
@@ -419,6 +421,13 @@ class AdminLibrary(APIView):
pub_repos = seafile_api.list_inner_pub_repos_by_owner(repo_owner) pub_repos = seafile_api.list_inner_pub_repos_by_owner(repo_owner)
# transfer repo # transfer repo
if '@seafile_group' in new_owner:
group_id = int(new_owner.split('@')[0])
if seaserv.is_org_group(group_id):
error_msg = 'Can not transfer library to an organization department %s' % new_owner
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE)
else:
seafile_api.set_repo_owner(repo_id, new_owner) seafile_api.set_repo_owner(repo_id, new_owner)
# reshare repo to user # reshare repo to user

View File

@@ -13,7 +13,10 @@ from seahub.api2.throttling import UserRateThrottle
from seahub.api2.authentication import TokenAuthentication from seahub.api2.authentication import TokenAuthentication
from seahub.api2.utils import api_error from seahub.api2.utils import api_error
from seahub.api2.endpoints.admin.groups import AdminGroup as SysAdminGroup from seahub.api2.endpoints.admin.groups import AdminGroup as SysAdminGroup
from seahub.avatar.settings import GROUP_AVATAR_DEFAULT_SIZE
from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, get_default_group_avatar_url
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
from seahub.utils.ccnet_db import CcnetDB
from seahub.utils.timeutils import timestamp_to_isoformat_timestr from seahub.utils.timeutils import timestamp_to_isoformat_timestr
from pysearpc import SearpcError from pysearpc import SearpcError
@@ -202,3 +205,45 @@ class OrgAdminSearchGroup(APIView):
groups_list.append(group) groups_list.append(group)
return Response({'group_list': groups_list}) return Response({'group_list': groups_list})
class OrgAdminDepartments(APIView):
"""
List all departments of the current organization
"""
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsProVersion, IsOrgAdminUser)
throttle_classes = (UserRateThrottle,)
def get(self, request, org_id):
try:
db_api = CcnetDB()
departments = db_api.list_org_departments(int(org_id))
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', GROUP_AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = GROUP_AVATAR_DEFAULT_SIZE
result = []
for group in departments:
try:
avatar_url, is_default, date_uploaded = api_grp_avatar_url(group.id, avatar_size)
except:
avatar_url = get_default_group_avatar_url()
created_at = timestamp_to_isoformat_timestr(group.timestamp)
department_info = {
"id": group.id,
"email": '%s@seafile_group' % str(group.id),
"parent_group_id": group.parent_group_id,
"name": group.group_name,
"owner": group.creator_name,
"created_at": created_at,
"avatar_url": request.build_absolute_uri(avatar_url),
}
result.append(department_info)
return Response(result)

View File

@@ -19,11 +19,13 @@ from seahub.group.utils import group_id_to_name
from seahub.utils.timeutils import timestamp_to_isoformat_timestr from seahub.utils.timeutils import timestamp_to_isoformat_timestr
from seahub.utils import is_valid_email from seahub.utils import is_valid_email
from seahub.signals import repo_deleted from seahub.signals import repo_deleted
from seahub.constants import PERMISSION_READ_WRITE
from seahub.organizations.views import is_org_repo, org_user_exists from seahub.organizations.views import is_org_repo, org_user_exists
from pysearpc import SearpcError from pysearpc import SearpcError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -128,12 +130,11 @@ class OrgAdminRepo(APIView):
"""Transfer an organization library """Transfer an organization library
""" """
new_owner = request.data.get('email', None) new_owner = request.data.get('email', None)
if not new_owner: if not new_owner:
error_msg = 'Email invalid.' error_msg = 'Email invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not is_valid_email(new_owner): if not is_valid_email(new_owner) and not '@seafile_group' in new_owner:
error_msg = 'Email invalid.' error_msg = 'Email invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
@@ -143,6 +144,7 @@ class OrgAdminRepo(APIView):
return api_error(status.HTTP_404_NOT_FOUND, error_msg) return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission checking # permission checking
if '@seafile_group' not in new_owner:
if not org_user_exists(org_id, new_owner): if not org_user_exists(org_id, new_owner):
error_msg = 'User %s not in org %s.' % (new_owner, org_id) error_msg = 'User %s not in org %s.' % (new_owner, org_id)
return api_error(status.HTTP_404_NOT_FOUND, error_msg) return api_error(status.HTTP_404_NOT_FOUND, error_msg)
@@ -166,7 +168,16 @@ class OrgAdminRepo(APIView):
# get all pub repos # get all pub repos
pub_repos = seafile_api.list_org_inner_pub_repos_by_owner(org_id, repo_owner) pub_repos = seafile_api.list_org_inner_pub_repos_by_owner(org_id, repo_owner)
# transfer repo
if '@seafile_group' in new_owner:
group_id = int(new_owner.split('@')[0])
if seaserv.is_org_group(group_id):
group_org_id = ccnet_api.get_org_id_by_group(group_id)
if org_id != group_org_id:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
seafile_api.org_transfer_repo_to_group(repo_id, org_id, group_id, PERMISSION_READ_WRITE)
else:
seafile_api.set_org_repo_owner(org_id, repo_id, new_owner) seafile_api.set_org_repo_owner(org_id, repo_id, new_owner)
# reshare repo to user # reshare repo to user

View File

@@ -13,7 +13,7 @@ from .api.group_members import AdminGroupMembers, AdminGroupMember
from .api.admin.users import OrgAdminUser, OrgAdminUsers, OrgAdminSearchUser, \ from .api.admin.users import OrgAdminUser, OrgAdminUsers, OrgAdminSearchUser, \
OrgAdminImportUsers, OrgAdminInviteUser OrgAdminImportUsers, OrgAdminInviteUser
from .api.admin.user_set_password import OrgAdminUserSetPassword from .api.admin.user_set_password import OrgAdminUserSetPassword
from .api.admin.groups import OrgAdminGroups, OrgAdminGroup, OrgAdminSearchGroup from .api.admin.groups import OrgAdminGroups, OrgAdminGroup, OrgAdminSearchGroup, OrgAdminDepartments
from .api.admin.repos import OrgAdminRepos, OrgAdminRepo from .api.admin.repos import OrgAdminRepos, OrgAdminRepo
from .api.admin.info import OrgAdminInfo from .api.admin.info import OrgAdminInfo
from .api.admin.links import OrgAdminLinks, OrgAdminLink from .api.admin.links import OrgAdminLinks, OrgAdminLink
@@ -95,5 +95,6 @@ urlpatterns = [
path('admin/logs/file-access/', OrgAdminLogsFileAccess.as_view(), name='api-v2.1-org-admin-logs-file-access'), path('admin/logs/file-access/', OrgAdminLogsFileAccess.as_view(), name='api-v2.1-org-admin-logs-file-access'),
path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'), path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'),
path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'), path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'),
path('<int:org_id>/admin/departments/', OrgAdminDepartments.as_view(), name='api-v2.1-org-admin-departments'),
] ]

View File

@@ -150,7 +150,7 @@ from seahub.api2.endpoints.admin.system_library import AdminSystemLibrary, \
AdminSystemLibraryUploadLink AdminSystemLibraryUploadLink
from seahub.api2.endpoints.admin.default_library import AdminDefaultLibrary from seahub.api2.endpoints.admin.default_library import AdminDefaultLibrary
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, AdminSearchGroup from seahub.api2.endpoints.admin.groups import AdminGroups, AdminGroup, AdminSearchGroup, AdminDepartments
from seahub.api2.endpoints.admin.group_libraries import AdminGroupLibraries, AdminGroupLibrary 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.group_members import AdminGroupMembers, AdminGroupMember
from seahub.api2.endpoints.admin.shares import AdminShares from seahub.api2.endpoints.admin.shares import AdminShares
@@ -635,6 +635,9 @@ urlpatterns = [
re_path(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/group-owned-libraries/$', AdminGroupOwnedLibraries.as_view(), name='api-v2.1-admin-group-owned-libraries'), re_path(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/group-owned-libraries/$', AdminGroupOwnedLibraries.as_view(), name='api-v2.1-admin-group-owned-libraries'),
re_path(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/group-owned-libraries/(?P<repo_id>[-0-9a-f]{36})/$', AdminGroupOwnedLibrary.as_view(), name='api-v2.1-admin-owned-group-library'), re_path(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/group-owned-libraries/(?P<repo_id>[-0-9a-f]{36})/$', AdminGroupOwnedLibrary.as_view(), name='api-v2.1-admin-owned-group-library'),
## admin::departments
re_path(r'api/v2.1/admin/departments/$', AdminDepartments.as_view(), name='api-v2.1-admin-departments'),
## admin::shares ## admin::shares
re_path(r'^api/v2.1/admin/shares/$', AdminShares.as_view(), name='api-v2.1-admin-shares'), re_path(r'^api/v2.1/admin/shares/$', AdminShares.as_view(), name='api-v2.1-admin-shares'),

View File

@@ -22,3 +22,56 @@ def get_ccnet_db_name():
error_msg = 'Failed to init ccnet db, only mysql db supported.' error_msg = 'Failed to init ccnet db, only mysql db supported.'
return None, error_msg return None, error_msg
return db_name, None return db_name, None
import os
import configparser
from django.db import connection
class CcnetGroup(object):
def __init__(self, **kwargs):
self.id = kwargs.get('group_id')
self.group_name = kwargs.get('group_name')
self.creator_name = kwargs.get('creator_name')
self.timestamp = kwargs.get('timestamp')
self.parent_group_id = kwargs.get('parent_group_id')
class CcnetDB:
def __init__(self):
self.db_name = get_ccnet_db_name()[0]
def list_org_departments(self, org_id):
sql = f"""
SELECT
g.group_id, group_name, creator_name, timestamp, type, parent_group_id
FROM
`{self.db_name}`.`OrgGroup` o
LEFT JOIN
`{self.db_name}`.`Group` g
ON o.group_id=g.group_id
WHERE
org_id={org_id} AND parent_group_id<>0;
"""
groups = []
with connection.cursor() as cursor:
cursor.execute(sql)
for item in cursor.fetchall():
group_id = item[0]
group_name = item[1]
creator_name = item[2]
timestamp=item[3]
parent_group_id = item[5]
params = {
'group_id':group_id,
'group_name': group_name,
'creator_name': creator_name,
'timestamp': timestamp,
'parent_group_id': parent_group_id
}
group_obj = CcnetGroup(**params)
groups.append(group_obj)
return groups