1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-05-11 01:17:02 +00:00

Add deactivation option ()

* sql delete share relation

* update rpc remove share

* update

* use sql remove share

* code optimize

* code optimize

* update selector

---------

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 2025-04-20 08:54:41 +08:00 committed by GitHub
parent 38c6ea36ae
commit f24516e88a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 186 additions and 32 deletions
frontend/src
seahub
api2/endpoints/admin
utils

View File

@ -0,0 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter, Form, FormGroup, Input, Label } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
const propTypes = {
toggleDialog: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired
};
class SysAdminUserDeactivateDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
keepSharing: true
};
}
handleOptionChange = (e) => {
this.setState({ keepSharing: e.target.value === 'true' });
};
submit = () => {
this.props.onSubmit(this.state.keepSharing);
this.props.toggleDialog();
};
render() {
return (
<Modal isOpen={true} toggle={this.props.toggleDialog}>
<SeahubModalHeader toggle={this.props.toggleDialog}>
{gettext('Set user inactive')}
</SeahubModalHeader>
<ModalBody>
<Form>
<FormGroup tag="fieldset">
<p>{gettext('Do you want to keep the sharing relationships?')}</p>
<FormGroup check>
<Label check>
<Input
type="radio"
name="keepSharing"
value="true"
checked={this.state.keepSharing === true}
onChange={this.handleOptionChange}
className="mr-2"
/>
{gettext('Keep sharing')}
</Label>
</FormGroup>
<FormGroup check>
<Label check>
<Input
type="radio"
name="keepSharing"
value="false"
checked={this.state.keepSharing === false}
onChange={this.handleOptionChange}
className="mr-2"
/>
{gettext('Do not keep sharing')}
</Label>
</FormGroup>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.submit}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
SysAdminUserDeactivateDialog.propTypes = propTypes;
export default SysAdminUserDeactivateDialog;

View File

@ -56,7 +56,7 @@ class Selector extends Component {
selectItem = (e, targetItem) => {
e.stopPropagation();
if (this.props.operationBeforeSelect) {
this.props.operationBeforeSelect();
this.props.operationBeforeSelect(targetItem);
} else {
this.props.selectOption(targetItem);
}

View File

@ -16,6 +16,7 @@ import OpMenu from '../../../components/dialog/op-menu';
import MainPanelTopbar from '../main-panel-topbar';
import UserLink from '../user-link';
import OrgNav from './org-nav';
import SysAdminUserDeactivateDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog';
dayjs.extend(relativeTime);
@ -160,7 +161,10 @@ class Item extends Component {
this.setState({ isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen });
};
toggleConfirmInactiveDialog = () => {
toggleConfirmInactiveDialog = (targetItem) => {
if (targetItem?.value === 'active') {
return;
}
this.setState({ isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen });
};
@ -168,8 +172,8 @@ class Item extends Component {
this.props.updateStatus(this.props.item.email, statusOption.value);
};
setUserInactive = () => {
this.props.updateStatus(this.props.item.email, 'inactive');
setUserInactive = (keepSharing) => {
this.props.updateStatus(this.props.item.email, 'inactive', { keepSharing: keepSharing });
};
updateMembership = (membershipOption) => {
@ -229,7 +233,6 @@ class Item extends Component {
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
let resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName);
const confirmSetUserInactiveMsg = gettext('Are you sure you want to set {user_placeholder} inactive?').replace('{user_placeholder}', itemName);
// for 'user status'
const curStatus = item.active ? 'active' : 'inactive';
@ -311,13 +314,10 @@ class Item extends Component {
/>
}
{isConfirmInactiveDialogOpen &&
<CommonOperationConfirmationDialog
title={gettext('Set user inactive')}
message={confirmSetUserInactiveMsg}
executeOperation={this.setUserInactive}
confirmBtnText={gettext('Set')}
toggleDialog={this.toggleConfirmInactiveDialog}
/>
<SysAdminUserDeactivateDialog
toggleDialog={this.toggleConfirmInactiveDialog}
onSubmit={this.setUserInactive}
/>
}
</Fragment>
);
@ -396,9 +396,9 @@ class OrgUsers extends Component {
});
};
updateStatus = (email, statusValue) => {
updateStatus = (email, statusValue, options = {}) => {
const isActive = statusValue == 'active';
systemAdminAPI.sysAdminUpdateOrgUser(this.props.orgID, email, 'active', isActive).then(res => {
systemAdminAPI.sysAdminUpdateOrgUser(this.props.orgID, email, 'active', isActive, options).then(res => {
let newUserList = this.state.userList.map(item => {
if (item.email == email) {
item.active = res.data.active;

View File

@ -204,8 +204,8 @@ class SearchUsers extends Component {
});
};
updateUser = (email, key, value) => {
systemAdminAPI.sysAdminUpdateUser(email, key, value).then(res => {
updateUser = (email, key, value, options) => {
systemAdminAPI.sysAdminUpdateUser(email, key, value, options).then(res => {
let newUserList = this.state.userList.map(item => {
if (item.email == email) {
item[key] = res.data[key];

View File

@ -15,6 +15,7 @@ import OpMenu from '../../../components/dialog/op-menu';
import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
import UserLink from '../user-link';
import SysAdminUserDeactivateDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog';
const { availableRoles, availableAdminRoles, institutions } = window.sysadmin.pageOptions;
dayjs.extend(relativeTime);
@ -206,7 +207,7 @@ class Item extends Component {
isDeleteUserDialogOpen: false,
isResetUserPasswordDialogOpen: false,
isRevokeAdminDialogOpen: false,
isConfirmInactiveDialogOpen: false
isConfirmInactiveDialogOpen: false,
};
}
@ -252,7 +253,10 @@ class Item extends Component {
this.setState({ isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen });
};
toggleConfirmInactiveDialog = () => {
toggleConfirmInactiveDialog = (targetItem) => {
if (targetItem?.value === 'active') {
return;
}
this.setState({ isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen });
};
@ -268,10 +272,14 @@ class Item extends Component {
this.props.updateUser(this.props.item.email, 'is_active', isActive);
};
setUserInactive = () => {
this.props.updateUser(this.props.item.email, 'is_active', false);
setUserInactive = (keepSharing) => {
this.props.updateUser(this.props.item.email, 'is_active', false, {
keep_sharing: keepSharing
});
this.toggleConfirmInactiveDialog();
};
updateRole = (roleOption) => {
this.props.updateUser(this.props.item.email, 'role', roleOption.value);
};
@ -408,7 +416,6 @@ class Item extends Component {
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
const resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName);
const revokeAdminDialogMsg = gettext('Are you sure you want to revoke the admin permission of {placeholder} ?').replace('{placeholder}', itemName);
const confirmSetUserInactiveMsg = gettext('Are you sure you want to set {user_placeholder} inactive?').replace('{user_placeholder}', itemName);
// for 'user status'
const curStatus = item.is_active ? 'active' : 'inactive';
@ -585,12 +592,9 @@ class Item extends Component {
/>
}
{isConfirmInactiveDialogOpen &&
<CommonOperationConfirmationDialog
title={gettext('Set user inactive')}
message={confirmSetUserInactiveMsg}
executeOperation={this.setUserInactive}
confirmBtnText={gettext('Set')}
<SysAdminUserDeactivateDialog
toggleDialog={this.toggleConfirmInactiveDialog}
onSubmit={this.setUserInactive}
/>
}
</Fragment>

View File

@ -360,8 +360,8 @@ class Users extends Component {
});
};
updateUser = (email, key, value) => {
systemAdminAPI.sysAdminUpdateUser(email, key, value).then(res => {
updateUser = (email, key, value, options = {}) => {
systemAdminAPI.sysAdminUpdateUser(email, key, value, options).then(res => {
let newUserList = this.state.userList.map(item => {
if (item.email == email) {
item[key] = res.data[key];

View File

@ -642,12 +642,15 @@ class SystemAdminAPI {
return this._sendPostRequest(url, formData);
}
sysAdminUpdateOrgUser(orgID, email, attribute, value) {
sysAdminUpdateOrgUser(orgID, email, attribute, value, options = {}) {
const url = this.server + '/api/v2.1/admin/organizations/' + orgID + '/users/' + encodeURIComponent(email) + '/';
let formData = new FormData();
switch (attribute) {
case 'active':
formData.append('active', value);
if (options.keepSharing !== undefined) {
formData.append('keep_sharing', options.keepSharing);
}
break;
case 'is_org_staff':
formData.append('is_org_staff', value);
@ -957,7 +960,7 @@ class SystemAdminAPI {
return this._sendPostRequest(url, formData);
}
sysAdminUpdateUser(email, attribute, value) {
sysAdminUpdateUser(email, attribute, value, options = {}) {
const url = this.server + '/api/v2.1/admin/users/' + encodeURIComponent(email) + '/';
let formData = new FormData();
switch (attribute) {
@ -966,6 +969,9 @@ class SystemAdminAPI {
break;
case 'is_active':
formData.append('is_active', value);
if (options.keep_sharing !== undefined) {
formData.append('keep_sharing', options.keep_sharing);
}
break;
case 'is_staff':
formData.append('is_staff', value);

View File

@ -31,6 +31,8 @@ from seahub.api2.permissions import IsProVersion
from seahub.api2.endpoints.utils import is_org_user
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
datetime_to_isoformat_timestr
from seahub.utils.db_api import SeafileDB
from seahub.share.models import ExtraSharePermission
try:
from seahub.settings import ORG_MEMBER_QUOTA_ENABLED
@ -303,6 +305,20 @@ class AdminOrgUser(APIView):
else:
user.is_active = False
keep_sharing = request.data.get("keep_sharing", None)
username = request.user.username
if keep_sharing and keep_sharing == 'false':
seafile_db = SeafileDB()
orgs = ccnet_api.get_orgs_by_user(email)
if orgs:
org_id = orgs[0].org_id
seafile_db.delete_received_share_by_user(email, org_id)
seafile_db.delete_share_by_user(email, org_id)
else:
seafile_db.delete_received_share_by_user(email)
seafile_db.delete_share_by_user(email)
ExtraSharePermission.objects.filter(share_to=username).delete()
try:
# update user status
result_code = user.save()

View File

@ -66,7 +66,7 @@ from seahub.auth.utils import get_virtual_id_by_email
from seahub.auth.models import SocialAuthUser
from seahub.options.models import UserOptions
from seahub.share.models import FileShare, UploadLinkShare
from seahub.share.models import FileShare, UploadLinkShare, ExtraSharePermission
from seahub.utils.ldap import ENABLE_LDAP, LDAP_FILTER, ENABLE_SASL, SASL_MECHANISM, ENABLE_SSO_USER_CHANGE_PASSWORD, \
LDAP_PROVIDER, LDAP_SERVER_URL, LDAP_BASE_DN, LDAP_ADMIN_DN, LDAP_ADMIN_PASSWORD, LDAP_LOGIN_ATTR, LDAP_USER_OBJECT_CLASS, \
ENABLE_MULTI_LDAP, MULTI_LDAP_1_SERVER_URL, MULTI_LDAP_1_BASE_DN, MULTI_LDAP_1_ADMIN_DN, \
@ -1262,7 +1262,20 @@ class AdminUser(APIView):
is_active = request.data.get("is_active", None)
if is_active:
keep_sharing = request.data.get("keep_sharing", None)
username = request.user.username
if keep_sharing and keep_sharing == 'false':
seafile_db = SeafileDB()
orgs = ccnet_api.get_orgs_by_user(email)
if orgs:
org_id = orgs[0].org_id
seafile_db.delete_received_share_by_user(email, org_id)
seafile_db.delete_share_by_user(email, org_id)
else:
seafile_db.delete_received_share_by_user(email)
seafile_db.delete_share_by_user(email)
ExtraSharePermission.objects.filter(share_to=username).delete()
try:
is_active = to_python_boolean(is_active)
except ValueError:

View File

@ -618,3 +618,38 @@ class SeafileDB:
wiki_info = WikiInfo(**params)
wikis.append(wiki_info)
return wikis
def delete_received_share_by_user(self, username, org_id=''):
# Delete the share content shared to <username>
if org_id:
delete_share_sql = f"""
DELETE FROM `{self.db_name}`.`OrgSharedRepo` WHERE to_email=%s AND org_id=%s
"""
else:
delete_share_sql = f"""
DELETE FROM `{self.db_name}`.`SharedRepo` WHERE to_email=%s
"""
with connection.cursor() as cursor:
cursor.execute(delete_share_sql, [username, org_id] if org_id else [username])
def delete_share_by_user(self, username, org_id=''):
# Delete the share content shared from <username>
if org_id:
delete_share_sql = f"""
DELETE FROM `{self.db_name}`.`OrgSharedRepo` WHERE from_email=%s AND org_id=%s
"""
delete_group_share_sql = f"""
DELETE FROM `{self.db_name}`.`OrgGroupRepo` WHERE owner=%s AND org_id=%s
"""
else:
delete_share_sql = f"""
DELETE FROM `{self.db_name}`.`SharedRepo` WHERE from_email=%s
"""
delete_group_share_sql = f"""
DELETE FROM `{self.db_name}`.`RepoGroup` WHERE user_name=%s
"""
with connection.cursor() as cursor:
cursor.execute(delete_share_sql, [username, org_id] if org_id else [username])
cursor.execute(delete_group_share_sql, [username, org_id] if org_id else [username])