diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index 1b202fad733d4c..7c86df05071403 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -395,11 +395,6 @@ class DataSetConfig(BaseModel): default=30, ) - DATASET_OPERATOR_ENABLED: bool = Field( - description='whether to enable dataset operator', - default=False, - ) - class WorkspaceConfig(BaseModel): """ diff --git a/api/controllers/console/datasets/datasets.py b/api/controllers/console/datasets/datasets.py index f98c0071a93959..fdd61b0a0c73d5 100644 --- a/api/controllers/console/datasets/datasets.py +++ b/api/controllers/console/datasets/datasets.py @@ -25,7 +25,7 @@ from libs.login import login_required from models.dataset import Dataset, Document, DocumentSegment from models.model import ApiToken, UploadFile -from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService +from services.dataset_service import DatasetService, DocumentService def _validate_name(name): @@ -85,12 +85,6 @@ def get(self): else: item['embedding_available'] = True - if item.get('permission') == 'partial_members': - part_users_list = DatasetPermissionService.get_dataset_partial_member_list(item['id']) - item.update({'partial_member_list': part_users_list}) - else: - item.update({'partial_member_list': []}) - response = { 'data': data, 'has_more': len(datasets) == limit, @@ -114,7 +108,7 @@ def post(self): help='Invalid indexing technique.') args = parser.parse_args() - # The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator + # The role of the current user in the ta table must be admin, owner, or editor if not current_user.is_editor: raise Forbidden() @@ -146,10 +140,6 @@ def get(self, dataset_id): except services.errors.account.NoPermissionError as e: raise Forbidden(str(e)) data = marshal(dataset, dataset_detail_fields) - if data.get('permission') == 'partial_members': - part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) - data.update({'partial_member_list': part_users_list}) - # check embedding setting provider_manager = ProviderManager() configurations = provider_manager.get_configurations( @@ -173,11 +163,6 @@ def get(self, dataset_id): data['embedding_available'] = False else: data['embedding_available'] = True - - if data.get('permission') == 'partial_members': - part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) - data.update({'partial_member_list': part_users_list}) - return data, 200 @setup_required @@ -203,21 +188,17 @@ def patch(self, dataset_id): nullable=True, help='Invalid indexing technique.') parser.add_argument('permission', type=str, location='json', choices=( - 'only_me', 'all_team_members', 'partial_members'), help='Invalid permission.' - ) + 'only_me', 'all_team_members'), help='Invalid permission.') parser.add_argument('embedding_model', type=str, location='json', help='Invalid embedding model.') parser.add_argument('embedding_model_provider', type=str, location='json', help='Invalid embedding model provider.') parser.add_argument('retrieval_model', type=dict, location='json', help='Invalid retrieval model.') - parser.add_argument('partial_member_list', type=list, location='json', help='Invalid parent user list.') args = parser.parse_args() - data = request.get_json() - # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator - DatasetPermissionService.check_permission( - current_user, dataset, data.get('permission'), data.get('partial_member_list') - ) + # The role of the current user in the ta table must be admin, owner, or editor + if not current_user.is_editor: + raise Forbidden() dataset = DatasetService.update_dataset( dataset_id_str, args, current_user) @@ -225,17 +206,7 @@ def patch(self, dataset_id): if dataset is None: raise NotFound("Dataset not found.") - result_data = marshal(dataset, dataset_detail_fields) - - if data.get('partial_member_list') and data.get('permission') == 'partial_members': - DatasetPermissionService.update_partial_member_list(dataset_id_str, data.get('partial_member_list')) - else: - DatasetPermissionService.clear_partial_member_list(dataset_id_str) - - partial_member_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) - result_data.update({'partial_member_list': partial_member_list}) - - return result_data, 200 + return marshal(dataset, dataset_detail_fields), 200 @setup_required @login_required @@ -244,7 +215,7 @@ def delete(self, dataset_id): dataset_id_str = str(dataset_id) # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor or current_user.is_dataset_operator: + if not current_user.is_editor: raise Forbidden() try: @@ -598,27 +569,6 @@ def get(self, dataset_id): }, 200 -class DatasetPermissionUserListApi(Resource): - @setup_required - @login_required - @account_initialization_required - def get(self, dataset_id): - dataset_id_str = str(dataset_id) - dataset = DatasetService.get_dataset(dataset_id_str) - if dataset is None: - raise NotFound("Dataset not found.") - try: - DatasetService.check_dataset_permission(dataset, current_user) - except services.errors.account.NoPermissionError as e: - raise Forbidden(str(e)) - - partial_members_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) - - return { - 'data': partial_members_list, - }, 200 - - api.add_resource(DatasetListApi, '/datasets') api.add_resource(DatasetApi, '/datasets/') api.add_resource(DatasetUseCheckApi, '/datasets//use-check') @@ -632,4 +582,3 @@ def get(self, dataset_id): api.add_resource(DatasetApiBaseUrlApi, '/datasets/api-base-info') api.add_resource(DatasetRetrievalSettingApi, '/datasets/retrieval-setting') api.add_resource(DatasetRetrievalSettingMockApi, '/datasets/retrieval-setting/') -api.add_resource(DatasetPermissionUserListApi, '/datasets//permission-part-users') diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index afe0ca7c69b2b7..b3a253c167768f 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -228,7 +228,7 @@ def post(self, dataset_id): raise NotFound('Dataset not found.') # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_dataset_editor: + if not current_user.is_editor: raise Forbidden() try: @@ -294,11 +294,6 @@ def post(self): parser.add_argument('retrieval_model', type=dict, required=False, nullable=False, location='json') args = parser.parse_args() - - # The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator - if not current_user.is_dataset_editor: - raise Forbidden() - if args['indexing_technique'] == 'high_quality': try: model_manager = ModelManager() @@ -762,19 +757,15 @@ def patch(self, dataset_id, document_id, action): dataset = DatasetService.get_dataset(dataset_id) if dataset is None: raise NotFound("Dataset not found.") - - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_dataset_editor: - raise Forbidden() - # check user's model setting DatasetService.check_dataset_model_setting(dataset) - # check user's permission - DatasetService.check_dataset_permission(dataset, current_user) - document = self.get_document(dataset_id, document_id) + # The role of the current user in the ta table must be admin, owner, or editor + if not current_user.is_editor: + raise Forbidden() + indexing_cache_key = 'document_{}_indexing'.format(document.id) cache_result = redis_client.get(indexing_cache_key) if cache_result is not None: @@ -964,11 +955,10 @@ class DocumentRenameApi(DocumentResource): @account_initialization_required @marshal_with(document_fields) def post(self, dataset_id, document_id): - # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator - if not current_user.is_dataset_editor: + # The role of the current user in the ta table must be admin or owner + if not current_user.is_admin_or_owner: raise Forbidden() - dataset = DatasetService.get_dataset(dataset_id) - DatasetService.check_dataset_operator_permission(current_user, dataset) + parser = reqparse.RequestParser() parser.add_argument('name', type=str, required=True, nullable=False, location='json') args = parser.parse_args() diff --git a/api/controllers/console/tag/tags.py b/api/controllers/console/tag/tags.py index 004afaa531a086..55b212358d90de 100644 --- a/api/controllers/console/tag/tags.py +++ b/api/controllers/console/tag/tags.py @@ -36,7 +36,7 @@ def get(self): @account_initialization_required def post(self): # The role of the current user in the ta table must be admin, owner, or editor - if not (current_user.is_editor or current_user.is_dataset_editor): + if not current_user.is_editor: raise Forbidden() parser = reqparse.RequestParser() @@ -68,7 +68,7 @@ class TagUpdateDeleteApi(Resource): def patch(self, tag_id): tag_id = str(tag_id) # The role of the current user in the ta table must be admin, owner, or editor - if not (current_user.is_editor or current_user.is_dataset_editor): + if not current_user.is_editor: raise Forbidden() parser = reqparse.RequestParser() @@ -109,8 +109,8 @@ class TagBindingCreateApi(Resource): @login_required @account_initialization_required def post(self): - # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator - if not (current_user.is_editor or current_user.is_dataset_editor): + # The role of the current user in the ta table must be admin, owner, or editor + if not current_user.is_editor: raise Forbidden() parser = reqparse.RequestParser() @@ -134,8 +134,8 @@ class TagBindingDeleteApi(Resource): @login_required @account_initialization_required def post(self): - # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator - if not (current_user.is_editor or current_user.is_dataset_editor): + # The role of the current user in the ta table must be admin, owner, or editor + if not current_user.is_editor: raise Forbidden() parser = reqparse.RequestParser() diff --git a/api/controllers/console/workspace/members.py b/api/controllers/console/workspace/members.py index e8c88850a42aa7..f404ca7efc4d2d 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -131,20 +131,7 @@ def put(self, member_id): return {'result': 'success'} -class DatasetOperatorMemberListApi(Resource): - """List all members of current tenant.""" - - @setup_required - @login_required - @account_initialization_required - @marshal_with(account_with_role_list_fields) - def get(self): - members = TenantService.get_dataset_operator_members(current_user.current_tenant) - return {'result': 'success', 'accounts': members}, 200 - - api.add_resource(MemberListApi, '/workspaces/current/members') api.add_resource(MemberInviteEmailApi, '/workspaces/current/members/invite-email') api.add_resource(MemberCancelInviteApi, '/workspaces/current/members/') api.add_resource(MemberUpdateRoleApi, '/workspaces/current/members//update-role') -api.add_resource(DatasetOperatorMemberListApi, '/workspaces/current/dataset-operators') diff --git a/api/migrations/versions/7e6a8693e07a_add_table_dataset_permissions.py b/api/migrations/versions/7e6a8693e07a_add_table_dataset_permissions.py deleted file mode 100644 index ff53eb65a6f56c..00000000000000 --- a/api/migrations/versions/7e6a8693e07a_add_table_dataset_permissions.py +++ /dev/null @@ -1,42 +0,0 @@ -"""add table dataset_permissions - -Revision ID: 7e6a8693e07a -Revises: 4ff534e1eb11 -Create Date: 2024-06-25 03:20:46.012193 - -""" -import sqlalchemy as sa -from alembic import op - -import models as models - -# revision identifiers, used by Alembic. -revision = '7e6a8693e07a' -down_revision = 'b2602e131636' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('dataset_permissions', - sa.Column('id', models.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False), - sa.Column('dataset_id', models.StringUUID(), nullable=False), - sa.Column('account_id', models.StringUUID(), nullable=False), - sa.Column('has_permission', sa.Boolean(), server_default=sa.text('true'), nullable=False), - sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP(0)'), nullable=False), - sa.PrimaryKeyConstraint('id', name='dataset_permission_pkey') - ) - with op.batch_alter_table('dataset_permissions', schema=None) as batch_op: - batch_op.create_index('idx_dataset_permissions_account_id', ['account_id'], unique=False) - batch_op.create_index('idx_dataset_permissions_dataset_id', ['dataset_id'], unique=False) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('dataset_permissions', schema=None) as batch_op: - batch_op.drop_index('idx_dataset_permissions_dataset_id') - batch_op.drop_index('idx_dataset_permissions_account_id') - op.drop_table('dataset_permissions') - # ### end Alembic commands ### diff --git a/api/models/account.py b/api/models/account.py index 23e7528d22fa67..3b258c4c82fe8f 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -80,10 +80,6 @@ def current_tenant_id(self, value): self._current_tenant = tenant - @property - def current_role(self): - return self._current_tenant.current_role - def get_status(self) -> AccountStatus: status_str = self.status return AccountStatus(status_str) @@ -114,14 +110,6 @@ def is_admin_or_owner(self): def is_editor(self): return TenantAccountRole.is_editing_role(self._current_tenant.current_role) - @property - def is_dataset_editor(self): - return TenantAccountRole.is_dataset_edit_role(self._current_tenant.current_role) - - @property - def is_dataset_operator(self): - return self._current_tenant.current_role == TenantAccountRole.DATASET_OPERATOR - class TenantStatus(str, enum.Enum): NORMAL = 'normal' ARCHIVE = 'archive' @@ -132,12 +120,10 @@ class TenantAccountRole(str, enum.Enum): ADMIN = 'admin' EDITOR = 'editor' NORMAL = 'normal' - DATASET_OPERATOR = 'dataset_operator' @staticmethod def is_valid_role(role: str) -> bool: - return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, - TenantAccountRole.NORMAL, TenantAccountRole.DATASET_OPERATOR} + return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL} @staticmethod def is_privileged_role(role: str) -> bool: @@ -145,17 +131,12 @@ def is_privileged_role(role: str) -> bool: @staticmethod def is_non_owner_role(role: str) -> bool: - return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL, - TenantAccountRole.DATASET_OPERATOR} + return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL} @staticmethod def is_editing_role(role: str) -> bool: return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR} - @staticmethod - def is_dataset_edit_role(role: str) -> bool: - return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, - TenantAccountRole.DATASET_OPERATOR} class Tenant(db.Model): __tablename__ = 'tenants' @@ -191,7 +172,6 @@ class TenantAccountJoinRole(enum.Enum): OWNER = 'owner' ADMIN = 'admin' NORMAL = 'normal' - DATASET_OPERATOR = 'dataset_operator' class TenantAccountJoin(db.Model): diff --git a/api/models/dataset.py b/api/models/dataset.py index 7c8a871aea1dc5..672c2be8fabdcc 100644 --- a/api/models/dataset.py +++ b/api/models/dataset.py @@ -663,18 +663,3 @@ class DatasetCollectionBinding(db.Model): type = db.Column(db.String(40), server_default=db.text("'dataset'::character varying"), nullable=False) collection_name = db.Column(db.String(64), nullable=False) created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) - - -class DatasetPermission(db.Model): - __tablename__ = 'dataset_permissions' - __table_args__ = ( - db.PrimaryKeyConstraint('id', name='dataset_permission_pkey'), - db.Index('idx_dataset_permissions_dataset_id', 'dataset_id'), - db.Index('idx_dataset_permissions_account_id', 'account_id') - ) - - id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'), primary_key=True) - dataset_id = db.Column(StringUUID, nullable=False) - account_id = db.Column(StringUUID, nullable=False) - has_permission = db.Column(db.Boolean, nullable=False, server_default=db.text('true')) - created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) diff --git a/api/services/account_service.py b/api/services/account_service.py index 3fd2b5c62772f2..36c24ef7bf4c59 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -369,28 +369,6 @@ def get_tenant_members(tenant: Tenant) -> list[Account]: return updated_accounts - @staticmethod - def get_dataset_operator_members(tenant: Tenant) -> list[Account]: - """Get dataset admin members""" - query = ( - db.session.query(Account, TenantAccountJoin.role) - .select_from(Account) - .join( - TenantAccountJoin, Account.id == TenantAccountJoin.account_id - ) - .filter(TenantAccountJoin.tenant_id == tenant.id) - .filter(TenantAccountJoin.role == 'dataset_operator') - ) - - # Initialize an empty list to store the updated accounts - updated_accounts = [] - - for account, role in query: - account.role = role - updated_accounts.append(account) - - return updated_accounts - @staticmethod def has_roles(tenant: Tenant, roles: list[TenantAccountJoinRole]) -> bool: """Check if user has any of the given roles for a tenant""" diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 45f8cc62e48fd6..6207a1a45c2db8 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -21,12 +21,11 @@ from extensions.ext_database import db from extensions.ext_redis import redis_client from libs import helper -from models.account import Account, TenantAccountRole +from models.account import Account from models.dataset import ( AppDatasetJoin, Dataset, DatasetCollectionBinding, - DatasetPermission, DatasetProcessRule, DatasetQuery, Document, @@ -57,38 +56,22 @@ class DatasetService: @staticmethod def get_datasets(page, per_page, provider="vendor", tenant_id=None, user=None, search=None, tag_ids=None): - query = Dataset.query.filter(Dataset.provider == provider, Dataset.tenant_id == tenant_id) - if user: - if user.current_role == TenantAccountRole.DATASET_OPERATOR: - dataset_permission = DatasetPermission.query.filter_by(account_id=user.id).all() - if dataset_permission: - dataset_ids = [dp.dataset_id for dp in dataset_permission] - query = query.filter(Dataset.id.in_(dataset_ids)) - else: - query = query.filter(db.false()) - else: - permission_filter = db.or_( - Dataset.created_by == user.id, - Dataset.permission == 'all_team_members', - Dataset.permission == 'partial_members', - Dataset.permission == 'only_me' - ) - query = query.filter(permission_filter) + permission_filter = db.or_(Dataset.created_by == user.id, + Dataset.permission == 'all_team_members') else: permission_filter = Dataset.permission == 'all_team_members' - query = query.filter(permission_filter) - + query = Dataset.query.filter( + db.and_(Dataset.provider == provider, Dataset.tenant_id == tenant_id, permission_filter)) \ + .order_by(Dataset.created_at.desc()) if search: - query = query.filter(Dataset.name.ilike(f'%{search}%')) - + query = query.filter(db.and_(Dataset.name.ilike(f'%{search}%'))) if tag_ids: target_ids = TagService.get_target_ids_by_tag_ids('knowledge', tenant_id, tag_ids) if target_ids: - query = query.filter(Dataset.id.in_(target_ids)) + query = query.filter(db.and_(Dataset.id.in_(target_ids))) else: return [], 0 - datasets = query.paginate( page=page, per_page=per_page, @@ -96,12 +79,6 @@ def get_datasets(page, per_page, provider="vendor", tenant_id=None, user=None, s error_out=False ) - # check datasets permission, - if user and user.current_role != TenantAccountRole.DATASET_OPERATOR: - datasets.items, datasets.total = DatasetService.filter_datasets_by_permission( - user, datasets - ) - return datasets.items, datasets.total @staticmethod @@ -125,12 +102,9 @@ def get_process_rules(dataset_id): @staticmethod def get_datasets_by_ids(ids, tenant_id): - datasets = Dataset.query.filter( - Dataset.id.in_(ids), - Dataset.tenant_id == tenant_id - ).paginate( - page=1, per_page=len(ids), max_per_page=len(ids), error_out=False - ) + datasets = Dataset.query.filter(Dataset.id.in_(ids), + Dataset.tenant_id == tenant_id).paginate( + page=1, per_page=len(ids), max_per_page=len(ids), error_out=False) return datasets.items, datasets.total @staticmethod @@ -138,8 +112,7 @@ def create_empty_dataset(tenant_id: str, name: str, indexing_technique: Optional # check if dataset name already exists if Dataset.query.filter_by(name=name, tenant_id=tenant_id).first(): raise DatasetNameDuplicateError( - f'Dataset with name {name} already exists.' - ) + f'Dataset with name {name} already exists.') embedding_model = None if indexing_technique == 'high_quality': model_manager = ModelManager() @@ -178,17 +151,13 @@ def check_dataset_model_setting(dataset): except LLMBadRequestError: raise ValueError( "No Embedding Model available. Please configure a valid provider " - "in the Settings -> Model Provider." - ) + "in the Settings -> Model Provider.") except ProviderTokenNotInitError as ex: - raise ValueError( - f"The dataset in unavailable, due to: " - f"{ex.description}" - ) + raise ValueError(f"The dataset in unavailable, due to: " + f"{ex.description}") @staticmethod def update_dataset(dataset_id, data, user): - data.pop('partial_member_list', None) filtered_data = {k: v for k, v in data.items() if v is not None or k == 'description'} dataset = DatasetService.get_dataset(dataset_id) DatasetService.check_dataset_permission(dataset, user) @@ -221,13 +190,12 @@ def update_dataset(dataset_id, data, user): except LLMBadRequestError: raise ValueError( "No Embedding Model available. Please configure a valid provider " - "in the Settings -> Model Provider." - ) + "in the Settings -> Model Provider.") except ProviderTokenNotInitError as ex: raise ValueError(ex.description) else: if data['embedding_model_provider'] != dataset.embedding_model_provider or \ - data['embedding_model'] != dataset.embedding_model: + data['embedding_model'] != dataset.embedding_model: action = 'update' try: model_manager = ModelManager() @@ -247,8 +215,7 @@ def update_dataset(dataset_id, data, user): except LLMBadRequestError: raise ValueError( "No Embedding Model available. Please configure a valid provider " - "in the Settings -> Model Provider." - ) + "in the Settings -> Model Provider.") except ProviderTokenNotInitError as ex: raise ValueError(ex.description) @@ -292,41 +259,14 @@ def dataset_use_check(dataset_id) -> bool: def check_dataset_permission(dataset, user): if dataset.tenant_id != user.current_tenant_id: logging.debug( - f'User {user.id} does not have permission to access dataset {dataset.id}' - ) + f'User {user.id} does not have permission to access dataset {dataset.id}') raise NoPermissionError( - 'You do not have permission to access this dataset.' - ) + 'You do not have permission to access this dataset.') if dataset.permission == 'only_me' and dataset.created_by != user.id: logging.debug( - f'User {user.id} does not have permission to access dataset {dataset.id}' - ) + f'User {user.id} does not have permission to access dataset {dataset.id}') raise NoPermissionError( - 'You do not have permission to access this dataset.' - ) - if dataset.permission == 'partial_members': - user_permission = DatasetPermission.query.filter_by( - dataset_id=dataset.id, account_id=user.id - ).first() - if not user_permission and dataset.tenant_id != user.current_tenant_id and dataset.created_by != user.id: - logging.debug( - f'User {user.id} does not have permission to access dataset {dataset.id}' - ) - raise NoPermissionError( - 'You do not have permission to access this dataset.' - ) - - @staticmethod - def check_dataset_operator_permission(user: Account = None, dataset: Dataset = None): - if dataset.permission == 'only_me': - if dataset.created_by != user.id: - raise NoPermissionError('You do not have permission to access this dataset.') - - elif dataset.permission == 'partial_members': - if not any( - dp.dataset_id == dataset.id for dp in DatasetPermission.query.filter_by(account_id=user.id).all() - ): - raise NoPermissionError('You do not have permission to access this dataset.') + 'You do not have permission to access this dataset.') @staticmethod def get_dataset_queries(dataset_id: str, page: int, per_page: int): @@ -342,22 +282,6 @@ def get_related_apps(dataset_id: str): return AppDatasetJoin.query.filter(AppDatasetJoin.dataset_id == dataset_id) \ .order_by(db.desc(AppDatasetJoin.created_at)).all() - @staticmethod - def filter_datasets_by_permission(user, datasets): - dataset_permission = DatasetPermission.query.filter_by(account_id=user.id).all() - permitted_dataset_ids = {dp.dataset_id for dp in dataset_permission} if dataset_permission else set() - - filtered_datasets = [ - dataset for dataset in datasets if - (dataset.permission == 'all_team_members') or - (dataset.permission == 'only_me' and dataset.created_by == user.id) or - (dataset.id in permitted_dataset_ids) - ] - - filtered_count = len(filtered_datasets) - - return filtered_datasets, filtered_count - class DocumentService: DEFAULT_RULES = { @@ -623,7 +547,6 @@ def sync_website_document(dataset_id: str, document: Document): redis_client.setex(sync_indexing_cache_key, 600, 1) sync_website_document_indexing_task.delay(dataset_id, document.id) - @staticmethod def get_documents_position(dataset_id): document = Document.query.filter_by(dataset_id=dataset_id).order_by(Document.position.desc()).first() @@ -633,11 +556,9 @@ def get_documents_position(dataset_id): return 1 @staticmethod - def save_document_with_dataset_id( - dataset: Dataset, document_data: dict, - account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, - created_from: str = 'web' - ): + def save_document_with_dataset_id(dataset: Dataset, document_data: dict, + account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, + created_from: str = 'web'): # check document limit features = FeatureService.get_features(current_user.current_tenant_id) @@ -667,7 +588,7 @@ def save_document_with_dataset_id( if not dataset.indexing_technique: if 'indexing_technique' not in document_data \ - or document_data['indexing_technique'] not in Dataset.INDEXING_TECHNIQUE_LIST: + or document_data['indexing_technique'] not in Dataset.INDEXING_TECHNIQUE_LIST: raise ValueError("Indexing technique is required") dataset.indexing_technique = document_data["indexing_technique"] @@ -697,8 +618,7 @@ def save_document_with_dataset_id( } dataset.retrieval_model = document_data.get('retrieval_model') if document_data.get( - 'retrieval_model' - ) else default_retrieval_model + 'retrieval_model') else default_retrieval_model documents = [] batch = time.strftime('%Y%m%d%H%M%S') + str(random.randint(100000, 999999)) @@ -766,14 +686,12 @@ def save_document_with_dataset_id( documents.append(document) duplicate_document_ids.append(document.id) continue - document = DocumentService.build_document( - dataset, dataset_process_rule.id, - document_data["data_source"]["type"], - document_data["doc_form"], - document_data["doc_language"], - data_source_info, created_from, position, - account, file_name, batch - ) + document = DocumentService.build_document(dataset, dataset_process_rule.id, + document_data["data_source"]["type"], + document_data["doc_form"], + document_data["doc_language"], + data_source_info, created_from, position, + account, file_name, batch) db.session.add(document) db.session.flush() document_ids.append(document.id) @@ -814,14 +732,12 @@ def save_document_with_dataset_id( "notion_page_icon": page['page_icon'], "type": page['type'] } - document = DocumentService.build_document( - dataset, dataset_process_rule.id, - document_data["data_source"]["type"], - document_data["doc_form"], - document_data["doc_language"], - data_source_info, created_from, position, - account, page['page_name'], batch - ) + document = DocumentService.build_document(dataset, dataset_process_rule.id, + document_data["data_source"]["type"], + document_data["doc_form"], + document_data["doc_language"], + data_source_info, created_from, position, + account, page['page_name'], batch) db.session.add(document) db.session.flush() document_ids.append(document.id) @@ -843,14 +759,12 @@ def save_document_with_dataset_id( 'only_main_content': website_info.get('only_main_content', False), 'mode': 'crawl', } - document = DocumentService.build_document( - dataset, dataset_process_rule.id, - document_data["data_source"]["type"], - document_data["doc_form"], - document_data["doc_language"], - data_source_info, created_from, position, - account, url, batch - ) + document = DocumentService.build_document(dataset, dataset_process_rule.id, + document_data["data_source"]["type"], + document_data["doc_form"], + document_data["doc_language"], + data_source_info, created_from, position, + account, url, batch) db.session.add(document) db.session.flush() document_ids.append(document.id) @@ -871,16 +785,13 @@ def check_documents_upload_quota(count: int, features: FeatureModel): can_upload_size = features.documents_upload_quota.limit - features.documents_upload_quota.size if count > can_upload_size: raise ValueError( - f'You have reached the limit of your subscription. Only {can_upload_size} documents can be uploaded.' - ) + f'You have reached the limit of your subscription. Only {can_upload_size} documents can be uploaded.') @staticmethod - def build_document( - dataset: Dataset, process_rule_id: str, data_source_type: str, document_form: str, - document_language: str, data_source_info: dict, created_from: str, position: int, - account: Account, - name: str, batch: str - ): + def build_document(dataset: Dataset, process_rule_id: str, data_source_type: str, document_form: str, + document_language: str, data_source_info: dict, created_from: str, position: int, + account: Account, + name: str, batch: str): document = Document( tenant_id=dataset.tenant_id, dataset_id=dataset.id, @@ -899,20 +810,16 @@ def build_document( @staticmethod def get_tenant_documents_count(): - documents_count = Document.query.filter( - Document.completed_at.isnot(None), - Document.enabled == True, - Document.archived == False, - Document.tenant_id == current_user.current_tenant_id - ).count() + documents_count = Document.query.filter(Document.completed_at.isnot(None), + Document.enabled == True, + Document.archived == False, + Document.tenant_id == current_user.current_tenant_id).count() return documents_count @staticmethod - def update_document_with_dataset_id( - dataset: Dataset, document_data: dict, - account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, - created_from: str = 'web' - ): + def update_document_with_dataset_id(dataset: Dataset, document_data: dict, + account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, + created_from: str = 'web'): DatasetService.check_dataset_model_setting(dataset) document = DocumentService.get_document(dataset.id, document_data["original_document_id"]) if document.display_status != 'available': @@ -1100,7 +1007,7 @@ def document_create_args_validate(cls, args: dict): DocumentService.process_rule_args_validate(args) else: if ('data_source' not in args and not args['data_source']) \ - and ('process_rule' not in args and not args['process_rule']): + and ('process_rule' not in args and not args['process_rule']): raise ValueError("Data source or Process rule is required") else: if args.get('data_source'): @@ -1162,7 +1069,7 @@ def process_rule_args_validate(cls, args: dict): raise ValueError("Process rule rules is invalid") if 'pre_processing_rules' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['pre_processing_rules'] is None: + or args['process_rule']['rules']['pre_processing_rules'] is None: raise ValueError("Process rule pre_processing_rules is required") if not isinstance(args['process_rule']['rules']['pre_processing_rules'], list): @@ -1187,21 +1094,21 @@ def process_rule_args_validate(cls, args: dict): args['process_rule']['rules']['pre_processing_rules'] = list(unique_pre_processing_rule_dicts.values()) if 'segmentation' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['segmentation'] is None: + or args['process_rule']['rules']['segmentation'] is None: raise ValueError("Process rule segmentation is required") if not isinstance(args['process_rule']['rules']['segmentation'], dict): raise ValueError("Process rule segmentation is invalid") if 'separator' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['separator']: + or not args['process_rule']['rules']['segmentation']['separator']: raise ValueError("Process rule segmentation separator is required") if not isinstance(args['process_rule']['rules']['segmentation']['separator'], str): raise ValueError("Process rule segmentation separator is invalid") if 'max_tokens' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['max_tokens']: + or not args['process_rule']['rules']['segmentation']['max_tokens']: raise ValueError("Process rule segmentation max_tokens is required") if not isinstance(args['process_rule']['rules']['segmentation']['max_tokens'], int): @@ -1237,7 +1144,7 @@ def estimate_args_validate(cls, args: dict): raise ValueError("Process rule rules is invalid") if 'pre_processing_rules' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['pre_processing_rules'] is None: + or args['process_rule']['rules']['pre_processing_rules'] is None: raise ValueError("Process rule pre_processing_rules is required") if not isinstance(args['process_rule']['rules']['pre_processing_rules'], list): @@ -1262,21 +1169,21 @@ def estimate_args_validate(cls, args: dict): args['process_rule']['rules']['pre_processing_rules'] = list(unique_pre_processing_rule_dicts.values()) if 'segmentation' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['segmentation'] is None: + or args['process_rule']['rules']['segmentation'] is None: raise ValueError("Process rule segmentation is required") if not isinstance(args['process_rule']['rules']['segmentation'], dict): raise ValueError("Process rule segmentation is invalid") if 'separator' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['separator']: + or not args['process_rule']['rules']['segmentation']['separator']: raise ValueError("Process rule segmentation separator is required") if not isinstance(args['process_rule']['rules']['segmentation']['separator'], str): raise ValueError("Process rule segmentation separator is invalid") if 'max_tokens' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['max_tokens']: + or not args['process_rule']['rules']['segmentation']['max_tokens']: raise ValueError("Process rule segmentation max_tokens is required") if not isinstance(args['process_rule']['rules']['segmentation']['max_tokens'], int): @@ -1530,16 +1437,12 @@ def delete_segment(cls, segment: DocumentSegment, document: Document, dataset: D class DatasetCollectionBindingService: @classmethod - def get_dataset_collection_binding( - cls, provider_name: str, model_name: str, - collection_type: str = 'dataset' - ) -> DatasetCollectionBinding: + def get_dataset_collection_binding(cls, provider_name: str, model_name: str, + collection_type: str = 'dataset') -> DatasetCollectionBinding: dataset_collection_binding = db.session.query(DatasetCollectionBinding). \ - filter( - DatasetCollectionBinding.provider_name == provider_name, - DatasetCollectionBinding.model_name == model_name, - DatasetCollectionBinding.type == collection_type - ). \ + filter(DatasetCollectionBinding.provider_name == provider_name, + DatasetCollectionBinding.model_name == model_name, + DatasetCollectionBinding.type == collection_type). \ order_by(DatasetCollectionBinding.created_at). \ first() @@ -1555,76 +1458,12 @@ def get_dataset_collection_binding( return dataset_collection_binding @classmethod - def get_dataset_collection_binding_by_id_and_type( - cls, collection_binding_id: str, - collection_type: str = 'dataset' - ) -> DatasetCollectionBinding: + def get_dataset_collection_binding_by_id_and_type(cls, collection_binding_id: str, + collection_type: str = 'dataset') -> DatasetCollectionBinding: dataset_collection_binding = db.session.query(DatasetCollectionBinding). \ - filter( - DatasetCollectionBinding.id == collection_binding_id, - DatasetCollectionBinding.type == collection_type - ). \ + filter(DatasetCollectionBinding.id == collection_binding_id, + DatasetCollectionBinding.type == collection_type). \ order_by(DatasetCollectionBinding.created_at). \ first() return dataset_collection_binding - - -class DatasetPermissionService: - @classmethod - def get_dataset_partial_member_list(cls, dataset_id): - user_list_query = db.session.query( - DatasetPermission.account_id, - ).filter( - DatasetPermission.dataset_id == dataset_id - ).all() - - user_list = [] - for user in user_list_query: - user_list.append(user.account_id) - - return user_list - - @classmethod - def update_partial_member_list(cls, dataset_id, user_list): - try: - db.session.query(DatasetPermission).filter(DatasetPermission.dataset_id == dataset_id).delete() - permissions = [] - for user in user_list: - permission = DatasetPermission( - dataset_id=dataset_id, - account_id=user['user_id'], - ) - permissions.append(permission) - - db.session.add_all(permissions) - db.session.commit() - except Exception as e: - db.session.rollback() - raise e - - @classmethod - def check_permission(cls, user, dataset, requested_permission, requested_partial_member_list): - if not user.is_dataset_editor: - raise NoPermissionError('User does not have permission to edit this dataset.') - - if user.is_dataset_operator and dataset.permission != requested_permission: - raise NoPermissionError('Dataset operators cannot change the dataset permissions.') - - if user.is_dataset_operator and requested_permission == 'partial_members': - if not requested_partial_member_list: - raise ValueError('Partial member list is required when setting to partial members.') - - local_member_list = cls.get_dataset_partial_member_list(dataset.id) - request_member_list = [user['user_id'] for user in requested_partial_member_list] - if set(local_member_list) != set(request_member_list): - raise ValueError('Dataset operators cannot change the dataset permissions.') - - @classmethod - def clear_partial_member_list(cls, dataset_id): - try: - db.session.query(DatasetPermission).filter(DatasetPermission.dataset_id == dataset_id).delete() - db.session.commit() - except Exception as e: - db.session.rollback() - raise e diff --git a/api/services/feature_service.py b/api/services/feature_service.py index 73755541561b86..07d1448bf22911 100644 --- a/api/services/feature_service.py +++ b/api/services/feature_service.py @@ -30,7 +30,6 @@ class FeatureModel(BaseModel): docs_processing: str = 'standard' can_replace_logo: bool = False model_load_balancing_enabled: bool = False - dataset_operator_enabled: bool = False # pydantic configs model_config = ConfigDict(protected_namespaces=()) @@ -69,7 +68,6 @@ def get_system_features(cls) -> SystemFeatureModel: def _fulfill_params_from_env(cls, features: FeatureModel): features.can_replace_logo = current_app.config['CAN_REPLACE_LOGO'] features.model_load_balancing_enabled = current_app.config['MODEL_LB_ENABLED'] - features.dataset_operator_enabled = current_app.config['DATASET_OPERATOR_ENABLED'] @classmethod def _fulfill_params_from_billing_api(cls, features: FeatureModel, tenant_id: str): diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx index 211b0b3677125c..7164a00be09bee 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx @@ -1,22 +1,11 @@ -'use client' import type { FC } from 'react' -import React, { useEffect } from 'react' -import { useRouter } from 'next/navigation' -import { useAppContext } from '@/context/app-context' +import React from 'react' export type IAppDetail = { children: React.ReactNode } const AppDetail: FC = ({ children }) => { - const router = useRouter() - const { isCurrentWorkspaceDatasetOperator } = useAppContext() - - useEffect(() => { - if (isCurrentWorkspaceDatasetOperator) - return router.replace('/datasets') - }, [isCurrentWorkspaceDatasetOperator]) - return ( <> {children} diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index c16512bd50db1f..a82ddd74b58957 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -1,7 +1,6 @@ 'use client' import { useCallback, useEffect, useRef, useState } from 'react' -import { useRouter } from 'next/navigation' import useSWRInfinite from 'swr/infinite' import { useTranslation } from 'react-i18next' import { useDebounceFn } from 'ahooks' @@ -51,8 +50,7 @@ const getKey = ( const Apps = () => { const { t } = useTranslation() - const router = useRouter() - const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() + const { isCurrentWorkspaceEditor } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'all', @@ -89,11 +87,6 @@ const Apps = () => { } }, []) - useEffect(() => { - if (isCurrentWorkspaceDatasetOperator) - return router.replace('/datasets') - }, [isCurrentWorkspaceDatasetOperator]) - const hasMore = data?.at(-1)?.has_more ?? true useEffect(() => { let observer: IntersectionObserver | undefined diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx index cb8f44c988f312..efba20e6521b9b 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx @@ -38,7 +38,6 @@ import { useStore } from '@/app/components/app/store' import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication' import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' import { getLocaleOnClient } from '@/i18n' -import { useAppContext } from '@/context/app-context' export type IAppDetailLayoutProps = { children: React.ReactNode @@ -188,7 +187,6 @@ const DatasetDetailLayout: FC = (props) => { const pathname = usePathname() const hideSideBar = /documents\/create$/.test(pathname) const { t } = useTranslation() - const { isCurrentWorkspaceDatasetOperator } = useAppContext() const media = useBreakpoints() const isMobile = media === MediaType.mobile @@ -234,7 +232,7 @@ const DatasetDetailLayout: FC = (props) => { icon_background={datasetRes?.icon_background || '#F5F5F5'} desc={datasetRes?.description || '--'} navigation={navigation} - extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => : undefined} + extraInfo={mode => } iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} />} { const { t } = useTranslation() - const router = useRouter() - const { currentWorkspace } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const options = [ @@ -61,11 +57,6 @@ const Container = () => { handleTagsUpdate() } - useEffect(() => { - if (currentWorkspace.role === 'normal') - return router.replace('/apps') - }, [currentWorkspace]) - return (
diff --git a/web/app/(commonLayout)/datasets/DatasetCard.tsx b/web/app/(commonLayout)/datasets/DatasetCard.tsx index 0042e2759fae4a..df122bc298dcdf 100644 --- a/web/app/(commonLayout)/datasets/DatasetCard.tsx +++ b/web/app/(commonLayout)/datasets/DatasetCard.tsx @@ -20,7 +20,6 @@ import Divider from '@/app/components/base/divider' import RenameDatasetModal from '@/app/components/datasets/rename-modal' import type { Tag } from '@/app/components/base/tag-management/constant' import TagSelector from '@/app/components/base/tag-management/selector' -import { useAppContext } from '@/context/app-context' export type DatasetCardProps = { dataset: DataSet @@ -33,7 +32,6 @@ const DatasetCard = ({ }: DatasetCardProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) - const { isCurrentWorkspaceDatasetOperator } = useAppContext() const [tags, setTags] = useState(dataset.tags) const [showRenameModal, setShowRenameModal] = useState(false) @@ -63,7 +61,7 @@ const DatasetCard = ({ setShowConfirmDelete(false) }, [dataset.id, notify, onSuccess, t]) - const Operations = (props: HtmlContentProps & { showDelete: boolean }) => { + const Operations = (props: HtmlContentProps) => { const onMouseLeave = async () => { props.onClose?.() } @@ -84,19 +82,15 @@ const DatasetCard = ({
{t('common.operation.settings')}
- {props.showDelete && ( - <> - -
- - {t('common.operation.delete')} - -
- - )} + +
+ + {t('common.operation.delete')} + +
) } @@ -180,7 +174,7 @@ const DatasetCard = ({
} + htmlContent={} position="br" trigger="click" btnElement={ diff --git a/web/app/(commonLayout)/tools/page.tsx b/web/app/(commonLayout)/tools/page.tsx index 4e64d8c0dfe8d3..066550b3a2a03b 100644 --- a/web/app/(commonLayout)/tools/page.tsx +++ b/web/app/(commonLayout)/tools/page.tsx @@ -1,27 +1,16 @@ 'use client' import type { FC } from 'react' -import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import React, { useEffect } from 'react' import ToolProviderList from '@/app/components/tools/provider-list' -import { useAppContext } from '@/context/app-context' const Layout: FC = () => { const { t } = useTranslation() - const router = useRouter() - const { isCurrentWorkspaceDatasetOperator } = useAppContext() useEffect(() => { document.title = `${t('tools.title')} - Dify` - if (isCurrentWorkspaceDatasetOperator) - return router.replace('/datasets') }, []) - useEffect(() => { - if (isCurrentWorkspaceDatasetOperator) - return router.replace('/datasets') - }, [isCurrentWorkspaceDatasetOperator]) - return } export default React.memo(Layout) diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index 180e2defc01650..d87138506ae296 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react' import { useRef, useState } from 'react' -import { useMount } from 'ahooks' import { useTranslation } from 'react-i18next' import { isEqual } from 'lodash-es' import cn from 'classnames' @@ -11,22 +10,19 @@ import Button from '@/app/components/base/button' import type { DataSet } from '@/models/datasets' import { useToastContext } from '@/app/components/base/toast' import { updateDatasetSetting } from '@/service/datasets' -import { useAppContext } from '@/context/app-context' import { useModalContext } from '@/context/modal-context' import type { RetrievalConfig } from '@/types/app' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' -import PermissionSelector from '@/app/components/datasets/settings/permission-selector' +import PermissionsRadio from '@/app/components/datasets/settings/permissions-radio' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' import { useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel, } from '@/app/components/header/account-setting/model-provider-page/hooks' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { fetchMembers } from '@/service/common' -import type { Member } from '@/models/common' type SettingsModalProps = { currentDataset: DataSet @@ -59,11 +55,7 @@ const SettingsModal: FC = ({ const { setShowAccountSettingModal } = useModalContext() const [loading, setLoading] = useState(false) - const { isCurrentWorkspaceDatasetOperator } = useAppContext() const [localeCurrentDataset, setLocaleCurrentDataset] = useState({ ...currentDataset }) - const [selectedMemberIDs, setSelectedMemberIDs] = useState(currentDataset.partial_member_list || []) - const [memberList, setMemberList] = useState([]) - const [indexMethod, setIndexMethod] = useState(currentDataset.indexing_technique) const [retrievalConfig, setRetrievalConfig] = useState(localeCurrentDataset?.retrieval_model_dict as RetrievalConfig) @@ -100,7 +92,7 @@ const SettingsModal: FC = ({ try { setLoading(true) const { id, name, description, permission } = localeCurrentDataset - const requestParams = { + await updateDatasetSetting({ datasetId: id, body: { name, @@ -114,16 +106,7 @@ const SettingsModal: FC = ({ embedding_model: localeCurrentDataset.embedding_model, embedding_model_provider: localeCurrentDataset.embedding_model_provider, }, - } as any - if (permission === 'partial_members') { - requestParams.body.partial_member_list = selectedMemberIDs.map((id) => { - return { - user_id: id, - role: memberList.find(member => member.id === id)?.role, - } - }) - } - await updateDatasetSetting(requestParams) + }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) onSave({ ...localeCurrentDataset, @@ -139,18 +122,6 @@ const SettingsModal: FC = ({ } } - const getMembers = async () => { - const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} }) - if (!accounts) - setMemberList([]) - else - setMemberList(accounts) - } - - useMount(() => { - getMembers() - }) - return (
= ({
{t('datasetSettings.form.permissions')}
- handleValueChange('permission', v!)} - onMemberSelect={setSelectedMemberIDs} - memberList={memberList} + itemClassName='sm:!w-[280px]' />
diff --git a/web/app/components/base/icons/assets/vender/solid/users/users-plus.svg b/web/app/components/base/icons/assets/vender/solid/users/users-plus.svg deleted file mode 100644 index 36c82d10d55ce4..00000000000000 --- a/web/app/components/base/icons/assets/vender/solid/users/users-plus.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/web/app/components/base/icons/src/vender/solid/users/UsersPlus.json b/web/app/components/base/icons/src/vender/solid/users/UsersPlus.json deleted file mode 100644 index a70117f655f61a..00000000000000 --- a/web/app/components/base/icons/src/vender/solid/users/UsersPlus.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "width": "24", - "height": "24", - "viewBox": "0 0 24 24", - "fill": "none", - "xmlns": "http://www.w3.org/2000/svg" - }, - "children": [ - { - "type": "element", - "name": "g", - "attributes": { - "id": "users-plus" - }, - "children": [ - { - "type": "element", - "name": "g", - "attributes": { - "id": "Solid" - }, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M20 15C20 14.4477 19.5523 14 19 14C18.4477 14 18 14.4477 18 15V17H16C15.4477 17 15 17.4477 15 18C15 18.5523 15.4477 19 16 19H18V21C18 21.5523 18.4477 22 19 22C19.5523 22 20 21.5523 20 21V19H22C22.5523 19 23 18.5523 23 18C23 17.4477 22.5523 17 22 17H20V15Z", - "fill": "currentColor" - }, - "children": [] - }, - { - "type": "element", - "name": "path", - "attributes": { - "fill-rule": "evenodd", - "clip-rule": "evenodd", - "d": "M12.181 14.1635C12.4632 14.3073 12.6927 14.5368 12.8365 14.819C12.9896 15.1194 13.0001 15.4476 13 15.7769C13 15.7847 13 15.7924 13 15.8C13 17.2744 12.9995 18.7488 13 20.2231C13.0001 20.3422 13.0001 20.4845 12.9899 20.6098C12.978 20.755 12.9476 20.963 12.8365 21.181C12.6927 21.4632 12.4632 21.6927 12.181 21.8365C11.963 21.9476 11.7551 21.978 11.6098 21.9899C11.4845 22.0001 11.3423 22.0001 11.2231 22C8.4077 21.999 5.59226 21.999 2.77682 22C2.65755 22.0001 2.51498 22.0001 2.38936 21.9898C2.24364 21.9778 2.03523 21.9472 1.81695 21.8356C1.53435 21.6911 1.30428 21.46 1.16109 21.1767C1.05079 20.9585 1.02087 20.7506 1.0095 20.6046C0.999737 20.4791 1.00044 20.3369 1.00103 20.2185C1.00619 19.1792 0.975203 18.0653 1.38061 17.0866C1.88808 15.8614 2.86145 14.8881 4.08659 14.3806C4.59629 14.1695 5.13457 14.0819 5.74331 14.0404C6.33532 14 7.06273 14 7.96449 14C9.05071 14 10.1369 14.0004 11.2231 14C11.5524 13.9999 11.8806 14.0104 12.181 14.1635Z", - "fill": "currentColor" - }, - "children": [] - }, - { - "type": "element", - "name": "path", - "attributes": { - "fill-rule": "evenodd", - "clip-rule": "evenodd", - "d": "M14.5731 2.91554C14.7803 2.40361 15.3633 2.1566 15.8752 2.36382C17.7058 3.10481 19 4.90006 19 7C19 9.09994 17.7058 10.8952 15.8752 11.6362C15.3633 11.8434 14.7803 11.5964 14.5731 11.0845C14.3658 10.5725 14.6129 9.98953 15.1248 9.7823C16.2261 9.33652 17 8.25744 17 7C17 5.74256 16.2261 4.66348 15.1248 4.2177C14.6129 4.01047 14.3658 3.42748 14.5731 2.91554Z", - "fill": "currentColor" - }, - "children": [] - }, - { - "type": "element", - "name": "path", - "attributes": { - "fill-rule": "evenodd", - "clip-rule": "evenodd", - "d": "M4.50001 7C4.50001 4.23858 6.73858 2 9.50001 2C12.2614 2 14.5 4.23858 14.5 7C14.5 9.76142 12.2614 12 9.50001 12C6.73858 12 4.50001 9.76142 4.50001 7Z", - "fill": "currentColor" - }, - "children": [] - } - ] - } - ] - } - ] - }, - "name": "UsersPlus" -} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/users/UsersPlus.tsx b/web/app/components/base/icons/src/vender/solid/users/UsersPlus.tsx deleted file mode 100644 index a2294960f73f77..00000000000000 --- a/web/app/components/base/icons/src/vender/solid/users/UsersPlus.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// GENERATE BY script -// DON NOT EDIT IT MANUALLY - -import * as React from 'react' -import data from './UsersPlus.json' -import IconBase from '@/app/components/base/icons/IconBase' -import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' - -const Icon = React.forwardRef, Omit>(( - props, - ref, -) => ) - -Icon.displayName = 'UsersPlus' - -export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/users/index.ts b/web/app/components/base/icons/src/vender/solid/users/index.ts index 4c969bffd784fe..7047a62edc2950 100644 --- a/web/app/components/base/icons/src/vender/solid/users/index.ts +++ b/web/app/components/base/icons/src/vender/solid/users/index.ts @@ -1,4 +1,3 @@ export { default as User01 } from './User01' export { default as UserEdit02 } from './UserEdit02' export { default as Users01 } from './Users01' -export { default as UsersPlus } from './UsersPlus' diff --git a/web/app/components/base/search-input/index.tsx b/web/app/components/base/search-input/index.tsx index 5e6f72eb8fabbd..7e37306f1375c6 100644 --- a/web/app/components/base/search-input/index.tsx +++ b/web/app/components/base/search-input/index.tsx @@ -37,7 +37,7 @@ const SearchInput: FC = ({ type="text" name="query" className={cn( - 'grow block h-[18px] bg-gray-200 border-0 text-gray-700 text-[13px] placeholder:text-gray-500 appearance-none outline-none group-hover:bg-gray-300 caret-blue-600', + 'grow block h-[18px] bg-gray-200 rounded-md border-0 text-gray-700 text-[13px] placeholder:text-gray-500 appearance-none outline-none group-hover:bg-gray-300 caret-blue-600', focus && '!bg-white hover:bg-white group-hover:bg-white placeholder:!text-gray-400', !focus && value && 'hover:!bg-gray-200 group-hover:!bg-gray-200', white && '!bg-white hover:!bg-white group-hover:!bg-white placeholder:!text-gray-400', diff --git a/web/app/components/billing/type.ts b/web/app/components/billing/type.ts index d78eab2ae36239..c6eae4858ee8ce 100644 --- a/web/app/components/billing/type.ts +++ b/web/app/components/billing/type.ts @@ -66,7 +66,6 @@ export type CurrentPlanInfoBackend = { docs_processing: DocumentProcessingPriority can_replace_logo: boolean model_load_balancing_enabled: boolean - dataset_operator_enabled: boolean } export type SubscriptionItem = { diff --git a/web/app/components/datasets/settings/form/index.tsx b/web/app/components/datasets/settings/form/index.tsx index 613ba3e2e44c0d..77910c1a6169fb 100644 --- a/web/app/components/datasets/settings/form/index.tsx +++ b/web/app/components/datasets/settings/form/index.tsx @@ -1,33 +1,31 @@ 'use client' -import { useState } from 'react' -import { useMount } from 'ahooks' +import { useEffect, useState } from 'react' +import type { Dispatch } from 'react' import { useContext } from 'use-context-selector' import { BookOpenIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' import cn from 'classnames' import { useSWRConfig } from 'swr' import { unstable_serialize } from 'swr/infinite' -import PermissionSelector from '../permission-selector' +import PermissionsRadio from '../permissions-radio' import IndexMethodRadio from '../index-method-radio' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' import { updateDatasetSetting } from '@/service/datasets' -import type { DataSetListResponse } from '@/models/datasets' +import type { DataSet, DataSetListResponse } from '@/models/datasets' import DatasetDetailContext from '@/context/dataset-detail' import { type RetrievalConfig } from '@/types/app' -import { useAppContext } from '@/context/app-context' +import { useModalContext } from '@/context/modal-context' import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' import { useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel, } from '@/app/components/header/account-setting/model-provider-page/hooks' -import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { fetchMembers } from '@/service/common' -import type { Member } from '@/models/common' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' const rowClass = ` flex justify-between py-4 flex-wrap gap-y-2 @@ -38,6 +36,11 @@ const labelClass = ` const inputClass = ` w-full max-w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none ` +const useInitialValue: (depend: T, dispatch: Dispatch) => void = (depend, dispatch) => { + useEffect(() => { + dispatch(depend) + }, [depend]) +} const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => { if (!pageIndex || previousPageData.has_more) @@ -49,14 +52,12 @@ const Form = () => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const { mutate } = useSWRConfig() - const { isCurrentWorkspaceDatasetOperator } = useAppContext() const { dataset: currentDataset, mutateDatasetRes: mutateDatasets } = useContext(DatasetDetailContext) + const { setShowAccountSettingModal } = useModalContext() const [loading, setLoading] = useState(false) const [name, setName] = useState(currentDataset?.name ?? '') const [description, setDescription] = useState(currentDataset?.description ?? '') const [permission, setPermission] = useState(currentDataset?.permission) - const [selectedMemberIDs, setSelectedMemberIDs] = useState(currentDataset?.partial_member_list || []) - const [memberList, setMemberList] = useState([]) const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique) const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig) const [embeddingModel, setEmbeddingModel] = useState( @@ -77,18 +78,6 @@ const Form = () => { } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) - const getMembers = async () => { - const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} }) - if (!accounts) - setMemberList([]) - else - setMemberList(accounts) - } - - useMount(() => { - getMembers() - }) - const handleSave = async () => { if (loading) return @@ -115,7 +104,7 @@ const Form = () => { }) try { setLoading(true) - const requestParams = { + await updateDatasetSetting({ datasetId: currentDataset!.id, body: { name, @@ -129,16 +118,7 @@ const Form = () => { embedding_model: embeddingModel.model, embedding_model_provider: embeddingModel.provider, }, - } as any - if (permission === 'partial_members') { - requestParams.body.partial_member_list = selectedMemberIDs.map((id) => { - return { - user_id: id, - role: memberList.find(member => member.id === id)?.role, - } - }) - } - await updateDatasetSetting(requestParams) + }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) if (mutateDatasets) { await mutateDatasets() @@ -153,6 +133,11 @@ const Form = () => { } } + useInitialValue(currentDataset?.name ?? '', setName) + useInitialValue(currentDataset?.description ?? '', setDescription) + useInitialValue(currentDataset?.permission, setPermission) + useInitialValue(currentDataset?.indexing_technique, setIndexMethod) + return (
@@ -189,13 +174,10 @@ const Form = () => {
{t('datasetSettings.form.permissions')}
- setPermission(v)} - onMemberSelect={setSelectedMemberIDs} - memberList={memberList} />
diff --git a/web/app/components/datasets/settings/permission-selector/index.tsx b/web/app/components/datasets/settings/permission-selector/index.tsx deleted file mode 100644 index 2405f9512b7f5e..00000000000000 --- a/web/app/components/datasets/settings/permission-selector/index.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { useTranslation } from 'react-i18next' -import cn from 'classnames' -import React, { useMemo, useState } from 'react' -import { useDebounceFn } from 'ahooks' -import { RiArrowDownSLine } from '@remixicon/react' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Avatar from '@/app/components/base/avatar' -import SearchInput from '@/app/components/base/search-input' -import { Check } from '@/app/components/base/icons/src/vender/line/general' -import { Users01, UsersPlus } from '@/app/components/base/icons/src/vender/solid/users' -import type { DatasetPermission } from '@/models/datasets' -import { useAppContext } from '@/context/app-context' -import type { Member } from '@/models/common' -export type RoleSelectorProps = { - disabled?: boolean - permission?: DatasetPermission - value: string[] - memberList: Member[] - onChange: (permission?: DatasetPermission) => void - onMemberSelect: (v: string[]) => void -} - -const PermissionSelector = ({ disabled, permission, value, memberList, onChange, onMemberSelect }: RoleSelectorProps) => { - const { t } = useTranslation() - const { userProfile } = useAppContext() - const [open, setOpen] = useState(false) - - const [keywords, setKeywords] = useState('') - const [searchKeywords, setSearchKeywords] = useState('') - const { run: handleSearch } = useDebounceFn(() => { - setSearchKeywords(keywords) - }, { wait: 500 }) - const handleKeywordsChange = (value: string) => { - setKeywords(value) - handleSearch() - } - const selectMember = (member: Member) => { - if (value.includes(member.id)) - onMemberSelect(value.filter(v => v !== member.id)) - else - onMemberSelect([...value, member.id]) - } - - const selectedMembers = useMemo(() => { - return [ - userProfile, - ...memberList.filter(member => member.id !== userProfile.id).filter(member => value.includes(member.id)), - ].map(member => member.name).join(', ') - }, [userProfile, value, memberList]) - const showMe = useMemo(() => { - return userProfile.name.includes(searchKeywords) || userProfile.email.includes(searchKeywords) - }, [searchKeywords, userProfile]) - const filteredMemberList = useMemo(() => { - return memberList.filter(member => (member.name.includes(searchKeywords) || member.email.includes(searchKeywords)) && member.id !== userProfile.id && ['owner', 'admin', 'editor', 'dataset_operator'].includes(member.role)) - }, [memberList, searchKeywords, userProfile]) - - return ( - -
- !disabled && setOpen(v => !v)} - className='block' - > - {permission === 'only_me' && ( -
- -
{t('datasetSettings.form.permissionsOnlyMe')}
- {!disabled && } -
- )} - {permission === 'all_team_members' && ( -
-
- -
-
{t('datasetSettings.form.permissionsAllMember')}
- {!disabled && } -
- )} - {permission === 'partial_members' && ( -
-
- -
-
{selectedMembers}
- {!disabled && } -
- )} -
- -
-
-
{ - onChange('only_me') - setOpen(false) - }}> -
- -
{t('datasetSettings.form.permissionsOnlyMe')}
- {permission === 'only_me' && } -
-
-
{ - onChange('all_team_members') - setOpen(false) - }}> -
-
- -
-
{t('datasetSettings.form.permissionsAllMember')}
- {permission === 'all_team_members' && } -
-
-
{ - onChange('partial_members') - onMemberSelect([userProfile.id]) - }}> -
-
- -
-
{t('datasetSettings.form.permissionsInvitedMembers')}
- {permission === 'partial_members' && } -
-
-
- {permission === 'partial_members' && ( -
-
- -
- {showMe && ( -
- -
-
- {userProfile.name} - {t('datasetSettings.form.me')} -
-
{userProfile.email}
-
- -
- )} - {filteredMemberList.map(member => ( -
selectMember(member)}> - -
-
{member.name}
-
{member.email}
-
- {value.includes(member.id) && } -
- ))} -
- )} -
-
-
-
- ) -} - -export default PermissionSelector diff --git a/web/app/components/datasets/settings/permissions-radio/assets/user.svg b/web/app/components/datasets/settings/permissions-radio/assets/user.svg new file mode 100644 index 00000000000000..f5974c94a8f280 --- /dev/null +++ b/web/app/components/datasets/settings/permissions-radio/assets/user.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/web/app/components/datasets/settings/permissions-radio/index.module.css b/web/app/components/datasets/settings/permissions-radio/index.module.css new file mode 100644 index 00000000000000..372c1bedbb26b0 --- /dev/null +++ b/web/app/components/datasets/settings/permissions-radio/index.module.css @@ -0,0 +1,46 @@ +.user-icon { + width: 24px; + height: 24px; + background: url(./assets/user.svg) center center; + background-size: contain; +} + +.wrapper .item:hover { + background-color: #ffffff; + border-color: #B2CCFF; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); +} + +.wrapper .item-active { + background-color: #ffffff; + border-width: 1.5px; + border-color: #528BFF; + box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); +} + +.wrapper .item-active .radio { + border-width: 5px; + border-color: #155EEF; +} + +.wrapper .item-active:hover { + border-width: 1.5px; + border-color: #528BFF; + box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); +} + +.wrapper .item.disable { + @apply opacity-60; +} +.wrapper .item-active.disable { + @apply opacity-60; +} +.wrapper .item.disable:hover { + @apply bg-gray-25 border border-gray-100 shadow-none cursor-default opacity-60; +} +.wrapper .item-active.disable:hover { + @apply cursor-default opacity-60; + border-width: 1.5px; + border-color: #528BFF; + box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); +} \ No newline at end of file diff --git a/web/app/components/datasets/settings/permissions-radio/index.tsx b/web/app/components/datasets/settings/permissions-radio/index.tsx new file mode 100644 index 00000000000000..4c851ad2f6dbde --- /dev/null +++ b/web/app/components/datasets/settings/permissions-radio/index.tsx @@ -0,0 +1,66 @@ +'use client' +import { useTranslation } from 'react-i18next' +import classNames from 'classnames' +import s from './index.module.css' +import type { DataSet } from '@/models/datasets' + +const itemClass = ` + flex items-center w-full sm:w-[234px] h-12 px-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer +` +const radioClass = ` + w-4 h-4 border-[2px] border-gray-200 rounded-full +` +type IPermissionsRadioProps = { + value?: DataSet['permission'] + onChange: (v?: DataSet['permission']) => void + itemClassName?: string + disable?: boolean +} + +const PermissionsRadio = ({ + value, + onChange, + itemClassName, + disable, +}: IPermissionsRadioProps) => { + const { t } = useTranslation() + const options = [ + { + key: 'only_me', + text: t('datasetSettings.form.permissionsOnlyMe'), + }, + { + key: 'all_team_members', + text: t('datasetSettings.form.permissionsAllMember'), + }, + ] + + return ( +
+ { + options.map(option => ( +
{ + if (!disable) + onChange(option.key as DataSet['permission']) + }} + > +
+
{option.text}
+
+
+ )) + } +
+ ) +} + +export default PermissionsRadio diff --git a/web/app/components/explore/index.tsx b/web/app/components/explore/index.tsx index cef6573bff99d6..2be9868810d539 100644 --- a/web/app/components/explore/index.tsx +++ b/web/app/components/explore/index.tsx @@ -1,7 +1,6 @@ 'use client' import type { FC } from 'react' import React, { useEffect, useState } from 'react' -import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import ExploreContext from '@/context/explore-context' import Sidebar from '@/app/components/explore/sidebar' @@ -17,9 +16,8 @@ const Explore: FC = ({ children, }) => { const { t } = useTranslation() - const router = useRouter() const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0) - const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext() + const { userProfile } = useAppContext() const [hasEditPermission, setHasEditPermission] = useState(false) const [installedApps, setInstalledApps] = useState([]) @@ -34,11 +32,6 @@ const Explore: FC = ({ })() }, []) - useEffect(() => { - if (isCurrentWorkspaceDatasetOperator) - return router.replace('/datasets') - }, [isCurrentWorkspaceDatasetOperator]) - return (
{ - if (isCurrentWorkspaceDatasetOperator) - return [] return [ { key: 'provider', @@ -176,9 +172,7 @@ export default function AccountSetting({ { menuItems.map(menuItem => (
- {!isCurrentWorkspaceDatasetOperator && ( -
{menuItem.name}
- )} +
{menuItem.name}
{ menuItem.items.map(item => ( diff --git a/web/app/components/header/account-setting/members-page/index.tsx b/web/app/components/header/account-setting/members-page/index.tsx index 711e7726842409..51a453e4a7f7d7 100644 --- a/web/app/components/header/account-setting/members-page/index.tsx +++ b/web/app/components/header/account-setting/members-page/index.tsx @@ -29,7 +29,6 @@ const MembersPage = () => { owner: t('common.members.owner'), admin: t('common.members.admin'), editor: t('common.members.editor'), - dataset_operator: t('common.members.datasetOperator'), normal: t('common.members.normal'), } const { locale } = useContext(I18n) diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx index 7d721c036ef6d5..2418a4775f7595 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx @@ -1,12 +1,13 @@ 'use client' -import { useCallback, useState } from 'react' +import { Fragment, useCallback, useMemo, useState } from 'react' import { useContext } from 'use-context-selector' import { XMarkIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' import { ReactMultiEmail } from 'react-multi-email' +import { Listbox, Transition } from '@headlessui/react' +import { CheckIcon } from '@heroicons/react/20/solid' import cn from 'classnames' import s from './index.module.css' -import RoleSelector from './role-selector' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import { inviteMember } from '@/service/common' @@ -30,14 +31,29 @@ const InviteModal = ({ const { notify } = useContext(ToastContext) const { locale } = useContext(I18n) - const [role, setRole] = useState('normal') + + const InvitingRoles = useMemo(() => [ + { + name: 'normal', + description: t('common.members.normalTip'), + }, + { + name: 'editor', + description: t('common.members.editorTip'), + }, + { + name: 'admin', + description: t('common.members.adminTip'), + }, + ], [t]) + const [role, setRole] = useState(InvitingRoles[0]) const handleSend = useCallback(async () => { if (emails.map((email: string) => emailRegex.test(email)).every(Boolean)) { try { const { result, invitation_results } = await inviteMember({ url: '/workspaces/current/members/invite-email', - body: { emails, role, language: locale }, + body: { emails, role: role.name, language: locale }, }) if (result === 'success') { @@ -83,9 +99,53 @@ const InviteModal = ({ placeholder={t('common.members.emailPlaceholder') || ''} />
-
- -
+ +
+ + {t('common.members.invitedAsRole', { role: t(`common.members.${role.name}`) })} + + + + {InvitingRoles.map(role => + + `${active ? ' bg-gray-50 rounded-xl' : ' bg-transparent'} + cursor-default select-none relative py-2 px-4 mx-2 flex flex-col` + } + value={role} + > + {({ selected }) => ( +
+ + {selected && ( +
+ + {t(`common.members.${role.name}`)} + + + {role.description} + +
+
+ )} +
, + )} +
+
+
+