web/satellite: access created step for new access grant flow

Added access created step to new access grant flow.
Also added some really tiny fixes for current project passphrase flow.

Change-Id: I94eb25b53cf7cc0c6ea7b9c13762ca76736ea7ac
This commit is contained in:
Vitalii 2023-02-16 18:59:30 +02:00 committed by Storj Robot
parent fa2f7336cd
commit 991211a2b0
13 changed files with 624 additions and 71 deletions

View File

@ -27,7 +27,9 @@
:on-select-permission="selectPermissions"
:selected-permissions="selectedPermissions"
:on-back="setFirstStepBasedOnAccessType"
:on-continue="() => setStep(CreateAccessStep.AccessEncryption)"
:on-continue="() => setStep(
selectedAccessTypes.includes(AccessType.APIKey) ? setLastStep() : CreateAccessStep.AccessEncryption
)"
:selected-buckets="selectedBuckets"
:on-select-bucket="selectBucket"
:on-select-all-buckets="selectAllBuckets"
@ -36,6 +38,7 @@
:on-set-not-after="setNotAfter"
:not-after-label="notAfterLabel"
:on-set-not-after-label="setNotAfterLabel"
:is-loading="isLoading"
/>
<AccessEncryptionStep
v-if="step === CreateAccessStep.AccessEncryption"
@ -43,33 +46,45 @@
:on-continue="setStepBasedOnPassphraseOption"
:passphrase-option="passphraseOption"
:set-option="setPassphraseOption"
:is-loading="isLoading"
/>
<EnterPassphraseStep
v-if="step === CreateAccessStep.EnterMyPassphrase"
:is-new-passphrase="false"
:on-back="() => setStep(CreateAccessStep.AccessEncryption)"
:on-continue="() => setStep(CreateAccessStep.AccessCreated)"
:on-continue="setLastStep"
:passphrase="enteredPassphrase"
:set-passphrase="setPassphrase"
info="Enter the encryption passphrase used for this project to create this access grant."
:is-loading="isLoading"
/>
<EnterPassphraseStep
v-if="step === CreateAccessStep.EnterNewPassphrase"
:is-new-passphrase="true"
:on-back="() => setStep(CreateAccessStep.AccessEncryption)"
:on-continue="() => setStep(CreateAccessStep.AccessCreated)"
:on-continue="setLastStep"
:passphrase="enteredPassphrase"
:set-passphrase="setPassphrase"
info="This passphrase will be used to encrypt all the files you upload using this access grant.
You will need it to access these files in the future."
:is-loading="isLoading"
/>
<PassphraseGeneratedStep
v-if="step === CreateAccessStep.PassphraseGenerated"
:on-back="() => setStep(CreateAccessStep.AccessEncryption)"
:on-continue="() => setStep(CreateAccessStep.AccessCreated)"
:on-continue="setLastStep"
:passphrase="generatedPassphrase"
:name="accessName"
:is-loading="isLoading"
/>
<AccessCreatedStep
v-if="step === CreateAccessStep.AccessCreated"
:on-continue="() => setStep(CreateAccessStep.CredentialsCreated)"
:access-grant="accessGrant"
:name="accessName"
:access-types="selectedAccessTypes"
/>
<div v-if="isLoading" class="modal__blur" />
</div>
</template>
</VModal>
@ -89,8 +104,14 @@ import {
STEP_ICON_AND_TITLE,
} from '@/types/createAccessGrant';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { LocalData } from '@/utils/localData';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { MetaUtils } from '@/utils/meta';
import { AnalyticsHttpApi } from '@/api/analytics';
import { OBJECTS_MUTATIONS } from '@/store/modules/objects';
import VModal from '@/components/common/VModal.vue';
import CreateNewAccessStep from '@/components/accessGrants/newCreateFlow/steps/CreateNewAccessStep.vue';
@ -99,6 +120,7 @@ import AccessEncryptionStep from '@/components/accessGrants/newCreateFlow/steps/
import EnterPassphraseStep from '@/components/accessGrants/newCreateFlow/steps/EnterPassphraseStep.vue';
import PassphraseGeneratedStep from '@/components/accessGrants/newCreateFlow/steps/PassphraseGeneratedStep.vue';
import EncryptionInfoStep from '@/components/accessGrants/newCreateFlow/steps/EncryptionInfoStep.vue';
import AccessCreatedStep from '@/components/accessGrants/newCreateFlow/steps/AccessCreatedStep.vue';
const router = useRouter();
const route = useRoute();
@ -126,6 +148,8 @@ const storedPassphrase = computed((): string => {
return store.state.objectsModule.passphrase;
});
const worker = ref<Worker>();
const isLoading = ref<boolean>(false);
const step = ref<CreateAccessStep>(CreateAccessStep.CreateNewAccess);
const selectedAccessTypes = ref<AccessType[]>([]);
const selectedPermissions = ref<Permission[]>(initPermissions);
@ -139,6 +163,14 @@ const accessName = ref<string>('');
const notAfter = ref<Date | undefined>(undefined);
const notAfterLabel = ref<string>('No end date');
// Generated values.
const cliAccess = ref<string>('');
const accessGrant = ref<string>('');
const edgeCredentials = ref<EdgeCredentials>(new EdgeCredentials());
const FIRST_PAGE = 1;
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Selects access type.
*/
@ -338,7 +370,7 @@ function setFirstStepBasedOnAccessType(): void {
/**
* Sets next step depending on selected passphrase option.
*/
function setStepBasedOnPassphraseOption(): void {
async function setStepBasedOnPassphraseOption(): Promise<void> {
switch (passphraseOption.value) {
case PassphraseOption.SetMyProjectPassphrase:
step.value = CreateAccessStep.EnterMyPassphrase;
@ -349,9 +381,8 @@ function setStepBasedOnPassphraseOption(): void {
case PassphraseOption.GenerateNewPassphrase:
step.value = CreateAccessStep.PassphraseGenerated;
break;
default:
// TODO: generate access and redirect to access created.
step.value = CreateAccessStep.AccessCreated;
case PassphraseOption.UseExistingPassphrase:
await setLastStep();
}
}
@ -362,11 +393,190 @@ function closeModal(): void {
router.push(RouteConfig.AccessGrants.path);
}
/**
* Sets local worker with worker instantiated in store.
* Also sets worker's onmessage and onerror logic.
*/
function setWorker(): void {
worker.value = store.state.accessGrantsModule.accessGrantsWebWorker;
if (worker.value) {
worker.value.onerror = (error: ErrorEvent) => {
notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
};
}
}
/**
* Generates CLI access.
*/
async function createCLIAccess(): Promise<void> {
if (!worker.value) {
throw new Error('Web worker is not initialized.');
}
// creates fresh new API key.
const cleanAPIKey: AccessGrant = await store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, accessName.value);
try {
await store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, FIRST_PAGE);
} catch (error) {
await notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.CREATE_AG_MODAL);
}
let permissionsMsg = {
'type': 'SetPermission',
'buckets': selectedBuckets.value,
'apiKey': cleanAPIKey.secret,
'isDownload': selectedPermissions.value.includes(Permission.Read),
'isUpload': selectedPermissions.value.includes(Permission.Write),
'isList': selectedPermissions.value.includes(Permission.List),
'isDelete': selectedPermissions.value.includes(Permission.Delete),
'notBefore': new Date().toISOString(),
};
if (notAfter.value) permissionsMsg = Object.assign(permissionsMsg, { 'notAfter': notAfter.value.toISOString() });
await worker.value.postMessage(permissionsMsg);
const grantEvent: MessageEvent = await new Promise(resolve => {
if (worker.value) {
worker.value.onmessage = resolve;
}
});
if (grantEvent.data.error) {
throw new Error(grantEvent.data.error);
}
cliAccess.value = grantEvent.data.value;
if (selectedAccessTypes.value.includes(AccessType.APIKey)) {
analytics.eventTriggered(AnalyticsEvent.API_ACCESS_CREATED);
}
}
/**
* Generates access grant.
*/
async function createAccessGrant(): Promise<void> {
if (!worker.value) {
throw new Error('Web worker is not initialized.');
}
// creates access credentials.
const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl');
const salt = await store.dispatch(PROJECTS_ACTIONS.GET_SALT, store.getters.selectedProject.id);
let usedPassphrase = '';
switch (passphraseOption.value) {
case PassphraseOption.UseExistingPassphrase:
usedPassphrase = storedPassphrase.value;
break;
case PassphraseOption.EnterNewPassphrase:
case PassphraseOption.SetMyProjectPassphrase:
usedPassphrase = enteredPassphrase.value;
break;
case PassphraseOption.GenerateNewPassphrase:
usedPassphrase = generatedPassphrase.value;
}
if (!usedPassphrase) {
throw new Error('Passphrase can\'t be empty');
}
worker.value.postMessage({
'type': 'GenerateAccess',
'apiKey': cliAccess.value,
'passphrase': usedPassphrase,
'salt': salt,
'satelliteNodeURL': satelliteNodeURL,
});
const accessEvent: MessageEvent = await new Promise(resolve => {
if (worker.value) {
worker.value.onmessage = resolve;
}
});
if (accessEvent.data.error) {
throw new Error(accessEvent.data.error);
}
accessGrant.value = accessEvent.data.value;
if (selectedAccessTypes.value.includes(AccessType.AccessGrant)) {
analytics.eventTriggered(AnalyticsEvent.ACCESS_GRANT_CREATED);
}
}
/**
* Generates edge credentials.
*/
async function createEdgeCredentials(): Promise<void> {
edgeCredentials.value = await store.dispatch(
ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant: accessGrant.value },
);
analytics.eventTriggered(AnalyticsEvent.GATEWAY_CREDENTIALS_CREATED);
}
/**
* Generates access and sets the last step depending on selected access type.
*/
async function setLastStep(): Promise<void> {
if (isLoading.value) {
return;
}
isLoading.value = true;
try {
switch (true) {
case selectedAccessTypes.value.includes(AccessType.APIKey):
await createCLIAccess();
step.value = CreateAccessStep.CLIAccessCreated;
break;
case selectedAccessTypes.value.includes(AccessType.AccessGrant) && selectedAccessTypes.value.includes(AccessType.S3):
await createCLIAccess();
await createAccessGrant();
await createEdgeCredentials();
step.value = CreateAccessStep.AccessCreated;
break;
case selectedAccessTypes.value.includes(AccessType.S3):
await createCLIAccess();
await createAccessGrant();
await createEdgeCredentials();
step.value = CreateAccessStep.CredentialsCreated;
break;
case selectedAccessTypes.value.includes(AccessType.AccessGrant):
await createCLIAccess();
await createAccessGrant();
step.value = CreateAccessStep.AccessCreated;
}
// This is an action to handle case if user sets project level passphrase.
if (
passphraseOption.value === PassphraseOption.SetMyProjectPassphrase &&
!selectedAccessTypes.value.includes(AccessType.APIKey)
) {
store.commit(OBJECTS_MUTATIONS.SET_PASSPHRASE, enteredPassphrase.value);
store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
}
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
}
isLoading.value = false;
}
onMounted(async () => {
if (route.params?.accessType) {
selectedAccessTypes.value.push(route.params?.accessType as AccessType);
}
setWorker();
generatedPassphrase.value = generateMnemonic();
try {
@ -383,6 +593,7 @@ onMounted(async () => {
padding: 32px;
display: flex;
flex-direction: column;
position: relative;
&__header {
display: flex;
@ -399,5 +610,15 @@ onMounted(async () => {
color: var(--c-black);
}
}
&__blur {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: rgb(0 0 0 / 10%);
border-radius: 10px;
}
}
</style>

View File

@ -3,19 +3,34 @@
<template>
<div class="blured-container">
<p v-if="isMnemonic" class="blured-container__mnemonic">{{ value }}</p>
<p v-else class="blured-container__text">{{ value }}</p>
<CopyIcon v-if="!isMnemonic" class="blured-container__copy" />
<div v-if="!isValueShown" class="blured-container__blur">
<VButton
:label="buttonLabel"
icon="lock"
width="159px"
height="40px"
font-size="12px"
:is-white="true"
:on-press="showValue"
/>
<div v-if="!isMnemonic" class="blured-container__header">
<h2 class="blured-container__header__title">{{ title }}</h2>
<VInfo v-if="info" class="blured-container__header__info">
<template #icon>
<InfoIcon class="blured-container__header__info__icon" />
</template>
<template #message>
<p class="blured-container__header__info__text">{{ info }}</p>
</template>
</VInfo>
</div>
<div class="blured-container__wrap">
<p v-if="isMnemonic" class="blured-container__wrap__mnemonic">{{ value }}</p>
<p v-else class="blured-container__wrap__text">{{ value }}</p>
<div v-if="!isMnemonic" v-clipboard:copy="value" class="blured-container__wrap__copy" @click="onCopy">
<CopyIcon />
</div>
<div v-if="!isValueShown" class="blured-container__wrap__blur">
<VButton
:label="buttonLabel"
icon="lock"
width="159px"
height="40px"
font-size="12px"
:is-white="true"
:on-press="showValue"
/>
</div>
</div>
</div>
</template>
@ -23,70 +38,146 @@
<script setup lang="ts">
import { ref } from 'vue';
import VButton from '@/components/common/VButton.vue';
import { useNotify } from '@/utils/hooks';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import VButton from '@/components/common/VButton.vue';
import VInfo from '@/components/common/VInfo.vue';
import InfoIcon from '@/../static/images/accessGrants/newCreateFlow/info.svg';
import CopyIcon from '@/../static/images/accessGrants/newCreateFlow/copy.svg';
const props = defineProps<{
const props = withDefaults(defineProps<{
isMnemonic: boolean;
value: string;
buttonLabel: string;
}>();
title?: string;
info?: string;
}>(), {
title: '',
info: '',
});
const notify = useNotify();
const isValueShown = ref<boolean>(false);
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Makes blurred value to be shown.
*/
function showValue(): void {
isValueShown.value = true;
}
/**
* Holds on copy click logic.
*/
function onCopy(): void {
analytics.eventTriggered(AnalyticsEvent.COPY_TO_CLIPBOARD_CLICKED);
notify.success(`${props.title} was copied successfully`);
}
</script>
<style scoped lang="scss">
.blured-container {
display: flex;
align-items: center;
font-family: 'font_regular', sans-serif;
padding: 10px 16px;
background: var(--c-grey-2);
border: 1px solid var(--c-grey-3);
border-radius: 10px;
position: relative;
&__mnemonic {
font-size: 14px;
line-height: 26px;
color: var(--c-black);
text-align: justify-all;
}
&__text {
font-size: 14px;
line-height: 20px;
color: var(--c-grey-7);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 16px;
}
&__copy {
min-width: 16px;
cursor: pointer;
}
&__blur {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
&__header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-grey-6);
}
&__info {
margin-left: 8px;
max-height: 16px;
&__icon {
cursor: pointer;
}
&__text {
color: var(--c-white);
}
}
}
&__wrap {
padding: 16px;
background: var(--c-grey-2);
border: 1px solid var(--c-grey-2);
border-radius: 10px;
backdrop-filter: blur(10px);
position: relative;
display: flex;
align-items: center;
&__mnemonic {
font-size: 14px;
line-height: 26px;
color: var(--c-black);
text-align: justify-all;
}
&__text {
font-size: 14px;
line-height: 20px;
color: var(--c-grey-7);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 16px;
}
&__copy {
min-width: 16px;
cursor: pointer;
}
&__blur {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
backdrop-filter: blur(10px);
}
}
}
:deep(.info__box) {
width: 270px;
left: calc(50% - 135px);
top: unset;
bottom: 15px;
cursor: default;
filter: none;
transform: rotate(-180deg);
}
:deep(.info__box__message) {
background: var(--c-grey-6);
border-radius: 4px;
padding: 10px 8px;
transform: rotate(-180deg);
}
:deep(.info__box__arrow) {
background: var(--c-grey-6);
width: 10px;
height: 10px;
margin-bottom: -3px;
}
</style>

View File

@ -0,0 +1,188 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="access-created">
<p class="access-created__info">
Now copy or download to save the Access Grant as it will only appear once.
</p>
<ButtonsContainer label="Save your access grant">
<template #leftButton>
<VButton
v-clipboard:copy="accessGrant"
:label="isCopied ? 'Copied' : 'Copy'"
width="100%"
height="40px"
font-size="14px"
:on-press="onCopy"
:icon="isCopied ? 'none' : 'copy'"
:is-white="!isCopied"
:is-white-green="isCopied"
/>
</template>
<template #rightButton>
<VButton
:label="isDownloaded ? 'Downloaded' : 'Download'"
width="100%"
height="40px"
font-size="14px"
:on-press="onDownload"
:icon="isDownloaded ? 'none' : 'download'"
:is-white="!isDownloaded"
:is-green-white="isDownloaded"
/>
</template>
</ButtonsContainer>
<div class="access-created__blurred">
<ValueWithBlur
button-label="Show Access Grant"
:is-mnemonic="false"
:value="accessGrant"
title="Access Grant"
/>
</div>
<ButtonsContainer>
<template #leftButton>
<a
class="access-created__button-link"
href="https://docs.storj.io/dcs/concepts/access/access-grants"
target="_blank"
rel="noopener noreferrer"
>
<p class="access-created__button-link__label">
Learn More
</p>
</a>
</template>
<template #rightButton>
<VButton
:label="hasNextStep ? 'Next' : 'Finish'"
width="100%"
height="48px"
font-size="14px"
:on-press="onNextButtonClick"
/>
</template>
</ButtonsContainer>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useNotify, useRouter, useStore } from '@/utils/hooks';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { Download } from '@/utils/download';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AccessType } from '@/types/createAccessGrant';
import { RouteConfig } from '@/router';
import VButton from '@/components/common/VButton.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/newCreateFlow/components/ValueWithBlur.vue';
const props = defineProps<{
accessTypes: AccessType[];
name: string;
accessGrant: string;
onContinue: () => void;
}>();
const store = useStore();
const notify = useNotify();
const router = useRouter();
const isCopied = ref<boolean>(false);
const isDownloaded = ref<boolean>(false);
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Indicates if there are S3 credentials to show.
*/
const hasNextStep = computed((): boolean => {
return props.accessTypes.includes(AccessType.S3) && props.accessTypes.includes(AccessType.AccessGrant);
});
/**
* Saves passphrase to clipboard.
*/
function onCopy(): void {
isCopied.value = true;
analytics.eventTriggered(AnalyticsEvent.COPY_TO_CLIPBOARD_CLICKED);
notify.success(`Access Grant was copied successfully`);
}
/**
* Downloads passphrase into .txt file.
*/
function onDownload(): void {
isDownloaded.value = true;
Download.file(props.accessGrant, `Storj-access-${props.name}-${new Date().toISOString()}.txt`);
analytics.eventTriggered(AnalyticsEvent.DOWNLOAD_TXT_CLICKED);
}
/**
* Holds on Next/Finish button click logic.
*/
function onNextButtonClick(): void {
if (hasNextStep.value) {
props.onContinue();
return;
}
router.push(RouteConfig.AccessGrants.path);
}
</script>
<style lang="scss" scoped>
.access-created {
font-family: 'font_regular', sans-serif;
&__info {
font-size: 14px;
line-height: 20px;
color: #091c45;
padding: 16px 0;
margin-bottom: 16px;
border-bottom: 1px solid #ebeef1;
text-align: left;
}
&__button-link {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 48px;
background: #fff;
border: 1px solid #d8dee3;
border-radius: 8px;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.02em;
color: #56606d;
margin-left: 8px;
}
&:hover {
border-color: #2683ff;
background-color: #2683ff;
p {
color: #fff;
}
}
}
&__blurred {
margin-top: 16px;
padding: 16px 0;
border-top: 1px solid #ebeef1;
border-bottom: 1px solid #ebeef1;
}
}
</style>

View File

@ -77,6 +77,7 @@
font-size="14px"
:on-press="onBack"
:is-white="true"
:is-disabled="isLoading"
/>
</template>
<template #rightButton>
@ -86,6 +87,7 @@
height="48px"
font-size="14px"
:on-press="onContinue"
:is-disabled="isLoading"
/>
</template>
</ButtonsContainer>
@ -115,6 +117,7 @@ const props = defineProps<{
setOption: (option: PassphraseOption) => void;
onBack: () => void;
onContinue: () => void;
isLoading: boolean;
}>();
const store = useStore();

View File

@ -102,6 +102,7 @@
font-size="14px"
:on-press="onBack"
:is-white="true"
:is-disabled="isLoading"
/>
</template>
<template #rightButton>
@ -110,8 +111,8 @@
width="100%"
height="48px"
font-size="14px"
:on-press="onContinue"
:is-disabled="isButtonDisabled"
:on-press="handleContinue"
:is-disabled="isButtonDisabled || isLoading"
/>
</template>
</ButtonsContainer>
@ -126,7 +127,8 @@ import {
FunctionalContainer,
Permission,
} from '@/types/createAccessGrant';
import { useStore } from '@/utils/hooks';
import { useNotify, useStore } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import ContainerWithIcon from '@/components/accessGrants/newCreateFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
@ -149,12 +151,14 @@ const props = withDefaults(defineProps<{
notAfterLabel: string;
onBack: () => void;
onContinue: () => void;
isLoading: boolean;
notAfter?: Date;
}>(), {
notAfter: undefined,
});
const store = useStore();
const notify = useNotify();
const allPermissionsShown = ref<boolean>(false);
const searchBucketsShown = ref<boolean>(false);
@ -188,6 +192,23 @@ function selectBucket(bucket: string): void {
searchQuery.value = '';
}
/**
* Handles continue button click.
*/
function handleContinue(): void {
if (props.notAfter) {
const now = new Date();
now.setHours(0, 0, 0, 0);
if (props.notAfter.getTime() < now.getTime()) {
notify.error('End date must be later or equal to today.', AnalyticsErrorEventSource.CREATE_AG_MODAL);
return;
}
}
props.onContinue();
}
/**
* Selects all buckets and clears search.
*/

View File

@ -29,6 +29,7 @@
font-size="14px"
:on-press="onBack"
:is-white="true"
:is-disabled="isLoading"
/>
</template>
<template #rightButton>
@ -38,7 +39,7 @@
height="48px"
font-size="14px"
:on-press="onContinue"
:is-disabled="isButtonDisabled"
:is-disabled="isButtonDisabled || isLoading"
/>
</template>
</ButtonsContainer>
@ -62,6 +63,7 @@ const props = defineProps<{
setPassphrase: (value: string) => void;
onBack: () => void;
onContinue: () => void;
isLoading: boolean;
}>();
const store = useStore();

View File

@ -57,6 +57,7 @@
font-size="14px"
:on-press="onBack"
:is-white="true"
:is-disabled="isLoading"
/>
</template>
<template #rightButton>
@ -66,7 +67,7 @@
height="48px"
font-size="14px"
:on-press="onContinue"
:is-disabled="isButtonDisabled"
:is-disabled="isButtonDisabled || isLoading"
/>
</template>
</ButtonsContainer>
@ -91,6 +92,7 @@ const props = defineProps<{
passphrase: string;
onBack: () => void;
onContinue: () => void;
isLoading: boolean;
}>();
const store = useStore();

View File

@ -161,7 +161,8 @@ export default class EnterPassphraseModal extends Vue {
const gatewayCredentials: EdgeCredentials = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant });
await this.$store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
await this.$store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
await this.$store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
this.$store.commit(OBJECTS_MUTATIONS.SET_PASSPHRASE, this.passphrase);
this.$store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
}
/**

View File

@ -108,7 +108,8 @@ export default class OpenBucketModal extends Vue {
if (this.isLoading) return;
if (this.isWarningState) {
await this.$store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
this.$store.commit(OBJECTS_MUTATIONS.SET_PASSPHRASE, this.passphrase);
this.$store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
this.closeModal();
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.UploadFile).path);
@ -134,7 +135,8 @@ export default class OpenBucketModal extends Vue {
this.isLoading = false;
return;
}
await this.$store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
this.$store.commit(OBJECTS_MUTATIONS.SET_PASSPHRASE, this.passphrase);
this.$store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
this.isLoading = false;
this.closeModal();

View File

@ -247,7 +247,8 @@ async function setAccess(): Promise<void> {
const gatewayCredentials: EdgeCredentials = await store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant });
await store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
await store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
await store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
store.commit(OBJECTS_MUTATIONS.SET_PASSPHRASE, passphrase.value);
store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
}
/**

View File

@ -171,6 +171,7 @@ async function setAccess(): Promise<void> {
const gatewayCredentials: EdgeCredentials = await store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant });
await store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
await store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
store.commit(OBJECTS_MUTATIONS.SET_PASSPHRASE, passphrase.value);
store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
}

View File

@ -8,6 +8,7 @@ import ChoosePermissionIcon from '@/../static/images/accessGrants/newCreateFlow/
import AccessEncryptionIcon from '@/../static/images/accessGrants/newCreateFlow/accessEncryption.svg';
import PassphraseGeneratedIcon from '@/../static/images/accessGrants/newCreateFlow/passphraseGenerated.svg';
import AccessCreatedIcon from '@/../static/images/accessGrants/newCreateFlow/accessCreated.svg';
import CLIAccessCreatedIcon from '@/../static/images/accessGrants/newCreateFlow/cliAccessCreated.svg';
import CredentialsCreatedIcon from '@/../static/images/accessGrants/newCreateFlow/credentialsCreated.svg';
import EncryptionInfoIcon from '@/../static/images/accessGrants/newCreateFlow/encryptionInfo.svg';
import TypeIcon from '@/../static/images/accessGrants/newCreateFlow/typeIcon.svg';
@ -44,6 +45,7 @@ export enum CreateAccessStep {
EnterMyPassphrase = 'enterMyPassphrase',
EnterNewPassphrase = 'enterNewPassphrase',
AccessCreated = 'accessCreated',
CLIAccessCreated = 'cliAccessCreated',
CredentialsCreated = 'credentialsCreated',
}
@ -92,6 +94,10 @@ export const STEP_ICON_AND_TITLE: Record<CreateAccessStep, IconAndTitle> = {
icon: CredentialsCreatedIcon,
title: 'Credentials created',
},
[CreateAccessStep.CLIAccessCreated]: {
icon: CLIAccessCreatedIcon,
title: 'CLI access created',
},
};
export enum FunctionalContainer {

View File

@ -0,0 +1,14 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4424 0H23.3464C28.8835 0 31.0805 0.613723 33.2354 1.76617C35.3903 2.91861 37.0814 4.60977 38.2339 6.76466L38.3215 6.93055C39.403 9.00672 39.9846 11.2 40 16.4423V23.3463C40 28.8835 39.3863 31.0805 38.2339 33.2353C37.0814 35.3902 35.3903 37.0814 33.2354 38.2338L33.0695 38.3214C30.9933 39.4029 28.8001 39.9846 23.5577 40H16.6537C11.1165 40 8.91955 39.3863 6.76466 38.2338C4.60978 37.0814 2.91861 35.3902 1.76617 33.2353L1.67858 33.0694C0.597074 30.9933 0.0154219 28.8 0 23.5577V16.6537C0 11.1165 0.613724 8.91954 1.76617 6.76466C2.91861 4.60977 4.60978 2.91861 6.76466 1.76617L6.93056 1.67858C9.00673 0.597074 11.2 0.0154219 16.4424 0Z" fill="#0218A7"/>
<path d="M6.65378 8.66401C7.49447 8.66401 8.17597 7.9825 8.17597 7.14182C8.17597 6.30114 7.49447 5.61963 6.65378 5.61963C5.8131 5.61963 5.13159 6.30114 5.13159 7.14182C5.13159 7.9825 5.8131 8.66401 6.65378 8.66401Z" fill="#FF458B"/>
<path d="M11.2205 8.66401C12.0612 8.66401 12.7427 7.9825 12.7427 7.14182C12.7427 6.30114 12.0612 5.61963 11.2205 5.61963C10.3798 5.61963 9.69824 6.30114 9.69824 7.14182C9.69824 7.9825 10.3798 8.66401 11.2205 8.66401Z" fill="#FFC600"/>
<path d="M15.7871 8.66401C16.6277 8.66401 17.3093 7.9825 17.3093 7.14182C17.3093 6.30114 16.6277 5.61963 15.7871 5.61963C14.9464 5.61963 14.2649 6.30114 14.2649 7.14182C14.2649 7.9825 14.9464 8.66401 15.7871 8.66401Z" fill="#00E567"/>
<path d="M20.6705 14.0259H6.85523C6.01453 14.0259 5.33301 14.7074 5.33301 15.5481C5.33301 16.3887 6.01453 17.0702 6.85523 17.0702H20.6705C21.5112 17.0702 22.1928 16.3887 22.1928 15.5481C22.1928 14.7074 21.5112 14.0259 20.6705 14.0259Z" fill="#0149FF"/>
<path d="M16.3216 19.3174H12.7698C11.9291 19.3174 11.2476 19.9989 11.2476 20.8396C11.2476 21.6803 11.9291 22.3618 12.7698 22.3618H16.3216C17.1622 22.3618 17.8438 21.6803 17.8438 20.8396C17.8438 19.9989 17.1622 19.3174 16.3216 19.3174Z" fill="#FF458B"/>
<path d="M25.5272 19.3174H21.9753C21.1346 19.3174 20.4531 19.9989 20.4531 20.8396C20.4531 21.6803 21.1346 22.3618 21.9753 22.3618H25.5272C26.3679 22.3618 27.0494 21.6803 27.0494 20.8396C27.0494 19.9989 26.3679 19.3174 25.5272 19.3174Z" fill="#00E567"/>
<path d="M34.7328 19.3174H31.1811C30.3404 19.3174 29.6589 19.9989 29.6589 20.8396C29.6589 21.6803 30.3404 22.3618 31.1811 22.3618H34.7328C35.5735 22.3618 36.255 21.6803 36.255 20.8396C36.255 19.9989 35.5735 19.3174 34.7328 19.3174Z" fill="#00E567"/>
<path d="M30.3112 14.0259H26.252C25.4113 14.0259 24.7297 14.7074 24.7297 15.5481C24.7297 16.3887 25.4113 17.0702 26.252 17.0702H30.3112C31.1519 17.0702 31.8334 16.3887 31.8334 15.5481C31.8334 14.7074 31.1519 14.0259 30.3112 14.0259Z" fill="#0149FF"/>
<path d="M25.5271 24.6089H17.3362C16.4955 24.6089 15.814 25.2904 15.814 26.1311C15.814 26.9717 16.4955 27.6532 17.3362 27.6532H25.5271C26.3678 27.6532 27.0493 26.9717 27.0493 26.1311C27.0493 25.2904 26.3678 24.6089 25.5271 24.6089Z" fill="#0149FF"/>
<path d="M20.6704 29.9004H17.3361C16.4955 29.9004 15.814 30.5819 15.814 31.4226C15.814 32.2632 16.4955 32.9447 17.3361 32.9447H20.6704C21.5111 32.9447 22.1926 32.2632 22.1926 31.4226C22.1926 30.5819 21.5111 29.9004 20.6704 29.9004Z" fill="#0149FF"/>
<path d="M30.3112 29.9004H26.252C25.4113 29.9004 24.7297 30.5819 24.7297 31.4226C24.7297 32.2632 25.4113 32.9447 26.252 32.9447H30.3112C31.1519 32.9447 31.8334 32.2632 31.8334 31.4226C31.8334 30.5819 31.1519 29.9004 30.3112 29.9004Z" fill="#FF458B"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB