From f00b81f8d8c26b5288706ea30ade7702c7de0aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E6=B0=B8=E5=BC=BA?= <11704063+s-yongqiang@user.noreply.gitee.com> Date: Mon, 17 Feb 2025 15:27:29 +0800 Subject: [PATCH 1/6] invite user --- .../dialog/group-invite-members-dialog.js | 113 ++++++++++++++++++ .../src/css/group-invite-members-dialog.css | 40 +++++++ frontend/src/pages/groups/group-view.js | 22 +++- frontend/src/utils/constants.js | 1 + frontend/src/utils/seafile-api.js | 16 +++ seahub/api2/endpoints/group_members.py | 99 ++++++++++++++- seahub/group/models.py | 41 +++++++ seahub/group/views.py | 35 +++++- seahub/templates/base_for_react.html | 1 + seahub/urls.py | 7 +- seahub/views/__init__.py | 5 +- sql/mysql.sql | 11 ++ 12 files changed, 382 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/dialog/group-invite-members-dialog.js create mode 100644 frontend/src/css/group-invite-members-dialog.css create mode 100644 seahub/group/models.py diff --git a/frontend/src/components/dialog/group-invite-members-dialog.js b/frontend/src/components/dialog/group-invite-members-dialog.js new file mode 100644 index 0000000000..b69440cd4b --- /dev/null +++ b/frontend/src/components/dialog/group-invite-members-dialog.js @@ -0,0 +1,113 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalBody } from 'reactstrap'; +import SeahubModalHeader from '@/components/common/seahub-modal-header'; +import copy from 'copy-to-clipboard'; +import toaster from '../toast'; +import { gettext } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; + +import '../../css/group-invite-members-dialog.css'; + +const propTypes = { + groupID: PropTypes.string.isRequired, + toggleGroupInviteDialog: PropTypes.func.isRequired, +}; + +class GroupInviteMembersDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + inviteList: [], + }; + } + + componentDidMount() { + this.listInviteLinks(); + } + + listInviteLinks = () => { + seafileAPI.getGroupInviteLinks(this.props.groupID).then((res) => { + this.setState({ inviteList: res.data.group_invite_link_list }); + }).catch(error => { + this.onError(error); + }); + }; + + addInviteLink = () => { + seafileAPI.addGroupInviteLinks(this.props.groupID).then(() => { + this.listInviteLinks(); + }).catch(error => { + this.onError(error); + }); + }; + + deleteLink = (token) => { + seafileAPI.deleteGroupInviteLinks(this.props.groupID, token).then(() => { + this.listInviteLinks(); + }).catch(error => { + this.onError(error); + }); + }; + + onError = (error) => { + let errMsg = Utils.getErrorMsg(error, true); + if (!error.response || error.response.status !== 403) { + toaster.danger(errMsg); + } + }; + + copyLink = () => { + const inviteLinkItem = this.state.inviteList[0]; + copy(inviteLinkItem.link); + const message = gettext('Invitation link has been copied to clipboard'); + toaster.success((message), { + duration: 2 + }); + }; + + toggle = () => { + this.props.toggleGroupInviteDialog(); + }; + + render() { + const { inviteList } = this.state; + const link = inviteList[0]; + return ( + + {gettext('Invite members')} + + {link ? + <> +
+ {gettext('Group invitation link')} +
+
+
{link.link}
+
+ +
+ +
+ + : + <> +
+ {gettext('No group invitation link yet. Group invitation link let registered users to join the group by clicking a link.')} +
+ + + } +
+
+ ); + } +} + +GroupInviteMembersDialog.propTypes = propTypes; + +export default GroupInviteMembersDialog; diff --git a/frontend/src/css/group-invite-members-dialog.css b/frontend/src/css/group-invite-members-dialog.css new file mode 100644 index 0000000000..4792452e6e --- /dev/null +++ b/frontend/src/css/group-invite-members-dialog.css @@ -0,0 +1,40 @@ +.group-invite-members th, +.group-invite-members td { + vertical-align: middle; + text-align: left; +} + +.group-invite-members .no-link-tip { + line-height: 24px; + color: #999; +} + +.invite-link-item { + display: flex; + margin: 1rem 0 2.5rem; +} + +.invite-link-item .form-item { + width: calc(100% - 120px); + padding-left: 10px; + height: 40px; + line-height: 40px; + border: 1px solid #ccc; + border-right: none; +} + +.invite-link-item .invite-link-copy { + width: 72px; +} + +.invite-link-item .invite-link-copy-btn { + width: 72px; + height: 40px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.invite-link-item .delete-link-btn { + color: #999; + width: 40px; +} \ No newline at end of file diff --git a/frontend/src/pages/groups/group-view.js b/frontend/src/pages/groups/group-view.js index fc523fc855..8c2446a470 100644 --- a/frontend/src/pages/groups/group-view.js +++ b/frontend/src/pages/groups/group-view.js @@ -2,7 +2,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import cookie from 'react-cookies'; import classnames from 'classnames'; -import { gettext, username, canAddRepo } from '../../utils/constants'; +import { gettext, username, canAddRepo, isMultiTenancy } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import Loading from '../../components/loading'; @@ -21,6 +21,7 @@ import DepartmentDetailDialog from '../../components/dialog/department-detail-di import LeaveGroupDialog from '../../components/dialog/leave-group-dialog'; import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view'; import SortOptionsDialog from '../../components/dialog/sort-options'; +import GroupInviteMembersDialog from '../../components/dialog/group-invite-members-dialog'; import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; import ViewModes from '../../components/view-modes'; import ReposSortMenu from '../../components/sort-menu'; @@ -65,6 +66,7 @@ class GroupView extends React.Component { showTransferGroupDialog: false, showImportMembersDialog: false, showManageMembersDialog: false, + showInviteMembersDialog: false, isLeaveGroupDialogOpen: false, isMembersDialogOpen: false }; @@ -287,6 +289,13 @@ class GroupView extends React.Component { }); }; + toggleInviteMembersDialog = () => { + this.setState({ + showInviteMembersDialog: !this.state.showInviteMembersDialog, + showGroupDropdown: false, + }); + }; + importMembersInBatch = (file) => { toaster.notify(gettext('It may take some time, please wait.')); seafileAPI.importGroupMembersViaFile(this.state.currentGroup.id, file).then((res) => { @@ -365,6 +374,7 @@ class GroupView extends React.Component { getOpList = () => { const { currentGroup, isDepartmentGroup, isStaff, isOwner } = this.state; + // const isGroup = this.state.currentGroup.owner !== 'system admin'; const opList = []; if ((!isDepartmentGroup && canAddRepo) || (isDepartmentGroup && isStaff)) { @@ -389,6 +399,10 @@ class GroupView extends React.Component { if (!isOwner && !isDepartmentGroup) { opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog }); } + + if (isOwner && this.state.currentGroup.owner !== 'system admin' && !isMultiTenancy) { + opList.push({ 'text': gettext('Invite Members'), 'onClick': this.toggleInviteMembersDialog }); + } } return opList; @@ -589,6 +603,12 @@ class GroupView extends React.Component { onGroupChanged={this.props.onGroupChanged} /> } + {this.state.showInviteMembersDialog && + + } ); } diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 91eb515d60..7f4efee481 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -112,6 +112,7 @@ export const enablePDFThumbnail = window.app.pageOptions.enablePDFThumbnail; export const enableOnlyoffice = window.app.pageOptions.enableOnlyoffice || false; export const onlyofficeConverterExtensions = window.app.pageOptions.onlyofficeConverterExtensions || []; +export const isMultiTenancy = window.app.pageOptions.isMultiTenacy; export const enableFileTags = window.app.pageOptions.enableFileTags || false; export const enableShowAbout = window.app.pageOptions.enableShowAbout || false; diff --git a/frontend/src/utils/seafile-api.js b/frontend/src/utils/seafile-api.js index 1b165bb672..91b3b3a076 100644 --- a/frontend/src/utils/seafile-api.js +++ b/frontend/src/utils/seafile-api.js @@ -303,6 +303,22 @@ class SeafileAPI { return this.req.delete(url, { data: params }); } + deleteGroupInviteLinks(groupID, token) { + const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/' + token + '/'; + return this.req.delete(url); + } + + addGroupInviteLinks(groupID) { + const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/'; + let formData = new FormData(); + return this._sendPostRequest(url, formData); + } + + getGroupInviteLinks(groupID) { + const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/'; + return this.req.get(url); + } + // ---- share operation listShareLinks({ repoID, path, page, perPage }) { diff --git a/seahub/api2/endpoints/group_members.py b/seahub/api2/endpoints/group_members.py index bb7b7a4ded..c52612efeb 100644 --- a/seahub/api2/endpoints/group_members.py +++ b/seahub/api2/endpoints/group_members.py @@ -3,7 +3,7 @@ import logging from io import BytesIO from openpyxl import load_workbook -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from django.utils.translation import gettext as _ from rest_framework.authentication import SessionAuthentication @@ -19,16 +19,18 @@ from seahub.api2.utils import api_error from seahub.api2.endpoints.utils import is_org_user from seahub.api2.throttling import UserRateThrottle from seahub.api2.authentication import TokenAuthentication -from seahub.avatar.settings import AVATAR_DEFAULT_SIZE from seahub.base.templatetags.seahub_tags import email2nickname from seahub.utils import string2list, is_org_context, get_file_type_and_ext +from seahub.group.models import GroupInviteLinkModel from seahub.utils.ms_excel import write_xls from seahub.utils.error_msg import file_type_error_msg from seahub.base.accounts import User from seahub.group.signals import add_user_to_group +from seahub.group.views import group_invite from seahub.group.utils import is_group_member, is_group_admin, \ is_group_owner, is_group_admin_or_owner, get_group_member_info from seahub.profile.models import Profile +from seahub.settings import MULTI_TENANCY from .utils import api_check_group @@ -541,3 +543,96 @@ class GroupMembersImportExample(APIView): wb.save(response) return response + + +class GroupInviteLinks(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + @api_check_group + def get(self, request, group_id): + """ + Get invitation link + """ + group_id = int(group_id) + email = request.user.username + + if MULTI_TENANCY: + error_msg = 'Feature disabled.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + group = ccnet_api.get_group(group_id) + if group.creator_name == "system admin": + error_msg = 'Forbidden to operate department group' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if not is_group_admin_or_owner(group_id, email): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + invite_link_query_set = GroupInviteLinkModel.objects.filter(group_id=group_id) + except Exception as e: + logger.error(f'query group invite links failed. {e}') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error') + + return Response({'group_invite_link_list': [group_invite_link.to_dict() for group_invite_link in + invite_link_query_set]}) + + @api_check_group + def post(self, request, group_id): + group_id = int(group_id) + email = request.user.username + if MULTI_TENANCY: + error_msg = 'Feature disabled.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + group = ccnet_api.get_group(group_id) + if group.creator_name == "system admin": + error_msg = 'Forbidden to operate department group' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if not is_group_admin_or_owner(group_id, email): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + invite_link = GroupInviteLinkModel.objects.create_link(group_id, email) + except Exception as e: + logger.error(f'create group invite links failed. {e}') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error') + + return Response(invite_link.to_dict()) + + +class GroupInviteLink(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + @api_check_group + def delete(self, request, group_id, token): + group_id = int(group_id) + email = request.user.username + + if MULTI_TENANCY: + error_msg = 'Feature disabled.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + group = ccnet_api.get_group(group_id) + if group.creator_name == "system admin": + error_msg = 'Forbidden to operate department group' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if not is_group_admin_or_owner(group_id, email): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + GroupInviteLinkModel.objects.filter(token=token, group_id=group_id).delete() + except Exception as e: + logger.error(f'delete group invite links failed. {e}') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error') + + return Response({'success': True}) \ No newline at end of file diff --git a/seahub/group/models.py b/seahub/group/models.py new file mode 100644 index 0000000000..02acb87684 --- /dev/null +++ b/seahub/group/models.py @@ -0,0 +1,41 @@ +import logging +import uuid +from django.db import models +from seahub.settings import SERVICE_URL +from seahub.utils.timeutils import datetime_to_isoformat_timestr +from seahub.base.templatetags.seahub_tags import email2nickname +logger = logging.getLogger(__name__) + + +class GroupInviteLinkModelManager(models.Manager): + def create_link(self, group_id, email): + token = uuid.uuid4().hex[:8] + while self.model.objects.filter(token=token).exists(): + token = uuid.uuid4().hex[:8] + + group_invite_link = super(GroupInviteLinkModelManager, self).create( + group_id=group_id, token=token, created_by=email) + return group_invite_link + + +class GroupInviteLinkModel(models.Model): + token = models.CharField(max_length=40, db_index=True) + group_id = models.IntegerField(db_index=True, null=False) + created_at = models.DateTimeField(auto_now_add=True) + created_by = models.CharField(max_length=255) + + objects = GroupInviteLinkModelManager() + + class Meta: + db_table = 'group_invite_link' + + def to_dict(self): + result = { + 'id': self.pk, + 'token': self.token, + 'group_id': self.group_id, + 'created_at': datetime_to_isoformat_timestr(self.created_at), + 'created_by': email2nickname(self.created_by), + 'link': f"{SERVICE_URL.rstrip('/')}/group-invite/{self.token}/", + } + return result \ No newline at end of file diff --git a/seahub/group/views.py b/seahub/group/views.py index f644bb259d..b6c27c6f11 100644 --- a/seahub/group/views.py +++ b/seahub/group/views.py @@ -18,9 +18,10 @@ from seahub.auth import REDIRECT_FIELD_NAME from seahub.base.decorators import sys_staff_required, require_POST from seahub.group.utils import validate_group_name, BadGroupNameError, \ ConflictGroupNameError, is_group_member -from seahub.settings import SITE_ROOT +from seahub.group.models import GroupInviteLinkModel +from seahub.settings import SITE_ROOT, SERVICE_URL, MULTI_TENANCY from seahub.utils import send_html_email, is_org_context, \ - get_site_name + get_site_name, render_error from seahub.share.models import ExtraGroupsSharePermission @@ -170,3 +171,33 @@ def send_group_member_add_mail(request, group, from_user, to_user): subject = _('You are invited to join a group on %s') % get_site_name() send_html_email(subject, 'group/add_member_email.html', c, None, [to_user]) + +@login_required +def group_invite(request, token): + """ + registered user add to group + """ + if MULTI_TENANCY: + return render_error(request, _('Feature disabled.')) + + email = request.user.username + next_url = request.GET.get('next', '/') + redirect_to = SERVICE_URL.rstrip('/') + '/' + next_url.lstrip('/') + group_invite_link = GroupInviteLinkModel.objects.filter(token=token).first() + if not group_invite_link: + return render_error(request, _('Group invite link does not exist')) + + if is_group_member(group_invite_link.group_id, email): + + return HttpResponseRedirect(redirect_to) + + if not group_invite_link.created_by: + return render_error(request, _('Group invite link broken')) + + try: + ccnet_api.group_add_member(group_invite_link.group_id, group_invite_link.created_by, email) + except Exception as e: + logger.error(f'group invite add user failed. {e}') + return render_error(request, 'Internal Server Error') + + return HttpResponseRedirect(redirect_to) diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html index 2e5b9e0cb4..988e27f165 100644 --- a/seahub/templates/base_for_react.html +++ b/seahub/templates/base_for_react.html @@ -158,6 +158,7 @@ enableWhiteboard: {% if enable_whiteboard %} true {% else %} false {% endif %}, isOrgContext: {% if org is not None %} true {% else %} false {% endif %}, enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %}, + isMultiTenacy: {% if multi_tenancy %} true {% else %} false {% endif %}, enableFileTags: {% if enable_file_tags %} true {% else %} false {% endif %}, enableShowAbout: {% if enable_show_about %} true {% else %} false {% endif %}, enableMultipleOfficeSuite: {% if user.permissions.can_choose_office_suite %} true {% else %} false {% endif %}, diff --git a/seahub/urls.py b/seahub/urls.py index cb44117b19..bd05ffdd3a 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -45,7 +45,8 @@ from seahub.api2.endpoints.address_book.groups import AddressBookGroupsSubGroups from seahub.api2.endpoints.address_book.members import AddressBookGroupsSearchMember from seahub.api2.endpoints.group_members import GroupMembers, GroupSearchMember, GroupMember, \ - GroupMembersBulk, GroupMembersImport, GroupMembersImportExample + GroupMembersBulk, GroupMembersImport, GroupMembersImportExample, GroupInviteLinks, GroupInviteLink, \ + group_invite from seahub.api2.endpoints.search_group import SearchGroup from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \ ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo, \ @@ -301,6 +302,7 @@ urlpatterns = [ path('my-libs/', react_fake_view, name="my_libs"), path('groups/', react_fake_view, name="groups"), path('group//', react_fake_view, name="group"), + re_path(r'^group-invite/(?P[-0-9a-f]{8})/$', group_invite, name='group_invite'), re_path(r'^library/(?P[-0-9a-f]{36})/$', react_fake_view, name="library_view"), re_path(r'^library/(?P[-0-9a-f]{36})/(?P[^/]+)/(?P.*)$', react_fake_view, name="lib_view"), re_path(r'^remote-library/(?P[-0-9a-f]{36})/(?P[-0-9a-f]{36})/(?P[^/]+)/(?P.*)$', react_fake_view, name="remote_lib_view"), @@ -367,7 +369,8 @@ urlpatterns = [ re_path(r'^api/v2.1/group-members-import-example/$', GroupMembersImportExample.as_view(), name='api-v2.1-group-members-import-example'), re_path(r'^api/v2.1/groups/(?P\d+)/members/(?P[^/]+)/$', GroupMember.as_view(), name='api-v2.1-group-member'), re_path(r'^api/v2.1/search-group/$', SearchGroup.as_view(), name='api-v2.1-search-group'), - + re_path(r'^api/v2.1/groups/(?P\d+)/invite-links/$', GroupInviteLinks.as_view(),name='api-v2.1-group-invite-links'), + re_path(r'^api/v2.1/groups/(?P\d+)/invite-links/(?P[-0-9a-f]{8})/$', GroupInviteLink.as_view(), name='api-v2.1-group-invite-link'), ## address book re_path(r'^api/v2.1/address-book/groups/(?P\d+)/sub-groups/$', AddressBookGroupsSubGroups.as_view(), name='api-v2.1-address-book-groups-sub-groups'), re_path(r'^api/v2.1/address-book/groups/(?P\d+)/search-member/$', AddressBookGroupsSearchMember.as_view(), name='api-v2.1-address-book-search-member'), diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 9d768d945d..867df8aab5 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -58,7 +58,7 @@ from seahub.settings import AVATAR_FILE_STORAGE, ENABLE_REPO_SNAPSHOT_LABEL, \ UPLOAD_LINK_EXPIRE_DAYS_MIN, UPLOAD_LINK_EXPIRE_DAYS_MAX, UPLOAD_LINK_EXPIRE_DAYS_DEFAULT, \ ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, \ ADDITIONAL_SHARE_DIALOG_NOTE, ADDITIONAL_ABOUT_DIALOG_LINKS, \ - SEADOC_SERVER_URL, SHOW_WECHAT_SUPPORT_GROUP + SEADOC_SERVER_URL, SHOW_WECHAT_SUPPORT_GROUP, MULTI_TENANCY from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS from seahub.ocm_via_webdav.settings import ENABLE_OCM_VIA_WEBDAV @@ -1148,7 +1148,8 @@ def react_fake_view(request, **kwargs): 'enable_sso_to_thirdpart_website': settings.ENABLE_SSO_TO_THIRDPART_WEBSITE, 'enable_metadata_management': ENABLE_METADATA_MANAGEMENT, 'enable_file_tags': settings.ENABLE_FILE_TAGS, - 'enable_show_about': settings.ENABLE_SHOW_ABOUT + 'enable_show_about': settings.ENABLE_SHOW_ABOUT, + 'multi_tenancy': MULTI_TENANCY, } if ENABLE_METADATA_MANAGEMENT: diff --git a/sql/mysql.sql b/sql/mysql.sql index 90c67ab9f5..ad2594b3be 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -1587,4 +1587,15 @@ CREATE TABLE `RepoTransfer` ( PRIMARY KEY (`id`), KEY `idx_file_transfer_org_id` (`org_id`), KEY `idx_file_transfer_timestamp` (`timestamp`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE `group_invite_link` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `token` varchar(40) NOT NULL, + `group_id` int(11) NOT NULL, + `created_at` datetime(6) NOT NULL, + `created_by` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + KEY `group_invite_link_group_id` (`group_id`), + KEY `group_invite_link_token` (`token`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; From f6f606cb8f465358604557c8ae19246346d0f9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E6=B0=B8=E5=BC=BA?= <11704063+s-yongqiang@user.noreply.gitee.com> Date: Mon, 24 Feb 2025 09:58:26 +0800 Subject: [PATCH 2/6] optimize --- seahub/group/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/seahub/group/views.py b/seahub/group/views.py index b6c27c6f11..d6daecdb0f 100644 --- a/seahub/group/views.py +++ b/seahub/group/views.py @@ -181,12 +181,11 @@ def group_invite(request, token): return render_error(request, _('Feature disabled.')) email = request.user.username - next_url = request.GET.get('next', '/') - redirect_to = SERVICE_URL.rstrip('/') + '/' + next_url.lstrip('/') group_invite_link = GroupInviteLinkModel.objects.filter(token=token).first() if not group_invite_link: return render_error(request, _('Group invite link does not exist')) + redirect_to = reverse('group', args=[group_invite_link.group_id]) if is_group_member(group_invite_link.group_id, email): return HttpResponseRedirect(redirect_to) From a8a2fd638243a354224892d16b9572b98674bf1d Mon Sep 17 00:00:00 2001 From: r350178982 <32759763+r350178982@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:42:47 +0800 Subject: [PATCH 3/6] update --- frontend/src/css/group-invite-members-dialog.css | 2 +- seahub/api2/endpoints/group_members.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/css/group-invite-members-dialog.css b/frontend/src/css/group-invite-members-dialog.css index 4792452e6e..bc6dfc395b 100644 --- a/frontend/src/css/group-invite-members-dialog.css +++ b/frontend/src/css/group-invite-members-dialog.css @@ -37,4 +37,4 @@ .invite-link-item .delete-link-btn { color: #999; width: 40px; -} \ No newline at end of file +} diff --git a/seahub/api2/endpoints/group_members.py b/seahub/api2/endpoints/group_members.py index c52612efeb..1b7bccd8be 100644 --- a/seahub/api2/endpoints/group_members.py +++ b/seahub/api2/endpoints/group_members.py @@ -635,4 +635,4 @@ class GroupInviteLink(APIView): logger.error(f'delete group invite links failed. {e}') return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error') - return Response({'success': True}) \ No newline at end of file + return Response({'success': True}) From 67dd7e5eedc99d03ffe159fdc40cb63cb142cbf0 Mon Sep 17 00:00:00 2001 From: r350178982 <32759763+r350178982@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:44:11 +0800 Subject: [PATCH 4/6] Update models.py --- seahub/group/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seahub/group/models.py b/seahub/group/models.py index 02acb87684..c61d64112e 100644 --- a/seahub/group/models.py +++ b/seahub/group/models.py @@ -38,4 +38,4 @@ class GroupInviteLinkModel(models.Model): 'created_by': email2nickname(self.created_by), 'link': f"{SERVICE_URL.rstrip('/')}/group-invite/{self.token}/", } - return result \ No newline at end of file + return result From 47f016e6e9aa689b4a5416885e90eb367bfafa30 Mon Sep 17 00:00:00 2001 From: Michael An <2331806369@qq.com> Date: Wed, 26 Feb 2025 10:10:19 +0800 Subject: [PATCH 5/6] change dialog style --- .../components/dialog/group-invite-members-dialog.js | 10 ++++------ frontend/src/css/group-invite-members-dialog.css | 12 +++--------- frontend/src/pages/groups/group-view.js | 7 +++---- frontend/src/utils/seafile-api.js | 6 +++--- sql/mysql.sql | 2 +- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/dialog/group-invite-members-dialog.js b/frontend/src/components/dialog/group-invite-members-dialog.js index b69440cd4b..26bf45b8a4 100644 --- a/frontend/src/components/dialog/group-invite-members-dialog.js +++ b/frontend/src/components/dialog/group-invite-members-dialog.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Button, Modal, ModalBody } from 'reactstrap'; +import { Button, Modal, ModalBody, Label } from 'reactstrap'; import SeahubModalHeader from '@/components/common/seahub-modal-header'; import copy from 'copy-to-clipboard'; import toaster from '../toast'; @@ -12,7 +12,7 @@ import '../../css/group-invite-members-dialog.css'; const propTypes = { groupID: PropTypes.string.isRequired, - toggleGroupInviteDialog: PropTypes.func.isRequired, + toggleInviteMembersDialog: PropTypes.func.isRequired, }; class GroupInviteMembersDialog extends React.Component { @@ -69,7 +69,7 @@ class GroupInviteMembersDialog extends React.Component { }; toggle = () => { - this.props.toggleGroupInviteDialog(); + this.props.toggleInviteMembersDialog(); }; render() { @@ -81,9 +81,7 @@ class GroupInviteMembersDialog extends React.Component { {link ? <> -
- {gettext('Group invitation link')} -
+
{link.link}
diff --git a/frontend/src/css/group-invite-members-dialog.css b/frontend/src/css/group-invite-members-dialog.css index bc6dfc395b..cfee611edc 100644 --- a/frontend/src/css/group-invite-members-dialog.css +++ b/frontend/src/css/group-invite-members-dialog.css @@ -1,17 +1,12 @@ -.group-invite-members th, -.group-invite-members td { - vertical-align: middle; - text-align: left; -} - .group-invite-members .no-link-tip { line-height: 24px; - color: #999; + color: #666; + font-size: 14px; } .invite-link-item { display: flex; - margin: 1rem 0 2.5rem; + margin-bottom: 2.5rem; } .invite-link-item .form-item { @@ -36,5 +31,4 @@ .invite-link-item .delete-link-btn { color: #999; - width: 40px; } diff --git a/frontend/src/pages/groups/group-view.js b/frontend/src/pages/groups/group-view.js index 8c2446a470..92e386d36f 100644 --- a/frontend/src/pages/groups/group-view.js +++ b/frontend/src/pages/groups/group-view.js @@ -374,7 +374,6 @@ class GroupView extends React.Component { getOpList = () => { const { currentGroup, isDepartmentGroup, isStaff, isOwner } = this.state; - // const isGroup = this.state.currentGroup.owner !== 'system admin'; const opList = []; if ((!isDepartmentGroup && canAddRepo) || (isDepartmentGroup && isStaff)) { @@ -401,7 +400,7 @@ class GroupView extends React.Component { } if (isOwner && this.state.currentGroup.owner !== 'system admin' && !isMultiTenancy) { - opList.push({ 'text': gettext('Invite Members'), 'onClick': this.toggleInviteMembersDialog }); + opList.push({ 'text': gettext('Invite members'), 'onClick': this.toggleInviteMembersDialog }); } } @@ -606,8 +605,8 @@ class GroupView extends React.Component { {this.state.showInviteMembersDialog && + toggleInviteMembersDialog={this.toggleInviteMembersDialog} + /> } ); diff --git a/frontend/src/utils/seafile-api.js b/frontend/src/utils/seafile-api.js index 91b3b3a076..5fa5fe2532 100644 --- a/frontend/src/utils/seafile-api.js +++ b/frontend/src/utils/seafile-api.js @@ -304,18 +304,18 @@ class SeafileAPI { } deleteGroupInviteLinks(groupID, token) { - const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/' + token + '/'; + const url = `${this.server}/api/v2.1/groups/${groupID}/invite-links/${token}/`; return this.req.delete(url); } addGroupInviteLinks(groupID) { - const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/'; + const url = `${this.server}/api/v2.1/groups/${groupID}/invite-links/`; let formData = new FormData(); return this._sendPostRequest(url, formData); } getGroupInviteLinks(groupID) { - const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/'; + const url = `${this.server}/api/v2.1/groups/${groupID}/invite-links/`; return this.req.get(url); } diff --git a/sql/mysql.sql b/sql/mysql.sql index ad2594b3be..cdb0371507 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -1587,7 +1587,7 @@ CREATE TABLE `RepoTransfer` ( PRIMARY KEY (`id`), KEY `idx_file_transfer_org_id` (`org_id`), KEY `idx_file_transfer_timestamp` (`timestamp`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; CREATE TABLE `group_invite_link` ( `id` int(11) NOT NULL AUTO_INCREMENT, From e44962be9ae62c0c1301d41c449255012ca053b8 Mon Sep 17 00:00:00 2001 From: Michael An <2331806369@qq.com> Date: Wed, 26 Feb 2025 11:42:54 +0800 Subject: [PATCH 6/6] change button style --- .../components/dialog/group-invite-members-dialog.js | 4 ++-- frontend/src/css/group-invite-members-dialog.css | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/dialog/group-invite-members-dialog.js b/frontend/src/components/dialog/group-invite-members-dialog.js index 26bf45b8a4..5d8e83c5ee 100644 --- a/frontend/src/components/dialog/group-invite-members-dialog.js +++ b/frontend/src/components/dialog/group-invite-members-dialog.js @@ -87,8 +87,8 @@ class GroupInviteMembersDialog extends React.Component {
-
diff --git a/frontend/src/css/group-invite-members-dialog.css b/frontend/src/css/group-invite-members-dialog.css index cfee611edc..2b5c2d09fc 100644 --- a/frontend/src/css/group-invite-members-dialog.css +++ b/frontend/src/css/group-invite-members-dialog.css @@ -30,5 +30,15 @@ } .invite-link-item .delete-link-btn { - color: #999; + border-color: #ff9800; + color: #ff9800; + font-size: 16px; + padding: 0; +} + +.invite-link-item .delete-link-btn:hover { + background-color: #d98100; + border-color: #d98100; + box-shadow: none; + color: #fff; }