1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-18 23:18:27 +00:00

Add deactivation option (#7626)

* 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

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) => { selectItem = (e, targetItem) => {
e.stopPropagation(); e.stopPropagation();
if (this.props.operationBeforeSelect) { if (this.props.operationBeforeSelect) {
this.props.operationBeforeSelect(); this.props.operationBeforeSelect(targetItem);
} else { } else {
this.props.selectOption(targetItem); this.props.selectOption(targetItem);
} }

View File

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

View File

@ -204,8 +204,8 @@ class SearchUsers extends Component {
}); });
}; };
updateUser = (email, key, value) => { updateUser = (email, key, value, options) => {
systemAdminAPI.sysAdminUpdateUser(email, key, value).then(res => { systemAdminAPI.sysAdminUpdateUser(email, key, value, options).then(res => {
let newUserList = this.state.userList.map(item => { let newUserList = this.state.userList.map(item => {
if (item.email == email) { if (item.email == email) {
item[key] = res.data[key]; 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 SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
import UserLink from '../user-link'; import UserLink from '../user-link';
import SysAdminUserDeactivateDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog';
const { availableRoles, availableAdminRoles, institutions } = window.sysadmin.pageOptions; const { availableRoles, availableAdminRoles, institutions } = window.sysadmin.pageOptions;
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@ -206,7 +207,7 @@ class Item extends Component {
isDeleteUserDialogOpen: false, isDeleteUserDialogOpen: false,
isResetUserPasswordDialogOpen: false, isResetUserPasswordDialogOpen: false,
isRevokeAdminDialogOpen: false, isRevokeAdminDialogOpen: false,
isConfirmInactiveDialogOpen: false isConfirmInactiveDialogOpen: false,
}; };
} }
@ -252,7 +253,10 @@ class Item extends Component {
this.setState({ isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen }); this.setState({ isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen });
}; };
toggleConfirmInactiveDialog = () => { toggleConfirmInactiveDialog = (targetItem) => {
if (targetItem?.value === 'active') {
return;
}
this.setState({ isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen }); this.setState({ isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen });
}; };
@ -268,10 +272,14 @@ class Item extends Component {
this.props.updateUser(this.props.item.email, 'is_active', isActive); this.props.updateUser(this.props.item.email, 'is_active', isActive);
}; };
setUserInactive = () => { setUserInactive = (keepSharing) => {
this.props.updateUser(this.props.item.email, 'is_active', false); this.props.updateUser(this.props.item.email, 'is_active', false, {
keep_sharing: keepSharing
});
this.toggleConfirmInactiveDialog();
}; };
updateRole = (roleOption) => { updateRole = (roleOption) => {
this.props.updateUser(this.props.item.email, 'role', roleOption.value); 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 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 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 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' // for 'user status'
const curStatus = item.is_active ? 'active' : 'inactive'; const curStatus = item.is_active ? 'active' : 'inactive';
@ -585,12 +592,9 @@ class Item extends Component {
/> />
} }
{isConfirmInactiveDialogOpen && {isConfirmInactiveDialogOpen &&
<CommonOperationConfirmationDialog <SysAdminUserDeactivateDialog
title={gettext('Set user inactive')}
message={confirmSetUserInactiveMsg}
executeOperation={this.setUserInactive}
confirmBtnText={gettext('Set')}
toggleDialog={this.toggleConfirmInactiveDialog} toggleDialog={this.toggleConfirmInactiveDialog}
onSubmit={this.setUserInactive}
/> />
} }
</Fragment> </Fragment>

View File

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

View File

@ -642,12 +642,15 @@ class SystemAdminAPI {
return this._sendPostRequest(url, formData); 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) + '/'; const url = this.server + '/api/v2.1/admin/organizations/' + orgID + '/users/' + encodeURIComponent(email) + '/';
let formData = new FormData(); let formData = new FormData();
switch (attribute) { switch (attribute) {
case 'active': case 'active':
formData.append('active', value); formData.append('active', value);
if (options.keepSharing !== undefined) {
formData.append('keep_sharing', options.keepSharing);
}
break; break;
case 'is_org_staff': case 'is_org_staff':
formData.append('is_org_staff', value); formData.append('is_org_staff', value);
@ -957,7 +960,7 @@ class SystemAdminAPI {
return this._sendPostRequest(url, formData); 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) + '/'; const url = this.server + '/api/v2.1/admin/users/' + encodeURIComponent(email) + '/';
let formData = new FormData(); let formData = new FormData();
switch (attribute) { switch (attribute) {
@ -966,6 +969,9 @@ class SystemAdminAPI {
break; break;
case 'is_active': case 'is_active':
formData.append('is_active', value); formData.append('is_active', value);
if (options.keep_sharing !== undefined) {
formData.append('keep_sharing', options.keep_sharing);
}
break; break;
case 'is_staff': case 'is_staff':
formData.append('is_staff', value); 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.api2.endpoints.utils import is_org_user
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \ from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
datetime_to_isoformat_timestr datetime_to_isoformat_timestr
from seahub.utils.db_api import SeafileDB
from seahub.share.models import ExtraSharePermission
try: try:
from seahub.settings import ORG_MEMBER_QUOTA_ENABLED from seahub.settings import ORG_MEMBER_QUOTA_ENABLED
@ -303,6 +305,20 @@ class AdminOrgUser(APIView):
else: else:
user.is_active = False 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: try:
# update user status # update user status
result_code = user.save() 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.auth.models import SocialAuthUser
from seahub.options.models import UserOptions 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, \ 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, \ 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, \ 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) is_active = request.data.get("is_active", None)
if is_active: 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: try:
is_active = to_python_boolean(is_active) is_active = to_python_boolean(is_active)
except ValueError: except ValueError:

View File

@ -618,3 +618,38 @@ class SeafileDB:
wiki_info = WikiInfo(**params) wiki_info = WikiInfo(**params)
wikis.append(wiki_info) wikis.append(wiki_info)
return wikis 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])