Skip to content

Commit

Permalink
[FR-240] fix: show vfolder types in folder create modal based on user…
Browse files Browse the repository at this point in the history
… permission (#2992)

<!--
Please precisely, concisely, and concretely describe what this PR changes, the rationale behind codes,
and how it affects the users and other developers.
-->

### This PR resolves [#FR-240](https://lablup.atlassian.net/browse/FR-240?atlOrigin=eyJpIjoiYTA1Mjk0NjJiNjFiNGI0MjhjYzJiNjEwZWI2MDlmZDYiLCJwIjoiaiJ9) issue

Apply rules based on the `current user role` and `list_allowed_types` to the vfolder type selector in `FolderCreateModal`.

**Changes:**
- Only `super admin` or `admin` can create project type vfolder.
- vfolder type in folder create modal is based on `list_allowed_types` comes from server
- Show `max_vfolder_count` in `Project Resource Policy` if user role is only `Super Admin` or `Admin`.

**Checklist:** (if applicable)

- [ ] Mention to the original issue
- [ ] Documentation
- [ ] Minium required manager version
- [ ] Specific setting for review (eg., KB link, endpoint or how to setup)
- [ ] Minimum requirements to check during review
- [ ] Test case(s) to demonstrate the difference of before/after
  • Loading branch information
ironAiken2 committed Jan 10, 2025
1 parent 16b95bf commit 1023999
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 13 deletions.
33 changes: 29 additions & 4 deletions react/src/components/FolderCreateModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useBaiSignedRequestWithPromise } from '../helper';
import { useCurrentDomainValue } from '../hooks';
import { useTanMutation } from '../hooks/reactQueryAlias';
import { useCurrentDomainValue, useSuspendedBackendaiClient } from '../hooks';
import { useCurrentUserRole } from '../hooks/backendai';
import { useTanMutation, useTanQuery } from '../hooks/reactQueryAlias';
import { useCurrentProjectValue } from '../hooks/useCurrentProject';
import BAIModal, { BAIModalProps } from './BAIModal';
import Flex from './Flex';
Expand All @@ -9,6 +10,7 @@ import StorageSelect from './StorageSelect';
import { App, Button, Divider, Form, Input, Radio, Switch, theme } from 'antd';
import { createStyles } from 'antd-style';
import { FormInstance } from 'antd/lib';
import _ from 'lodash';
import { Suspense, useRef } from 'react';
import { useTranslation } from 'react-i18next';

Expand Down Expand Up @@ -76,11 +78,21 @@ const FolderCreateModal: React.FC<FolderCreateModalProps> = ({
const { message } = App.useApp();

const formRef = useRef<FormInstance>(null);
const baiClient = useSuspendedBackendaiClient();
const userRole = useCurrentUserRole();
const currentDomain = useCurrentDomainValue();
const currentProject = useCurrentProjectValue();

const baiRequestWithPromise = useBaiSignedRequestWithPromise();

const { data: allowedTypes, isFetching: isFetchingAllowedTypes } =
useTanQuery({
queryKey: ['allowedTypes', modalProps.open],
enabled: modalProps.open,
queryFn: () =>
modalProps.open ? baiClient.vfolder.list_allowed_types() : undefined,
});

const mutationToCreateFolder = useTanMutation<
FolderCreationResponse,
{ message?: string },
Expand Down Expand Up @@ -135,6 +147,7 @@ const FolderCreateModal: React.FC<FolderCreateModalProps> = ({

return (
<BAIModal
loading={isFetchingAllowedTypes}
className={styles.modal}
title={t('data.CreateANewStorageFolder')}
footer={
Expand Down Expand Up @@ -167,6 +180,7 @@ const FolderCreateModal: React.FC<FolderCreateModalProps> = ({
</Flex>
}
width={650}
okButtonProps={{ loading: mutationToCreateFolder.isPending }}
onCancel={() => {
onRequestClose();
}}
Expand Down Expand Up @@ -224,8 +238,19 @@ const FolderCreateModal: React.FC<FolderCreateModalProps> = ({
style={{ flex: 1, marginBottom: 0 }}
>
<Radio.Group>
<Radio value={'user'}>User</Radio>
<Radio value={'project'}>Project</Radio>
{/* Both checks are required:
* - role check (admin/superadmin): Controls permission to create project folders
* - allowedTypes check: Ensures the 'group' type is registered in ETCD
* allowedTypes comes from ETCD and contains all registered types regardless of permissions,
* so we need both checks for proper access control
*/}
{_.includes(allowedTypes, 'user') ? (
<Radio value={'user'}>User</Radio>
) : null}
{(userRole === 'admin' || userRole === 'superadmin') &&
_.includes(allowedTypes, 'group') ? (
<Radio value={'project'}>Project</Radio>
) : null}
</Radio.Group>
</Form.Item>
<Divider />
Expand Down
28 changes: 22 additions & 6 deletions react/src/components/StorageStatusPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { addQuotaScopeTypePrefix, usageIndicatorColor } from '../helper';
import { useCurrentDomainValue, useSuspendedBackendaiClient } from '../hooks';
import { useCurrentUserRole } from '../hooks/backendai';
import { useSuspenseTanQuery } from '../hooks/reactQueryAlias';
import { useCurrentProjectValue } from '../hooks/useCurrentProject';
import Flex from './Flex';
Expand Down Expand Up @@ -35,6 +36,7 @@ const StorageStatusPanel: React.FC<{
const { token } = theme.useToken();
const baiClient = useSuspendedBackendaiClient();
const currentProject = useCurrentProjectValue();
const currentUserRole = useCurrentUserRole();

const [selectedVolumeInfo, setSelectedVolumeInfo] = useState<VolumeInfo>();
const deferredSelectedVolumeInfo = useDeferredValue(selectedVolumeInfo);
Expand Down Expand Up @@ -116,14 +118,15 @@ const StorageStatusPanel: React.FC<{

const {
user_resource_policy,
project_resource_policy,
keypair_resource_policy,
project_quota_scope,
user_quota_scope,
} = useLazyLoadQuery<StorageStatusPanelQuery>(
graphql`
query StorageStatusPanelQuery(
$user_RP_name: String
# $project_RP_name: String!
$project_RP_name: String!
$keypair_resource_policy_name: String
$project_quota_scope_id: String!
$user_quota_scope_id: String!
Expand All @@ -133,9 +136,10 @@ const StorageStatusPanel: React.FC<{
user_resource_policy(name: $user_RP_name) @since(version: "23.09.6") {
max_vfolder_count
}
# project_resource_policy(name: $project_RP_name) @since(version: "23.09.1") {
# max_vfolder_count
# }
project_resource_policy(name: $project_RP_name)
@since(version: "23.09.1") {
max_vfolder_count
}
keypair_resource_policy(name: $keypair_resource_policy_name)
# use max_vfolder_count in keypair_resource_policy before adding max_vfolder_count in user_resource_policy
@deprecatedSince(version: "23.09.4") {
Expand All @@ -157,7 +161,7 @@ const StorageStatusPanel: React.FC<{
`,
{
user_RP_name: user?.resource_policy,
// project_RP_name: currentProjectDetail?.resource_policy || "",
project_RP_name: currentProject?.name ?? '',
keypair_resource_policy_name: keypair?.resource_policy,
project_quota_scope_id: addQuotaScopeTypePrefix(
'project',
Expand All @@ -171,7 +175,6 @@ const StorageStatusPanel: React.FC<{
!deferredSelectedVolumeInfo?.id,
},
);

// Support version:
// keypair resource policy < 23.09.4
// user resource policy, project resource policy >= 23.09.6
Expand Down Expand Up @@ -228,6 +231,19 @@ const StorageStatusPanel: React.FC<{
{t('data.ProjectFolder')}:
</Typography.Text>
{projectFolderCount}
{(currentUserRole === 'admin' ||
currentUserRole === 'superadmin') &&
(project_resource_policy?.max_vfolder_count ?? -1) >= 0 ? (
<>
<Typography.Text type="secondary">{' / '}</Typography.Text>
<Typography.Text type="secondary">
{t('data.Limit')}:
</Typography.Text>
{project_resource_policy?.max_vfolder_count === 0
? '∞'
: project_resource_policy?.max_vfolder_count}
</>
) : null}
</Flex>
<Flex gap={token.marginXXS} style={{ marginRight: 30 }}>
<Typography.Text type="secondary">
Expand Down
6 changes: 3 additions & 3 deletions react/src/pages/VFolderListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,20 @@ const tabParam = withDefault(StringParam, 'general');

const VFolderListPage: React.FC<VFolderListPageProps> = (props) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const [curTabKey, setCurTabKey] = useQueryParam('tab', tabParam, {
updateType: 'replace',
});
const baiClient = useSuspendedBackendaiClient();
const [fetchKey, updateFetchKey] = useUpdatableState('first');
const dataViewRef = useRef(null);
const [isOpenCreateModal, { toggle: openCreateModal }] = useToggle(false);
const [inviteFolderId, setInviteFolderId] = useState<string | null>(null);
const [
isVisibleImportFromHuggingFaceModal,
{ toggle: toggleImportFromHuggingFaceModal },
] = useToggle(false);

const { token } = theme.useToken();
const dataViewRef = useRef(null);
const baiClient = useSuspendedBackendaiClient();
const enableImportFromHuggingFace =
baiClient._config.enableImportFromHuggingFace;

Expand Down

0 comments on commit 1023999

Please sign in to comment.