diff --git a/web/satellite/src/api/accessGrants.ts b/web/satellite/src/api/accessGrants.ts index 0bce8dd0161c..6ce0995b87ed 100644 --- a/web/satellite/src/api/accessGrants.ts +++ b/web/satellite/src/api/accessGrants.ts @@ -129,7 +129,26 @@ export class AccessGrantsApiGql extends BaseGql implements AccessGrantsApi { } /** - * Used to delete access grant access grant by name and project ID. + * Fetch all API key names. + * + * @returns string[] + * @throws Error + */ + public async getAllAPIKeyNames(projectId: string): Promise { + const path = `${this.ROOT_PATH}/api-key-names?projectID=${projectId}`; + const response = await this.client.get(path); + + if (!response.ok) { + throw new Error('Can not get access grant names'); + } + + const result = await response.json(); + + return result ? result : []; + } + + /** + * Used to delete access grant by name and project ID. * * @param name - name of the access grant that will be deleted * @param projectID - id of the project where access grant was created diff --git a/web/satellite/src/components/accessGrants/createFlow/CreateAccessGrantFlow.vue b/web/satellite/src/components/accessGrants/createFlow/CreateAccessGrantFlow.vue index ba42049e4fa1..23b43203dc3b 100644 --- a/web/satellite/src/components/accessGrants/createFlow/CreateAccessGrantFlow.vue +++ b/web/satellite/src/components/accessGrants/createFlow/CreateAccessGrantFlow.vue @@ -99,7 +99,9 @@ :credentials="edgeCredentials" :name="accessName" /> - @@ -130,6 +132,7 @@ import { useProjectsStore } from '@/store/modules/projectsStore'; import { useConfigStore } from '@/store/modules/configStore'; import VModal from '@/components/common/VModal.vue'; +import VLoader from '@/components/common/VLoader.vue'; import CreateNewAccessStep from '@/components/accessGrants/createFlow/steps/CreateNewAccessStep.vue'; import ChoosePermissionStep from '@/components/accessGrants/createFlow/steps/ChoosePermissionStep.vue'; import AccessEncryptionStep from '@/components/accessGrants/createFlow/steps/AccessEncryptionStep.vue'; @@ -157,6 +160,13 @@ const initPermissions = [ Permission.List, ]; +/** + * Returns all AG names from store. + */ +const allAGNames = computed((): string[] => { + return agStore.state.allAGNames; +}); + /** * Indicates if user has to be prompt to enter project passphrase. */ @@ -172,7 +182,7 @@ const storedPassphrase = computed((): string => { }); const worker = ref(null); -const isLoading = ref(false); +const isLoading = ref(true); const step = ref(CreateAccessStep.CreateNewAccess); const selectedAccessTypes = ref([]); const selectedPermissions = ref(initPermissions); @@ -384,6 +394,11 @@ function setStep(stepArg: CreateAccessStep): void { * If not then we set regular second step (Permissions). */ function setSecondStepBasedOnAccessType(): void { + if (allAGNames.value.includes(accessName.value)) { + notify.error('Provided name is already in use', AnalyticsErrorEventSource.CREATE_AG_MODAL); + return; + } + // Unfortunately local storage updates are not reactive so putting it inside computed property doesn't do anything. // That's why we explicitly call it here. const shouldShowInfo = !LocalData.getServerSideEncryptionModalHidden() && selectedAccessTypes.value.includes(AccessType.S3); @@ -626,10 +641,17 @@ onMounted(async () => { generatedPassphrase.value = generateMnemonic(); try { - await bucketsStore.getAllBucketsNames(projectsStore.state.selectedProject.id); + const projectID = projectsStore.state.selectedProject.id; + + await Promise.all([ + agStore.getAllAGNames(projectID), + bucketsStore.getAllBucketsNames(projectID), + ]); } catch (error) { - notify.error(`Unable to fetch all bucket names. ${error.message}`, AnalyticsErrorEventSource.CREATE_AG_MODAL); + notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL); } + + isLoading.value = false; }); @@ -677,6 +699,9 @@ onMounted(async () => { inset: 0; background-color: rgb(0 0 0 / 10%); border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; } } diff --git a/web/satellite/src/store/modules/accessGrantsStore.ts b/web/satellite/src/store/modules/accessGrantsStore.ts index 218e5344f274..c43277faaa16 100644 --- a/web/satellite/src/store/modules/accessGrantsStore.ts +++ b/web/satellite/src/store/modules/accessGrantsStore.ts @@ -18,6 +18,7 @@ import { useConfigStore } from '@/store/modules/configStore'; import { DEFAULT_PAGE_LIMIT } from '@/types/pagination'; class AccessGrantsState { + public allAGNames: string[] = []; public cursor: AccessGrantCursor = new AccessGrantCursor(); public page: AccessGrantsPage = new AccessGrantsPage(); public selectedAccessGrantsIds: string[] = []; @@ -76,6 +77,10 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => { state.isAccessGrantsWebWorkerReady = false; } + async function getAllAGNames(projectID: string): Promise { + state.allAGNames = await api.getAllAPIKeyNames(projectID); + } + async function getAccessGrants(pageNumber: number, projectID: string, limit = DEFAULT_PAGE_LIMIT): Promise { state.cursor.page = pageNumber; state.cursor.limit = limit; @@ -205,6 +210,7 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => { } function clear(): void { + state.allAGNames = []; state.cursor = new AccessGrantCursor(); state.page = new AccessGrantsPage(); state.selectedAccessGrantsIds = []; @@ -227,6 +233,7 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => { return { state, selectedAccessGrants, + getAllAGNames, startWorker, stopWorker, getAccessGrants, diff --git a/web/satellite/src/types/accessGrants.ts b/web/satellite/src/types/accessGrants.ts index 7272d15579f5..c917b130d4af 100644 --- a/web/satellite/src/types/accessGrants.ts +++ b/web/satellite/src/types/accessGrants.ts @@ -6,14 +6,6 @@ import { DEFAULT_PAGE_LIMIT } from '@/types/pagination'; export type OnHeaderClickCallback = (sortBy: AccessGrantsOrderBy, sortDirection: SortDirection) => Promise; -/** - * AccessGrantsWorker provides access to the WASM module. - */ -export interface AccessGrantsWorkerFactory { - // TODO: this should be converted to a proper interface. - create(): Worker; -} - /** * Exposes all access grants-related functionality. */ @@ -50,6 +42,14 @@ export interface AccessGrantsApi { */ deleteByNameAndProjectID(name: string, projectID: string): Promise; + /** + * Fetch all API key names. + * + * @returns string[] + * @throws Error + */ + getAllAPIKeyNames(projectId: string): Promise + /** * Get gateway credentials using access grant * diff --git a/web/satellite/tests/unit/mock/api/accessGrants.ts b/web/satellite/tests/unit/mock/api/accessGrants.ts index 3e884846ed7a..699c890775dc 100644 --- a/web/satellite/tests/unit/mock/api/accessGrants.ts +++ b/web/satellite/tests/unit/mock/api/accessGrants.ts @@ -36,6 +36,10 @@ export class AccessGrantsMock implements AccessGrantsApi { return Promise.resolve(); } + getAllAPIKeyNames(_projectID: string): Promise { + return Promise.resolve([]); + } + getGatewayCredentials(_accessGrant: string, _requestURL: string): Promise { return Promise.resolve(new EdgeCredentials('testCredId', new Date(), 'testAccessKeyId', 'testSecret', 'testEndpoint')); }