web/satellite: removed old create access grant flow

Removed old flow and feature flag.

Issue:
https://github.com/storj/storj/issues/5407

Change-Id: I9dec18eb7d8c7912ead87188789466db5f59a5ca
This commit is contained in:
Vitalii 2022-12-22 18:58:38 +02:00 committed by Storj Robot
parent b2422caaef
commit af238e2ef9
23 changed files with 198 additions and 3611 deletions

View File

@ -93,7 +93,6 @@ type Config struct {
LinksharingURL string `help:"url link for linksharing requests" default:"https://link.storjshare.io" devDefault:"http://localhost:8001"`
PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"`
NewProjectDashboard bool `help:"indicates if new project dashboard should be used" default:"true"`
NewAccessGrantFlow bool `help:"indicates if new access grant flow should be used" default:"true"`
NewBillingScreen bool `help:"indicates if new billing screens should be used" default:"true"`
GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"`
OptionalSignupSuccessURL string `help:"optional url to external registration success page" default:""`
@ -467,7 +466,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
NewProjectDashboard bool
DefaultPaidStorageLimit memory.Size
DefaultPaidBandwidthLimit memory.Size
NewAccessGrantFlow bool
NewBillingScreen bool
InactivityTimerEnabled bool
InactivityTimerDuration int
@ -513,7 +511,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
data.LoginHcaptchaEnabled = server.config.Captcha.Login.Hcaptcha.Enabled
data.LoginHcaptchaSiteKey = server.config.Captcha.Login.Hcaptcha.SiteKey
data.NewProjectDashboard = server.config.NewProjectDashboard
data.NewAccessGrantFlow = server.config.NewAccessGrantFlow
data.NewBillingScreen = server.config.NewBillingScreen
data.InactivityTimerEnabled = server.config.Session.InactivityTimerEnabled
data.InactivityTimerDuration = server.config.Session.InactivityTimerDuration

View File

@ -238,9 +238,6 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
# indicates if storj native token payments system is enabled
# console.native-token-payments-enabled: false
# indicates if new access grant flow should be used
# console.new-access-grant-flow: true
# indicates if new billing screens should be used
# console.new-billing-screen: true

View File

@ -34,7 +34,6 @@
<meta name="new-project-dashboard" content="{{ .NewProjectDashboard }}">
<meta name="default-paid-storage-limit" content="{{ .DefaultPaidStorageLimit }}">
<meta name="default-paid-bandwidth-limit" content="{{ .DefaultPaidBandwidthLimit }}">
<meta name="new-access-grant-flow" content="{{ .NewAccessGrantFlow }}">
<meta name="new-billing-screen" content="{{ .NewBillingScreen }}">
<meta name="inactivity-timer-enabled" content="{{ .InactivityTimerEnabled }}">
<meta name="inactivity-timer-duration" content="{{ .InactivityTimerDuration }}">

View File

@ -1,33 +1,13 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="access-grants">
<div v-if="!isNewAccessGrantFlow" class="access-grants__title-area">
<h2 class="access-grants__title-area__title" aria-roledescription="title">Access Grants</h2>
<div v-if="accessGrantsList.length" class="access-grants__title-area__right">
<VButton
v-if="selectedAccessGrantsAmount"
:label="deleteButtonLabel"
width="203px"
height="40px"
:on-press="onDeleteClick"
:is-deletion="true"
/>
<VButton
v-else
label="Create Access Grant +"
width="203px"
height="44px"
:on-press="onCreateClick"
:is-disabled="areGrantsFetching"
/>
</div>
</div>
<div v-if="isNewAccessGrantFlow" class="access-grants__new-title-area">
<div class="access-grants__new-title-area">
<h2 class="access-grants__title-area__title" aria-roledescription="title">Access Management</h2>
<div class="access-grants__title-area__title-subtext" aria-roledescription="title">Create encryption keys to setup permissions to access your objects.</div>
</div>
<div v-if="isNewAccessGrantFlow" class="access-grants__flows-area">
<div class="access-grants__flows-area">
<div class="access-grants__flows-area__access-grant">
<div class="access-grants__flows-area__icon-container">
<AccessGrantsIcon />
@ -127,115 +107,70 @@
</div>
</div>
</div>
<div v-if="isNewAccessGrantFlow">
<div class="access-grants__header-container">
<h3 class="access-grants__header-container__title">My Accesses</h3>
<div class="access-grants__header-container__divider" />
<VHeader
class="access-header-component"
placeholder="Accesses"
:search="fetch"
style-type="access"
/>
</div>
<VLoader v-if="areGrantsFetching" width="100px" height="100px" class="grants-loader" />
<div class="access-grants-items2">
<v-table
v-if="accessGrantsList.length && !areGrantsFetching"
class="access-grants-items2__content"
items-label="access grants"
:limit="accessGrantLimit"
:total-page-count="totalPageCount"
:total-items-count="accessGrantsTotalCount"
:on-page-click-callback="onPageClick"
>
<template #head>
<th class="align-left">Name</th>
<th class="align-left">Date Created</th>
</template>
<template #body>
<AccessGrantsItem2
v-for="(grant, key) in accessGrantsList"
:key="key"
:item-data="grant"
:dropdown-key="key"
:is-dropdown-open="activeDropdown === key"
@openDropdown="openDropdown"
@deleteClick="onDeleteClick"
/>
</template>
</v-table>
<div
v-if="!accessGrantsList.length && !areGrantsFetching"
class="access-grants-items2__empty-state"
>
<span class="access-grants-items2__empty-state__text">
{{ emptyStateLabel }}
</span>
</div>
</div>
</div>
<div v-if="!isNewAccessGrantFlow">
<VLoader v-if="areGrantsFetching" width="100px" height="100px" class="grants-loader" />
<div v-if="accessGrantsList.length && !areGrantsFetching" class="access-grants-items">
<v-table
v-if="accessGrantsList.length && !areGrantsFetching"
class="access-grants-items__content"
items-label="access grants"
:selectable="true"
:limit="accessGrantLimit"
:total-page-count="totalPageCount"
:total-items-count="accessGrantsTotalCount"
:on-page-click-callback="onPageClick"
>
<template #head>
<th class="align-left">Name</th>
<th class="align-left">Date Created</th>
</template>
<template #body>
<AccessGrantsItem
v-for="(grant, key) in accessGrantsList"
:key="key"
:item-data="grant"
@accessGrantClick="toggleSelection"
@selectChange="(_) => toggleSelection(grant)"
/>
</template>
</v-table>
</div>
</div>
<div v-if="!isNewAccessGrantFlow">
<ConfirmDeletePopup
v-if="isDeleteClicked"
@close="onClearSelection"
/>
<EmptyState v-if="!accessGrantsList.length && !areGrantsFetching" />
</div>
<div v-if="isNewAccessGrantFlow">
<ConfirmDeletePopup2
v-if="isDeleteClicked"
@close="onClearSelection"
<div class="access-grants__header-container">
<h3 class="access-grants__header-container__title">My Accesses</h3>
<div class="access-grants__header-container__divider" />
<VHeader
class="access-header-component"
placeholder="Accesses"
:search="fetch"
style-type="access"
/>
</div>
<VLoader v-if="areGrantsFetching" width="100px" height="100px" class="grants-loader" />
<div class="access-grants-items">
<v-table
v-if="accessGrantsList.length && !areGrantsFetching"
class="access-grants-items__content"
items-label="access grants"
:limit="accessGrantLimit"
:total-page-count="totalPageCount"
:total-items-count="accessGrantsTotalCount"
:on-page-click-callback="onPageClick"
>
<template #head>
<th class="align-left">Name</th>
<th class="align-left">Date Created</th>
</template>
<template #body>
<AccessGrantsItem
v-for="(grant, key) in accessGrantsList"
:key="key"
:item-data="grant"
:dropdown-key="key"
:is-dropdown-open="activeDropdown === key"
@openDropdown="openDropdown"
@deleteClick="onDeleteClick"
/>
</template>
</v-table>
<div
v-if="!accessGrantsList.length && !areGrantsFetching"
class="access-grants-items__empty-state"
>
<span class="access-grants-items__empty-state__text">
{{ emptyStateLabel }}
</span>
</div>
</div>
<ConfirmDeletePopup
v-if="isDeleteClicked"
@close="onClearSelection"
/>
<router-view />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { MetaUtils } from '@/utils/meta';
import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant, AccessGrantsOrderBy } from '@/types/accessGrants';
import { SortDirection } from '@/types/common';
import { AccessGrant } from '@/types/accessGrants';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import AccessGrantsItem from '@/components/accessGrants/AccessGrantsItem.vue';
import AccessGrantsItem2 from '@/components/accessGrants/AccessGrantsItem2.vue';
import ConfirmDeletePopup from '@/components/accessGrants/ConfirmDeletePopup.vue';
import ConfirmDeletePopup2 from '@/components/accessGrants/ConfirmDeletePopup2.vue';
import EmptyState from '@/components/accessGrants/EmptyState.vue';
import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue';
import VHeader from '@/components/common/VHeader.vue';
@ -249,23 +184,18 @@ const {
FETCH,
TOGGLE_SELECTION,
CLEAR_SELECTION,
SET_SORT_BY,
SET_SORT_DIRECTION,
SET_SEARCH_QUERY,
} = ACCESS_GRANTS_ACTIONS;
// @vue/component
@Component({
components: {
AccessGrantsItem2,
AccessGrantsItem,
AccessGrantsIcon,
CLIIcon,
EmptyState,
S3Icon,
VButton,
ConfirmDeletePopup,
ConfirmDeletePopup2,
VLoader,
VHeader,
VTable,
@ -277,22 +207,9 @@ export default class AccessGrants extends Vue {
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Indicates if the access modal should be shown and what the defaulted type of access should be defaulted.
*/
private showAccessModal = false;
private modalAccessType = '';
public activeDropdown = -1;
public areGrantsFetching = true;
/**
* Indicates if navigation side bar is hidden.
*/
public get isNewAccessGrantFlow(): boolean {
const isNewAccessGrantFlow = MetaUtils.getMetaContent('new-access-grant-flow');
return isNewAccessGrantFlow === 'true';
}
/**
* Lifecycle hook after initial render where list of existing access grants is fetched.
*/
@ -304,6 +221,7 @@ export default class AccessGrants extends Vue {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
}
/**
* Lifecycle hook before component destruction.
* Clears existing access grants selection.
@ -311,6 +229,7 @@ export default class AccessGrants extends Vue {
public beforeDestroy(): void {
this.onClearSelection();
}
/**
* Toggles access grant selection.
* @param accessGrant
@ -318,6 +237,7 @@ export default class AccessGrants extends Vue {
public async toggleSelection(accessGrant: AccessGrant): Promise<void> {
await this.$store.dispatch(TOGGLE_SELECTION, accessGrant);
}
/**
* Fetches access grants page by clicked index.
* @param index
@ -329,28 +249,6 @@ export default class AccessGrants extends Vue {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
}
/**
* Used for sorting.
* @param sortBy
* @param sortDirection
*/
public async onHeaderSectionClickCallback(sortBy: AccessGrantsOrderBy, sortDirection: SortDirection): Promise<void> {
await this.$store.dispatch(SET_SORT_BY, sortBy);
await this.$store.dispatch(SET_SORT_DIRECTION, sortDirection);
try {
await this.$store.dispatch(FETCH, this.FIRST_PAGE);
} catch (error) {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
}
/**
* Starts create access grant flow.
*/
public onCreateClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant).with(RouteConfig.NameStep).path);
this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant).with(RouteConfig.NameStep).path);
}
/**
* Opens AccessGrantItem2 dropdown.
@ -372,6 +270,7 @@ export default class AccessGrants extends Vue {
await this.$store.dispatch(TOGGLE_SELECTION, grant);
this.isDeleteClicked = true;
}
/**
* Clears access grants selection.
*/
@ -379,6 +278,7 @@ export default class AccessGrants extends Vue {
this.isDeleteClicked = false;
await this.$store.dispatch(CLEAR_SELECTION);
}
/**
* Fetches Access records by name depending on search query.
*/
@ -391,9 +291,7 @@ export default class AccessGrants extends Vue {
await this.$notify.error(`Unable to fetch accesses: ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
}
public get deleteButtonLabel(): string {
return `Remove Selected (${this.selectedAccessGrantsAmount})`;
}
/**
* Returns access grants pages count from store.
*/
@ -422,13 +320,6 @@ export default class AccessGrants extends Vue {
return this.$store.state.accessGrantsModule.page.accessGrants;
}
/**
* Returns selected access grants IDs amount from store.
*/
public get selectedAccessGrantsAmount(): number {
return this.$store.state.accessGrantsModule.selectedAccessGrantsIds.length;
}
/**
* Returns search query from store.
*/
@ -606,13 +497,6 @@ export default class AccessGrants extends Vue {
&__content {
margin-top: 20px;
}
}
.access-grants-items2 {
&__content {
margin-top: 20px;
}
&__empty-state {
padding: 48px 0;

View File

@ -3,29 +3,117 @@
<template>
<table-item
:item="{ name: itemData.name, date: itemData.localDate() }"
:selectable="true"
:selected="itemData.isSelected"
:on-click="(_) => $emit('accessGrantClick', itemData)"
@selectChange="(value) => $emit('selectChange', value)"
/>
:item="itemToRender"
:on-click="onClick"
>
<th slot="options" v-click-outside="closeDropdown" class="grant-item__functional options overflow-visible" @click.stop="openDropdown">
<dots-icon />
<div v-if="isDropdownOpen" class="grant-item__functional__dropdown">
<div class="grant-item__functional__dropdown__item" @click.stop="onDeleteClick">
<delete-icon />
<p class="grant-item__functional__dropdown__item__label">Delete Access</p>
</div>
</div>
</th>
</table-item>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Component, Prop } from 'vue-property-decorator';
import DeleteIcon from '../../../static/images/objects/delete.svg';
import DotsIcon from '../../../static/images/objects/dots.svg';
import { AccessGrant } from '@/types/accessGrants';
import Resizable from '@/components/common/Resizable.vue';
import TableItem from '@/components/common/TableItem.vue';
// @vue/component
@Component({
components: {
TableItem,
DeleteIcon,
DotsIcon,
},
})
export default class AccessGrantsItem extends Vue {
export default class AccessGrantsItem extends Resizable {
@Prop({ default: new AccessGrant('', '', new Date(), '') })
private readonly itemData: AccessGrant;
@Prop({ default: () => () => {} })
public readonly onClick: () => void;
@Prop({ default: false })
public readonly isDropdownOpen: boolean;
@Prop({ default: -1 })
public readonly dropdownKey: number;
public get itemToRender(): { [key: string]: string | string[] } {
if (!this.isMobile) return { name: this.itemData.name, date: this.itemData.localDate() };
return { info: [ this.itemData.name, `Created ${this.itemData.localDate()}` ] };
}
/**
* Closes dropdown.
*/
public closeDropdown(): void {
this.$emit('openDropdown', -1);
}
/**
* Opens dropdown.
*/
public openDropdown(): void {
this.$emit('openDropdown', this.dropdownKey);
}
public async onDeleteClick(): Promise<void> {
this.$emit('deleteClick', this.itemData);
this.closeDropdown();
}
}
</script>
<style scoped lang="scss">
.grant-item {
&__functional {
padding: 0 10px;
position: relative;
cursor: pointer;
&__dropdown {
position: absolute;
top: 55px;
right: 15px;
background: #fff;
box-shadow: 0 20px 34px rgb(10 27 44 / 28%);
border-radius: 6px;
width: 255px;
z-index: 100;
overflow: hidden;
&__item {
display: flex;
align-items: center;
padding: 20px 25px;
width: calc(100% - 50px);
&__label {
margin: 0 0 0 10px;
}
&:hover {
background-color: #f4f5f7;
font-family: 'font_medium', sans-serif;
color: var(--c-blue-3);
svg :deep(path) {
fill: var(--c-blue-3);
}
}
}
}
}
}
</style>

View File

@ -1,119 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<table-item
:item="itemToRender"
:on-click="onClick"
>
<th slot="options" v-click-outside="closeDropdown" class="grant-item__functional options overflow-visible" @click.stop="openDropdown">
<dots-icon />
<div v-if="isDropdownOpen" class="grant-item__functional__dropdown">
<div class="grant-item__functional__dropdown__item" @click.stop="onDeleteClick">
<delete-icon />
<p class="grant-item__functional__dropdown__item__label">Delete Access</p>
</div>
</div>
</th>
</table-item>
</template>
<script lang="ts">
import { Component, Prop } from 'vue-property-decorator';
import DeleteIcon from '../../../static/images/objects/delete.svg';
import DotsIcon from '../../../static/images/objects/dots.svg';
import { AccessGrant } from '@/types/accessGrants';
import Resizable from '@/components/common/Resizable.vue';
import TableItem from '@/components/common/TableItem.vue';
// @vue/component
@Component({
components: {
TableItem,
DeleteIcon,
DotsIcon,
},
})
export default class AccessGrantsItem extends Resizable {
@Prop({ default: new AccessGrant('', '', new Date(), '') })
private readonly itemData: AccessGrant;
@Prop({ default: () => () => {} })
public readonly onClick: () => void;
@Prop({ default: false })
public readonly isDropdownOpen: boolean;
@Prop({ default: -1 })
public readonly dropdownKey: number;
public get itemToRender(): { [key: string]: string | string[] } {
if (!this.isMobile) return { name: this.itemData.name, date: this.itemData.localDate() };
return { info: [ this.itemData.name, `Created ${this.itemData.localDate()}` ] };
}
/**
* Closes dropdown.
*/
public closeDropdown(): void {
this.$emit('openDropdown', -1);
}
/**
* Opens dropdown.
*/
public openDropdown(): void {
this.$emit('openDropdown', this.dropdownKey);
}
public async onDeleteClick(): Promise<void> {
this.$emit('deleteClick', this.itemData);
this.closeDropdown();
}
}
</script>
<style scoped lang="scss">
.grant-item {
&__functional {
padding: 0 10px;
position: relative;
cursor: pointer;
&__dropdown {
position: absolute;
top: 55px;
right: 15px;
background: #fff;
box-shadow: 0 20px 34px rgb(10 27 44 / 28%);
border-radius: 6px;
width: 255px;
z-index: 100;
overflow: hidden;
&__item {
display: flex;
align-items: center;
padding: 20px 25px;
width: calc(100% - 50px);
&__label {
margin: 0 0 0 10px;
}
&:hover {
background-color: #f4f5f7;
font-family: 'font_medium', sans-serif;
color: var(--c-blue-3);
svg :deep(path) {
fill: var(--c-blue-3);
}
}
}
}
}
}
</style>

View File

@ -1,15 +1,12 @@
// Copyright (C) 2020 Storj Labs, Inc.
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="confirm-delete">
<div v-if="!isNewAccessGrantFlow" class="confirm-delete__container">
<h1 class="confirm-delete__container__title">Are you sure?</h1>
<div class="confirm-delete__container">
<h1 class="confirm-delete__container__title">Delete Access</h1>
<p class="confirm-delete__container__info">
When an access grant is removed, users using it will no longer have access to the buckets or data.
</p>
<p class="confirm-delete__container__list-label">
The following access grant(s) will be removed from this project:
You wont be able to access bucket(s) or object(s) related to the access:
</p>
<div class="confirm-delete__container__list">
<div
@ -24,53 +21,18 @@
</div>
</div>
</div>
<div>
<p>This action cannot be undone.</p>
</div>
<VInput
placeholder="Type the name of the access"
@setData="setConfirmedInput"
/>
<div class="confirm-delete__container__buttons-area">
<VButton
class="cancel-button"
label="Cancel"
width="50%"
height="44px"
:on-press="onCancelClick"
:is-white="true"
:is-disabled="isLoading"
/>
<VButton
label="Remove"
width="50%"
height="44px"
:on-press="onDeleteClick"
:is-disabled="isLoading"
/>
</div>
<div class="confirm-delete__container__close-cross-container" @click="onCancelClick">
<CloseCrossIcon />
</div>
</div>
<div v-if="isNewAccessGrantFlow" class="confirm-delete__container">
<div class="confirm-delete__text-container">
<h1 class="confirm-delete__container__title">Delete Access</h1>
<p class="confirm-delete__container__info-new">
You wont be able to access bucket(s) or object(s) related to this access. This action cannot be undone.
</p>
</div>
<div class="confirm-delete__container__list">
<div
v-for="accessGrant in selectedAccessGrants"
:key="accessGrant.id"
class="confirm-delete__container__list__container"
>
<div class="confirm-delete__container__list__container__item">
<p class="confirm-delete__container__list__container__item__name">
{{ accessGrant.name }}
</p>
</div>
</div>
</div>
<div class="confirm-delete__container__buttons-area">
<VButton
class="cancel-button"
label="Cancel"
width="50%"
width="70px"
height="44px"
:on-press="onCancelClick"
:is-white="true"
@ -78,13 +40,13 @@
/>
<VButton
label="Delete Access"
width="50%"
width="150px"
height="44px"
:is-solid-delete="true"
:on-press="onDeleteClick"
:is-disabled="isLoading"
:is-disabled="isLoading || confirmedInput !== selectedAccessGrants[0].name"
:is-solid-delete="true"
has-trash-icon="true"
/>
<TrashIcon class="confirm-delete__trash-icon" />
</div>
<div class="confirm-delete__container__close-cross-container" @click="onCancelClick">
<CloseCrossIcon />
@ -96,27 +58,35 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { MetaUtils } from '@/utils/meta';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant } from '@/types/accessGrants';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import TrashIcon from '@/../static/images/accessGrants/trashIcon.svg';
// @vue/component
@Component({
components: {
VButton,
VInput,
CloseCrossIcon,
TrashIcon,
},
})
export default class ConfirmDeletePopup extends Vue {
private FIRST_PAGE = 1;
private isLoading = false;
private confirmedInput = '';
private readonly FIRST_PAGE: number = 1;
/**
* sets Comfirmed Input property to the given value.
*/
public setConfirmedInput(value: string): void {
this.confirmedInput = value;
}
/**
* Deletes selected access grants, fetches updated list and closes popup.
@ -140,7 +110,7 @@ export default class ConfirmDeletePopup extends Vue {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.CONFIRM_DELETE_AG_MODAL);
}
this.$emit('reset-pagination');
this.$emit('resetPagination');
this.isLoading = false;
this.onCancelClick();
}
@ -151,13 +121,7 @@ export default class ConfirmDeletePopup extends Vue {
public onCancelClick(): void {
this.$emit('close');
}
/**
* Checks for new access grant flag
*/
public get isNewAccessGrantFlow(): boolean {
const isNewAccessGrantFlow = MetaUtils.getMetaContent('new-access-grant-flow');
return isNewAccessGrantFlow === 'true';
}
/**
* Returns list of selected access grants from store.
*/
@ -194,12 +158,11 @@ export default class ConfirmDeletePopup extends Vue {
&__container {
border-radius: 6px;
max-width: 475px;
padding: 50px 65px;
max-width: 325px;
padding: 40px 30px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
background-color: #fff;
&__title {
@ -214,9 +177,8 @@ export default class ConfirmDeletePopup extends Vue {
font-weight: normal;
font-size: 16px;
line-height: 21px;
text-align: center;
color: #000;
margin: 20px 0;
margin: 25px 0 10px;
}
&__info-new {
@ -247,17 +209,18 @@ export default class ConfirmDeletePopup extends Vue {
&__container {
&__item {
padding: 25px;
width: calc(100% - 50px);
max-width: calc(100% - 50px);
background: rgb(245 246 250 / 60%);
padding: 3px 7px;
max-width: fit-content;
background: var(--c-grey-3);
border-radius: 20px;
margin-bottom: 10px;
&__name {
font-family: 'font_medium', sans-serif;
margin: 0;
font-weight: bold;
font-size: 14px;
line-height: 19px;
font-size: 17px;
line-height: 30px;
color: #1b2533;
overflow: hidden;
text-overflow: ellipsis;
@ -268,7 +231,7 @@ export default class ConfirmDeletePopup extends Vue {
}
&__buttons-area {
width: 100%;
width: fit-content;
display: flex;
align-items: center;
margin-top: 30px;

View File

@ -1,261 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="confirm-delete">
<div class="confirm-delete__container">
<h1 class="confirm-delete__container__title">Delete Access</h1>
<p class="confirm-delete__container__info">
You wont be able to access bucket(s) or object(s) related to the access:
</p>
<div class="confirm-delete__container__list">
<div
v-for="accessGrant in selectedAccessGrants"
:key="accessGrant.id"
class="confirm-delete__container__list__container"
>
<div class="confirm-delete__container__list__container__item">
<p class="confirm-delete__container__list__container__item__name">
{{ accessGrant.name }}
</p>
</div>
</div>
</div>
<div>
<p>This action cannot be undone.</p>
</div>
<VInput
placeholder="Type the name of the access"
@setData="setConfirmedInput"
/>
<div class="confirm-delete__container__buttons-area">
<VButton
class="cancel-button"
label="Cancel"
width="70px"
height="44px"
:on-press="onCancelClick"
:is-white="true"
:is-disabled="isLoading"
/>
<VButton
label="Delete Access"
width="150px"
height="44px"
:on-press="onDeleteClick"
:is-disabled="isLoading || confirmedInput !== selectedAccessGrants[0].name"
:is-solid-delete="true"
has-trash-icon="true"
/>
</div>
<div class="confirm-delete__container__close-cross-container" @click="onCancelClick">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant } from '@/types/accessGrants';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
// @vue/component
@Component({
components: {
VButton,
VInput,
CloseCrossIcon,
},
})
export default class ConfirmDeletePopup extends Vue {
private isLoading = false;
private confirmedInput = '';
private readonly FIRST_PAGE: number = 1;
/**
* sets Comfirmed Input property to the given value.
*/
public setConfirmedInput(value: string): void {
this.confirmedInput = value;
}
/**
* Deletes selected access grants, fetches updated list and closes popup.
*/
public async onDeleteClick(): Promise<void> {
if (this.isLoading) return;
this.isLoading = true;
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE);
await this.$notify.success(`Access Grant deleted successfully`);
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.CONFIRM_DELETE_AG_MODAL);
}
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR_SELECTION);
} catch (error) {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.CONFIRM_DELETE_AG_MODAL);
}
this.$emit('resetPagination');
this.isLoading = false;
this.onCancelClick();
}
/**
* Closes popup
*/
public onCancelClick(): void {
this.$emit('close');
}
/**
* Returns list of selected access grants from store.
*/
public get selectedAccessGrants(): AccessGrant[] {
return this.$store.getters.selectedAccessGrants;
}
}
</script>
<style scoped lang="scss">
.confirm-delete {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background: rgb(27 37 51 / 75%);
display: flex;
align-items: center;
justify-content: center;
font-family: 'font_regular', sans-serif;
font-style: normal;
&__trash-icon {
position: absolute;
left: 57%;
margin-top: -3px;
}
&__text-container {
text-align: left;
}
&__container {
border-radius: 6px;
max-width: 325px;
padding: 40px 30px;
position: relative;
display: flex;
flex-direction: column;
background-color: #fff;
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 28px;
line-height: 34px;
color: #000;
}
&__info {
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #000;
margin: 25px 0 10px;
}
&__info-new {
font-weight: normal;
font-size: 16px;
line-height: 21px;
text-align: left;
color: #000;
margin: 20px 0;
}
&__list-label {
font-weight: bold;
font-size: 14px;
line-height: 18px;
color: #e30011;
font-family: 'font_medium', sans-serif;
white-space: nowrap;
margin-bottom: 30px;
}
&__list {
max-height: 255px;
overflow-y: scroll;
border-radius: 6px;
width: 100%;
&__container {
&__item {
padding: 3px 7px;
max-width: fit-content;
background: var(--c-grey-3);
border-radius: 20px;
margin-bottom: 10px;
&__name {
font-family: 'font_medium', sans-serif;
margin: 0;
font-weight: bold;
font-size: 17px;
line-height: 30px;
color: #1b2533;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
&__buttons-area {
width: fit-content;
display: flex;
align-items: center;
margin-top: 30px;
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 30px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
}
}
}
.cancel-button {
margin-right: 15px;
}
</style>

View File

@ -1,149 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="create-grant">
<div class="create-grant__container">
<ProgressBar v-if="!isProgressBarHidden" />
<router-view />
<div class="create-grant__container__close-cross-container" @click="onCloseClick">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AnalyticsHttpApi } from '@/api/analytics';
import ProgressBar from '@/components/accessGrants/ProgressBar.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
// @vue/component
@Component({
components: {
ProgressBar,
CloseCrossIcon,
},
})
export default class CreateAccessGrant extends Vue {
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Closes popup.
*/
public onCloseClick(): void {
this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR_SELECTION);
this.analytics.pageVisit(RouteConfig.AccessGrants.path);
this.$router.push(RouteConfig.AccessGrants.path);
}
/**
* Indicates if progress bar is hidden.
*/
public get isProgressBarHidden(): boolean {
return this.$route.name === RouteConfig.CLIStep.name;
}
}
</script>
<style scoped lang="scss">
::-webkit-scrollbar,
::-webkit-scrollbar-track,
::-webkit-scrollbar-thumb {
margin: 0;
width: 0;
}
.create-grant {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
background: rgb(27 37 51 / 75%);
display: flex;
align-items: center;
justify-content: center;
&__container {
background: #f5f6fa;
border-radius: 6px;
display: flex;
align-items: flex-start;
position: relative;
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 30px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
}
}
}
@media screen and (max-height: 800px) {
.create-grant {
padding: 50px 0 20px;
overflow-y: scroll;
}
}
@media screen and (max-height: 750px) {
.create-grant {
padding: 100px 0 20px;
}
}
@media screen and (max-height: 700px) {
.create-grant {
padding: 150px 0 20px;
}
}
@media screen and (max-height: 650px) {
.create-grant {
padding: 200px 0 20px;
}
}
@media screen and (max-height: 600px) {
.create-grant {
padding: 250px 0 20px;
}
}
@media screen and (max-height: 550px) {
.create-grant {
padding: 300px 0 20px;
}
}
@media screen and (max-height: 500px) {
.create-grant {
padding: 350px 0 20px;
}
}
</style>

View File

@ -1,89 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="empty-state">
<div class="empty-state__modal">
<Key />
<h4 class="empty-state__modal__heading">Create Your First Access Grant</h4>
<p class="empty-state__modal__subheading">Get started by creating an Access to interact with your Buckets</p>
<VButton
label="Create Access Grant +"
width="199px"
height="44px"
class="empty-state__modal__cta"
:on-press="onCreateClick"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { AnalyticsHttpApi } from '@/api/analytics';
import VButton from '@/components/common/VButton.vue';
import Key from '@/../static/images/accessGrants/key.svg';
// @vue/component
@Component({
components: {
Key,
VButton,
},
})
export default class EmptyState extends Vue {
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Starts create access grant flow.
*/
public onCreateClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant).with(RouteConfig.NameStep).path);
this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant).with(RouteConfig.NameStep).path);
}
}
</script>
<style scoped lang="scss">
.empty-state {
background-image: url('../../../static/images/accessGrants/access-grants-bg.png');
background-size: contain;
margin-top: 40px;
&__modal {
display: block;
margin-left: auto;
margin-right: auto;
width: 660px;
text-align: center;
background: #fff;
padding: 100px 30px;
position: relative;
top: 110px;
&__heading {
font-family: 'font_bold', sans-serif;
font-size: 28px;
font-weight: 700;
line-height: 16px;
margin-bottom: 30px;
}
&__subheading {
font-family: 'font_regular', sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 21px;
}
&__cta {
margin: 25px auto 0;
}
}
}
</style>

View File

@ -1,116 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="progress-bar">
<div class="progress-bar__item">
<div class="progress-bar__item__line" :class="{ blue: isNameStep }" />
<p class="progress-bar__item__label" :class="{ checked: isNameStep }">Name Access</p>
</div>
<div class="progress-bar__item">
<div class="progress-bar__item__line" :class="{ blue: isPermissionsStep }" />
<p class="progress-bar__item__label" :class="{ checked: isPermissionsStep }">Permissions</p>
</div>
<div class="progress-bar__item">
<div class="progress-bar__item__line" :class="{ blue: isPassphraseStep }" />
<p class="progress-bar__item__label" :class="{ checked: isPassphraseStep }">Passphrase</p>
</div>
<div class="progress-bar__item">
<div class="progress-bar__item__line" :class="{ blue: isResultStep }" />
<p class="progress-bar__item__label" :class="{ checked: isResultStep }">Access Grant</p>
</div>
<div v-if="isGatewayStep" class="progress-bar__item">
<div class="progress-bar__item__line" :class="{ blue: isGatewayStep }" />
<p class="progress-bar__item__label" :class="{ checked: isGatewayStep }">S3 Gateway</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
// @vue/component
@Component
export default class ProgressBar extends Vue {
/**
* Indicates if current route is on name step.
*/
public get isNameStep(): boolean {
return this.$route.name === RouteConfig.NameStep.name;
}
/**
* Indicates if current route is on permissions step.
*/
public get isPermissionsStep(): boolean {
return this.$route.name === RouteConfig.PermissionsStep.name;
}
/**
* Indicates if current route is on passphrase step.
*/
public get isPassphraseStep(): boolean {
return this.$route.name === RouteConfig.CreatePassphraseStep.name || this.$route.name === RouteConfig.EnterPassphraseStep.name;
}
/**
* Indicates if current route is on result step.
*/
public get isResultStep(): boolean {
return this.$route.name === RouteConfig.ResultStep.name;
}
/**
* Indicates if current route is on gateway step.
*/
public get isGatewayStep(): boolean {
return this.$route.name === RouteConfig.GatewayStep.name;
}
}
</script>
<style scoped lang="scss">
.progress-bar {
padding: 55px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
background: #f5f6fa;
border-radius: 6px 0 0 6px;
&__item {
display: flex;
align-items: center;
&__line {
width: 7px;
height: 75px;
border-radius: 40px;
background: #dcdde1;
margin-right: 20px;
}
&__label {
font-family: 'font_regular', sans-serif;
font-style: normal;
font-size: 10px;
line-height: 15px;
color: rgb(0 0 0 / 40%);
margin: 0;
white-space: nowrap;
}
}
}
.checked {
font-family: 'font_medium', sans-serif;
color: #000;
}
.blue {
background: #0068dc;
}
</style>

View File

@ -1,119 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="sort-header-container">
<div class="sort-header-container__name-item" @click="onHeaderItemClick(AccessGrantsOrderBy.NAME)">
<p class="sort-header-container__name-item__title">NAME</p>
<VerticalArrows
:is-active="areAccessGrantsSortedByName"
:direction="getSortDirection"
/>
</div>
<div class="sort-header-container__date-item" @click="onHeaderItemClick(AccessGrantsOrderBy.CREATED_AT)">
<p class="sort-header-container__date-item__title creation-date">DATE CREATED</p>
<VerticalArrows
:is-active="!areAccessGrantsSortedByName"
:direction="getSortDirection"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { AccessGrantsOrderBy, OnHeaderClickCallback } from '@/types/accessGrants';
import { SortDirection } from '@/types/common';
import VerticalArrows from '@/components/common/VerticalArrows.vue';
// @vue/component
@Component({
components: {
VerticalArrows,
},
})
export default class SortAccessGrantsHeader extends Vue {
@Prop({ default: () => () => new Promise(() => false) })
private readonly onHeaderClickCallback: OnHeaderClickCallback;
public AccessGrantsOrderBy = AccessGrantsOrderBy;
public sortBy: AccessGrantsOrderBy = AccessGrantsOrderBy.NAME;
public sortDirection: SortDirection = SortDirection.ASCENDING;
/**
* Used for arrow styling.
*/
public get getSortDirection(): SortDirection {
return this.sortDirection === SortDirection.DESCENDING ? SortDirection.ASCENDING : SortDirection.DESCENDING;
}
public get areAccessGrantsSortedByName(): boolean {
return this.sortBy === AccessGrantsOrderBy.NAME;
}
/**
* Sets sorting kind if different from current.
* If same, changes sort direction.
* @param sortBy
*/
public async onHeaderItemClick(sortBy: AccessGrantsOrderBy): Promise<void> {
if (this.sortBy !== sortBy) {
this.sortBy = sortBy;
this.sortDirection = SortDirection.ASCENDING;
await this.onHeaderClickCallback(this.sortBy, this.sortDirection);
return;
}
this.sortDirection = this.sortDirection === SortDirection.DESCENDING ?
SortDirection.ASCENDING
: SortDirection.DESCENDING;
await this.onHeaderClickCallback(this.sortBy, this.sortDirection);
}
}
</script>
<style scoped lang="scss">
.sort-header-container {
display: flex;
width: calc(100% - 32px);
height: 40px;
background-color: #fff;
margin-top: 31px;
padding: 16px 16px 0;
border-radius: 8px 8px 0 0;
&__name-item,
&__date-item {
width: 60%;
display: flex;
align-items: center;
margin: 0;
cursor: pointer;
&__title {
font-family: 'font_medium', sans-serif;
font-size: 16px;
margin: 0 0 0 23px;
color: #2a2a32;
}
.creation-date {
margin-left: 0;
}
}
&__date-item {
width: 40%;
&__title {
margin: 0;
}
}
}
</style>

View File

@ -1,120 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="sort-header-container">
<div class="sort-header-container__name-item" @click="onHeaderItemClick(AccessGrantsOrderBy.NAME)">
<p class="sort-header-container__name-item__title">NAME</p>
<VerticalArrows
:is-active="areAccessGrantsSortedByName"
:direction="getSortDirection"
/>
</div>
<div class="sort-header-container__date-item" @click="onHeaderItemClick(AccessGrantsOrderBy.CREATED_AT)">
<p class="sort-header-container__date-item__title creation-date">DATE CREATED</p>
<VerticalArrows
:is-active="!areAccessGrantsSortedByName"
:direction="getSortDirection"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { AccessGrantsOrderBy, OnHeaderClickCallback } from '@/types/accessGrants';
import { SortDirection } from '@/types/common';
import VerticalArrows from '@/components/common/VerticalArrows.vue';
// @vue/component
@Component({
components: {
VerticalArrows,
},
})
export default class SortAccessGrantsHeader extends Vue {
@Prop({ default: () => new Promise(() => false) })
private readonly onHeaderClickCallback: OnHeaderClickCallback;
public AccessGrantsOrderBy = AccessGrantsOrderBy;
public sortBy: AccessGrantsOrderBy = AccessGrantsOrderBy.NAME;
public sortDirection: SortDirection = SortDirection.ASCENDING;
/**
* Used for arrow styling.
*/
public get getSortDirection(): SortDirection {
return this.sortDirection === SortDirection.DESCENDING ? SortDirection.ASCENDING : SortDirection.DESCENDING;
}
public get areAccessGrantsSortedByName(): boolean {
return this.sortBy === AccessGrantsOrderBy.NAME;
}
/**
* Sets sorting kind if different from current.
* If same, changes sort direction.
* @param sortBy
*/
public async onHeaderItemClick(sortBy: AccessGrantsOrderBy): Promise<void> {
if (this.sortBy !== sortBy) {
this.sortBy = sortBy;
this.sortDirection = SortDirection.ASCENDING;
await this.onHeaderClickCallback(this.sortBy, this.sortDirection);
return;
}
this.sortDirection = this.sortDirection === SortDirection.DESCENDING ?
SortDirection.ASCENDING
: SortDirection.DESCENDING;
await this.onHeaderClickCallback(this.sortBy, this.sortDirection);
}
}
</script>
<style scoped lang="scss">
.sort-header-container {
display: flex;
width: calc(100% - 32px);
height: 40px;
background-color: #fff;
margin-top: 31px;
padding: 16px 16px 0;
border-radius: 8px 8px 0 0;
border: 1px solid #e5e7eb;
border-bottom: 0;
&__name-item,
&__date-item {
width: 50%;
display: flex;
align-items: center;
margin: 0;
cursor: pointer;
&__title {
font-family: 'font_medium', sans-serif;
font-size: 16px;
margin: 0 0 0 23px;
color: #2a2a32;
}
.creation-date {
margin-left: 0;
}
}
&__date-item {
&__title {
margin: 0;
}
}
}
</style>

View File

@ -1,257 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="cli-container" :class="{ 'border-radius': isOnboardingTour }">
<BackIcon class="cli-container__back-icon" @click="onBackClick" />
<h1 class="cli-container__title">Create Access Grant in CLI</h1>
<p class="cli-container__sub-title">
Run the 'setup' command in the uplink CLI and input the satellite address and token below when prompted to generate your access grant.
</p>
<div class="cli-container__token-area">
<p class="cli-container__token-area__label">Satellite Address</p>
<div class="cli-container__token-area__container">
<p ref="addressContainer" class="cli-container__token-area__container__token" @click="selectAddress">{{ satelliteAddress }}</p>
<VButton
class="cli-container__token-area__container__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyAddressClick"
/>
</div>
<p class="cli-container__token-area__label">API Key</p>
<div class="cli-container__token-area__container">
<p class="cli-container__token-area__container__token">{{ restrictedKey }}</p>
<VButton
class="cli-container__token-area__container__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyTokenClick"
/>
</div>
</div>
<VButton
class="cli-container__done-button"
label="Done"
width="100%"
height="48px"
:on-press="onDoneClick"
/>
<a
class="cli-container__docs-link"
href="https://docs.storj.io/getting-started/generate-access-grants-and-tokens/generate-a-token"
target="_blank"
rel="noopener noreferrer"
>
Visit the Docs
</a>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { MetaUtils } from '@/utils/meta';
import { AnalyticsHttpApi } from '@/api/analytics';
import VButton from '@/components/common/VButton.vue';
import BackIcon from '@/../static/images/accessGrants/back.svg';
// @vue/component
@Component({
components: {
BackIcon,
VButton,
},
})
export default class CLIStep extends Vue {
public key = '';
public restrictedKey = '';
public satelliteAddress: string = MetaUtils.getMetaContent('satellite-nodeurl');
public $refs!: {
addressContainer: HTMLElement;
};
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Lifecycle hook after initial render.
* Sets local key from props value.
*/
public mounted(): void {
if (!this.$route.params.key && !this.$route.params.restrictedKey) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
return;
}
this.key = this.$route.params.key;
this.restrictedKey = this.$route.params.restrictedKey;
}
/**
* Holds on back button click logic.
* Redirects to previous step.
*/
public onBackClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).name,
params: {
key: this.key,
},
});
}
/**
* Holds on done button click logic.
* Redirects to upload step.
*/
public onDoneClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.path);
this.isOnboardingTour ? this.$router.push(RouteConfig.ProjectDashboard.path) : this.$router.push(RouteConfig.AccessGrants.path);
}
/**
* Holds selecting address logic for click event.
*/
public selectAddress(): void {
const range: Range = document.createRange();
const selection: Selection | null = window.getSelection();
range.selectNodeContents(this.$refs.addressContainer);
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
}
/**
* Holds on copy button click logic.
* Copies satellite address to clipboard.
*/
public onCopyAddressClick(): void {
this.$copyText(this.satelliteAddress);
this.$notify.success('Satellite address was copied successfully');
}
/**
* Holds on copy button click logic.
* Copies token to clipboard.
*/
public onCopyTokenClick(): void {
this.$copyText(this.restrictedKey);
this.$notify.success('Token was copied successfully');
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>
<style scoped lang="scss">
.cli-container {
height: calc(100% - 60px);
max-width: 485px;
padding: 30px 65px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background-color: #fff;
border-radius: 6px;
&__back-icon {
position: absolute;
top: 40px;
left: 40px;
cursor: pointer;
}
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 10px;
}
&__sub-title {
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #000;
text-align: center;
margin: 0 0 50px;
}
&__token-area {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
margin-bottom: 30px;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0 0 5px;
}
&__container {
display: flex;
align-items: center;
padding: 10px 10px 10px 20px;
width: calc(100% - 30px);
border: 1px solid rgb(56 75 101 / 40%);
border-radius: 6px;
margin-bottom: 20px;
&__token {
font-size: 16px;
line-height: 21px;
color: #384b65;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin: 0;
}
&__button {
min-width: 66px;
min-height: 30px;
margin-left: 10px;
}
}
}
&__docs-link {
font-family: 'font_medium', sans-serif;
font-weight: 600;
font-size: 16px;
line-height: 23px;
color: #0068dc;
margin: 16px 0;
}
}
.border-radius {
border-radius: 6px;
}
</style>

View File

@ -1,500 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="create-passphrase">
<BackIcon class="create-passphrase__back-icon" @click="onBackClick" />
<div class="create-passphrase__container">
<h1 class="create-passphrase__container__title">Encryption Passphrase</h1>
<div class="create-passphrase__container__choosing">
<p class="create-passphrase__container__choosing__label">Passphrase</p>
<div class="create-passphrase__container__choosing__right">
<p
class="create-passphrase__container__choosing__right__option left-option"
:class="{ active: isGenerateState }"
@click="onChooseGenerate"
>
Generate Phrase
</p>
<p
class="create-passphrase__container__choosing__right__option"
:class="{ active: isEnterState }"
@click="onChooseCreate"
>
Enter Phrase
</p>
</div>
</div>
<div v-if="isEnterState" class="create-passphrase__container__enter-passphrase-box">
<div class="create-passphrase__container__enter-passphrase-box__header">
<GreenWarningIcon />
<h2 class="create-passphrase__container__enter-passphrase-box__header__label">Enter an Existing Passphrase</h2>
</div>
<p class="create-passphrase__container__enter-passphrase-box__message">
if you already have an encryption passphrase, enter your encryption passphrase here.
</p>
</div>
<div class="create-passphrase__container__value-area">
<div v-if="isGenerateState" class="create-passphrase__container__value-area__mnemonic">
<p class="create-passphrase__container__value-area__mnemonic__value">{{ passphrase }}</p>
<VButton
class="create-passphrase__container__value-area__mnemonic__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyClick"
/>
</div>
<div v-else class="create-passphrase__container__value-area__password">
<VInput
placeholder="Enter encryption passphrase here"
:error="errorMessage"
@setData="onChangePassphrase"
/>
</div>
</div>
<div v-if="isGenerateState" class="create-passphrase__container__warning">
<h2 class="create-passphrase__container__warning__title">Save Your Encryption Passphrase</h2>
<p class="create-passphrase__container__warning__message">
Youll need this passphrase to access data in the future. This is the only time it will be displayed.
Be sure to write it down.
</p>
<label class="create-passphrase__container__warning__check-area" :class="{ error: isError }" for="pass-checkbox">
<input
id="pass-checkbox"
v-model="isChecked"
class="create-passphrase__container__warning__check-area__checkbox"
type="checkbox"
@change="isError = false"
>
Yes, I wrote this down or saved it somewhere.
</label>
</div>
<VButton
class="create-passphrase__container__next-button"
label="Next"
width="100%"
height="48px"
:on-press="onNextClick"
:is-disabled="isButtonDisabled"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { generateMnemonic } from 'bip39';
import { RouteConfig } from '@/router';
import { MetaUtils } from '@/utils/meta';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue';
import BackIcon from '@/../static/images/accessGrants/back.svg';
import GreenWarningIcon from '@/../static/images/accessGrants/greenWarning.svg';
// @vue/component
@Component({
components: {
BackIcon,
GreenWarningIcon,
VButton,
VInput,
},
})
export default class CreatePassphraseStep extends Vue {
private key = '';
private restrictedKey = '';
private access = '';
private worker: Worker;
private isLoading = true;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
public isGenerateState = true;
public isEnterState = false;
public isChecked = false;
public isError = false;
public passphrase = '';
public errorMessage = '';
/**
* Lifecycle hook after initial render.
* Sets local key from props value.
*/
public async mounted(): Promise<void> {
if (!this.$route.params.key && !this.$route.params.restrictedKey) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
await this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
return;
}
this.key = this.$route.params.key;
this.restrictedKey = this.$route.params.restrictedKey;
this.setWorker();
this.passphrase = generateMnemonic();
this.isLoading = false;
}
/**
* Sets local worker with worker instantiated in store.
* Also sets worker's onmessage and onerror logic.
*/
public setWorker(): void {
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
this.$notify.error(error.message, null);
};
}
/**
* Sets passphrase from child component.
*/
public setPassphrase(passphrase: string): void {
this.passphrase = passphrase;
}
/**
* Holds on next button click logic.
* Generates access grant and redirects to next step.
*/
public async onNextClick(): Promise<void> {
if (!this.passphrase) {
this.errorMessage = 'Passphrase can\'t be empty';
return;
}
if (!this.isChecked && this.isGenerateState) {
this.isError = true;
return;
}
if (this.isLoading) return;
this.isLoading = true;
await this.analytics.eventTriggered(AnalyticsEvent.PASSPHRASE_CREATED);
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id);
this.worker.postMessage({
'type': 'GenerateAccess',
'apiKey': this.restrictedKey,
'passphrase': this.passphrase,
'salt': salt,
'satelliteNodeURL': satelliteNodeURL,
});
const accessEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (accessEvent.data.error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(accessEvent.data.error, null);
this.isLoading = false;
return;
}
this.access = accessEvent.data.value;
await this.$notify.success('Access Grant was generated successfully');
this.isLoading = false;
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.ResultStep)).path);
await this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.ResultStep)).name,
params: {
access: this.access,
key: this.key,
restrictedKey: this.restrictedKey,
},
});
}
/**
* Changes state to generate passphrase.
*/
public onChooseGenerate(): void {
if (this.passphrase && this.isGenerateState) return;
this.passphrase = generateMnemonic();
this.isEnterState = false;
this.isGenerateState = true;
}
/**
* Changes state to create passphrase.
*/
public onChooseCreate(): void {
if (this.passphrase && this.isEnterState) return;
this.errorMessage = '';
this.passphrase = '';
this.isEnterState = true;
this.isGenerateState = false;
}
/**
* Holds on copy button click logic.
* Copies passphrase to clipboard.
*/
public onCopyClick(): void {
this.$copyText(this.passphrase);
this.$notify.success('Passphrase was copied successfully');
}
/**
* Changes passphrase data from input value.
* @param value
*/
public onChangePassphrase(value: string): void {
this.passphrase = value.trim();
this.errorMessage = '';
}
/**
* Indicates if button is disabled.
*/
public get isButtonDisabled(): boolean {
return this.isLoading || !this.passphrase || (!this.isChecked && this.isGenerateState);
}
/**
* Holds on back button click logic.
* Redirects to previous step.
*/
public onBackClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).name,
params: {
key: this.key,
},
});
}
/**
* Indicates if current route is onboarding tour.
*/
private get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>
<style scoped lang="scss">
.create-passphrase {
position: relative;
&__back-icon {
position: absolute;
top: 30px;
left: 65px;
cursor: pointer;
}
&__container {
padding: 25px 50px;
max-width: 515px;
min-width: 515px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
background-color: #fff;
border-radius: 6px;
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 30px;
}
&__enter-passphrase-box {
padding: 20px;
background: #f9fffc;
border: 1px solid #1a9666;
border-radius: 9px;
&__header {
display: flex;
align-items: center;
margin-bottom: 10px;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 0 0 0 10px;
}
}
&__message {
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 0;
}
}
&__warning {
display: flex;
flex-direction: column;
padding: 20px;
width: calc(100% - 40px);
margin: 35px 0;
background: #fff;
border: 1px solid #e6e9ef;
border-radius: 9px;
&__title {
width: 100%;
text-align: center;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 0 0 0 15px;
}
&__message {
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 10px 0 0;
text-align: center;
}
&__check-area {
margin-top: 27px;
font-size: 14px;
line-height: 19px;
color: #1b2533;
display: flex;
justify-content: center;
align-items: center;
&__checkbox {
margin: 0 10px 0 0;
}
}
}
&__choosing {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 25px;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0;
}
&__right {
display: flex;
align-items: center;
&__option {
font-size: 14px;
line-height: 17px;
color: #768394;
margin: 0;
cursor: pointer;
border-bottom: 3px solid #fff;
}
}
}
&__value-area {
width: 100%;
display: flex;
align-items: flex-start;
&__mnemonic {
display: flex;
background: #f5f6fa;
border-radius: 9px;
padding: 10px;
width: calc(100% - 20px);
&__value {
font-family: 'Source Code Pro', sans-serif;
font-size: 14px;
line-height: 25px;
color: #384b65;
word-break: break-word;
margin: 0;
word-spacing: 8px;
}
&__button {
margin-left: 10px;
min-width: 66px;
min-height: 30px;
}
}
&__password {
width: 100%;
margin: 10px 0 20px;
}
}
}
}
.left-option {
margin-right: 15px;
}
.active {
font-family: 'font_medium', sans-serif;
color: #0068dc;
border-bottom: 3px solid #0068dc;
}
.error {
color: red;
}
:deep(.label-container__main) {
margin-bottom: 10px;
}
:deep(.label-container__main__label) {
margin: 0;
font-size: 14px;
line-height: 19px;
color: #7c8794;
font-family: 'font_bold', sans-serif;
}
:deep(.label-container__main__error) {
margin: 0 0 0 10px;
font-size: 14px;
line-height: 19px;
}
</style>

View File

@ -1,215 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="enter-passphrase">
<BackIcon class="enter-passphrase__back-icon" @click="onBackClick" />
<h1 class="enter-passphrase__title">Enter Encryption Passphrase</h1>
<p class="enter-passphrase__sub-title">Enter the passphrase you most recently generated for Access Grants</p>
<VInput
label="Encryption Passphrase"
placeholder="Enter your passphrase here"
:error="errorMessage"
@setData="onChangePassphrase"
/>
<VButton
class="enter-passphrase__next-button"
label="Next"
width="100%"
height="48px"
:on-press="onNextClick"
:is-disabled="isLoading"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { MetaUtils } from '@/utils/meta';
import { AnalyticsHttpApi } from '@/api/analytics';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import VInput from '@/components/common/VInput.vue';
import VButton from '@/components/common/VButton.vue';
import BackIcon from '@/../static/images/accessGrants/back.svg';
// @vue/component
@Component({
components: {
VInput,
VButton,
BackIcon,
},
})
export default class EnterPassphraseStep extends Vue {
private key = '';
private restrictedKey = '';
private access = '';
private worker: Worker;
private isLoading = true;
public passphrase = '';
public errorMessage = '';
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Lifecycle hook after initial render.
* Sets local key from props value.
*/
public async mounted(): Promise<void> {
if (!this.$route.params.key && !this.$route.params.restrictedKey) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
await this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
return;
}
this.key = this.$route.params.key;
this.restrictedKey = this.$route.params.restrictedKey;
this.setWorker();
this.isLoading = false;
}
/**
* Changes passphrase data from input value.
* @param value
*/
public onChangePassphrase(value: string): void {
this.passphrase = value.trim();
this.errorMessage = '';
}
/**
* Holds on next button click logic.
* Generates access grant and redirects to next step.
*/
public async onNextClick(): Promise<void> {
if (!this.passphrase) {
this.errorMessage = 'Passphrase can`t be empty';
return;
}
this.isLoading = true;
const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl');
const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id);
this.worker.postMessage({
'type': 'GenerateAccess',
'apiKey': this.restrictedKey,
'passphrase': this.passphrase,
'salt': salt,
'satelliteNodeURL': satelliteNodeURL,
});
const accessEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (accessEvent.data.error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(accessEvent.data.error, null);
this.isLoading = false;
return;
}
this.access = accessEvent.data.value;
await this.$notify.success('Access Grant was generated successfully');
this.isLoading = false;
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.ResultStep)).path);
await this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.ResultStep)).name,
params: {
access: this.access,
key: this.key,
restrictedKey: this.restrictedKey,
},
});
}
/**
* Sets local worker with worker instantiated in store.
* Also sets worker's onmessage and onerror logic.
*/
public setWorker(): void {
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
this.$notify.error(error.message, null);
};
}
/**
* Holds on back button click logic.
* Redirects to previous step.
*/
public onBackClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).name,
params: {
key: this.key,
},
});
}
}
</script>
<style scoped lang="scss">
.enter-passphrase {
height: calc(100% - 60px);
padding: 30px 65px;
max-width: 475px;
min-width: 475px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background-color: #fff;
border-radius: 0 6px 6px 0;
&__back-icon {
position: absolute;
top: 30px;
left: 65px;
cursor: pointer;
}
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 30px;
}
&__sub-title {
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #000;
text-align: center;
margin: 0 0 75px;
max-width: 340px;
}
&__next-button {
margin-top: 93px;
}
}
.border-radius {
border-radius: 6px;
}
</style>

View File

@ -1,383 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="gateway" :class="{ 'border-radius': isOnboardingTour }">
<BackIcon class="gateway__back-icon" @click="onBackClick" />
<h1 class="gateway__title">S3 Gateway</h1>
<div class="gateway__container">
<h3 class="gateway__container__title">
Generate S3 Gateway Credentials
</h3>
<p class="gateway__container__disclaimer">
By generating gateway credentials, you are opting in to Server-side encryption
</p>
<VButton
v-if="!areKeysVisible"
class="gateway__container__button"
label="Generate Credentials"
width="calc(100% - 4px)"
height="48px"
:is-blue-white="true"
:on-press="onGenerateCredentialsClick"
:is-disabled="isLoading"
/>
<div v-else class="gateway__container__keys-area">
<div class="gateway__container__keys-area__label-area">
<h3 class="gateway__container__keys-area__label-area__label">Access Key</h3>
<VInfo class="gateway__container__keys-area__label-area__info-button">
<template #icon>
<InfoIcon />
</template>
<template #message>
<p class="gateway__container__keys-area__label-area__info-button__message">
The access key ID uniquely identifies your account.
</p>
</template>
</VInfo>
</div>
<div class="gateway__container__keys-area__key">
<p class="gateway__container__keys-area__key__value">{{ gatewayCredentials.accessKeyId }}</p>
<VButton
class="gateway__container__keys-area__key__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyAccessClick"
/>
</div>
<div class="gateway__container__keys-area__label-area">
<h3 class="gateway__container__keys-area__label-area__label">Secret Key</h3>
<VInfo class="gateway__container__keys-area__label-area__info-button">
<template #icon>
<InfoIcon />
</template>
<template #message>
<p class="gateway__container__keys-area__label-area__info-button__message">
Secret access keys areas the name impliessecrets, like your password.
</p>
</template>
</VInfo>
</div>
<div class="gateway__container__keys-area__key">
<p class="gateway__container__keys-area__key__value">{{ gatewayCredentials.secretKey }}</p>
<VButton
class="gateway__container__keys-area__key__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopySecretClick"
/>
</div>
<div class="gateway__container__keys-area__label-area">
<h3 class="gateway__container__keys-area__label-area__label">End Point</h3>
<VInfo class="gateway__container__keys-area__label-area__info-button">
<template #icon>
<InfoIcon />
</template>
<template #message>
<p class="gateway__container__keys-area__label-area__info-button__message">
The service to which you want to establish the connection.
</p>
</template>
</VInfo>
</div>
<div class="gateway__container__keys-area__key">
<p class="gateway__container__keys-area__key__value">{{ gatewayCredentials.endpoint }}</p>
<VButton
class="gateway__container__keys-area__key__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyEndpointClick"
/>
</div>
</div>
</div>
<VButton
label="Done"
width="100%"
height="48px"
:on-press="onDoneClick"
:is-disabled="!gatewayCredentials.accessKeyId"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { AnalyticsHttpApi } from '@/api/analytics';
import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { EdgeCredentials } from '@/types/accessGrants';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import VInfo from '@/components/common/VInfo.vue';
import VButton from '@/components/common/VButton.vue';
import InfoIcon from '@/../static/images/accessGrants/info.svg';
import BackIcon from '@/../static/images/accessGrants/back.svg';
// @vue/component
@Component({
components: {
VButton,
VInfo,
BackIcon,
InfoIcon,
},
})
export default class GatewayStep extends Vue {
private key = '';
private restrictedKey = '';
private access = '';
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
public areKeysVisible = false;
public isLoading = false;
/**
* Lifecycle hook after initial render.
* Sets local access from props value.
*/
public mounted(): void {
if (!this.$route.params.access && !this.$route.params.key && !this.$route.params.resctrictedKey) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
return;
}
this.access = this.$route.params.access;
this.key = this.$route.params.key;
this.restrictedKey = this.$route.params.restrictedKey;
}
/**
* Holds on copy access key button click logic.
* Copies key to clipboard.
*/
public onCopyAccessClick(): void {
this.$copyText(this.gatewayCredentials.accessKeyId);
this.$notify.success('Key was copied successfully');
}
/**
* Holds on copy secret key button click logic.
* Copies secret to clipboard.
*/
public onCopySecretClick(): void {
this.$copyText(this.gatewayCredentials.secretKey);
this.$notify.success('Secret was copied successfully');
}
/**
* Holds on copy endpoint button click logic.
* Copies endpoint to clipboard.
*/
public onCopyEndpointClick(): void {
this.$copyText(this.gatewayCredentials.endpoint);
this.$notify.success('Endpoint was copied successfully');
}
/**
* Holds on back button click logic.
* Redirects to previous step.
*/
public onBackClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.ResultStep)).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.ResultStep)).name,
params: {
access: this.access,
key: this.key,
restrictedKey: this.restrictedKey,
},
});
}
/**
* Holds on done button click logic.
* Proceed to upload data step.
*/
public onDoneClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.path);
this.isOnboardingTour ? this.$router.push(RouteConfig.ProjectDashboard.path) : this.$router.push(RouteConfig.AccessGrants.path);
}
/**
* Holds on generate credentials button click logic.
* Generates gateway credentials.
*/
public async onGenerateCredentialsClick(): Promise<void> {
if (this.isLoading) return;
this.isLoading = true;
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant: this.access });
await this.$notify.success('Gateway credentials were generated successfully');
await this.analytics.eventTriggered(AnalyticsEvent.GATEWAY_CREDENTIALS_CREATED);
this.areKeysVisible = true;
} catch (error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(error.message, null);
}
this.isLoading = false;
}
/**
* Returns generated gateway credentials from store.
*/
public get gatewayCredentials(): EdgeCredentials {
return this.$store.state.accessGrantsModule.gatewayCredentials;
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>
<style scoped lang="scss">
.gateway {
height: calc(100% - 60px);
padding: 30px 65px;
max-width: 475px;
min-width: 475px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background-color: #fff;
border-radius: 0 6px 6px 0;
&__back-icon {
position: absolute;
top: 30px;
left: 65px;
cursor: pointer;
}
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0;
}
&__container {
background: #f5f6fa;
border-radius: 6px;
padding: 50px;
margin: 55px 0 40px;
width: calc(100% - 100px);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 25px;
text-align: center;
}
&__disclaimer {
font-size: 16px;
line-height: 28px;
color: #000;
margin: 0 0 25px;
text-align: center;
}
&__keys-area {
display: flex;
flex-direction: column;
align-items: flex-start;
&__label-area {
display: flex;
align-items: center;
margin: 20px 0 10px;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0;
}
&__info-button {
max-height: 20px;
cursor: pointer;
margin-left: 10px;
&:hover {
.ag-info-rect {
fill: #fff;
}
.ag-info-path {
fill: #2683ff;
}
}
&__message {
color: #586c86;
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 18px;
}
}
}
&__key {
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 9px;
padding: 10px;
width: calc(100% - 20px);
max-width: calc(100% - 20px);
border: 1px solid rgb(56 75 101 / 40%);
background-color: #fff;
&__value {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin: 0;
}
&__button {
min-width: 66px;
min-height: 30px;
margin-left: 10px;
}
}
}
}
}
.border-radius {
border-radius: 6px;
}
:deep(.info__box__message) {
min-width: 300px;
}
</style>

View File

@ -1,201 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="name-step" :class="{ 'border-radius': isOnboardingTour }">
<h1 class="name-step__title" aria-roledescription="name-ag-title">Name Your Access Grant</h1>
<p class="name-step__sub-title">Enter a name for your new Access grant to get started.</p>
<VInput
label="Access Grant Name"
placeholder="Enter a name here..."
:error="errorMessage"
@setData="onChangeName"
/>
<div class="name-step__buttons-area">
<VButton
v-if="!isOnboardingTour"
class="cancel-button"
label="Cancel"
width="50%"
height="48px"
:on-press="onCancelClick"
:is-white="true"
:is-disabled="isLoading"
/>
<VButton
label="Next"
width="50%"
height="48px"
:on-press="onNextClick"
:is-disabled="isLoading"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { AccessGrant } from '@/types/accessGrants';
import { AnalyticsHttpApi } from '@/api/analytics';
import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue';
// @vue/component
@Component({
components: {
VInput,
VButton,
},
})
export default class NameStep extends Vue {
private name = '';
private errorMessage = '';
private isLoading = false;
private key = '';
private readonly FIRST_PAGE = 1;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Changes name data from input value.
* @param value
*/
public onChangeName(value: string): void {
this.name = value.trim();
this.errorMessage = '';
}
/**
* Holds on cancel button click logic.
*/
public onCancelClick(): void {
this.onChangeName('');
this.analytics.pageVisit(RouteConfig.AccessGrants.path);
this.$router.push(RouteConfig.AccessGrants.path);
}
/**
* Holds on next button click logic.
* Creates AccessGrant common entity.
*/
public async onNextClick(): Promise<void> {
if (this.isLoading) {
return;
}
if (!this.name) {
this.errorMessage = 'Access Grant name can\'t be empty';
return;
}
this.isLoading = true;
// Check if at least one project exists.
// Used like backwards compatibility for the old accounts without any project.
if (this.$store.getters.projects.length === 0) {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.CREATE_DEFAULT_PROJECT);
} catch (error) {
this.isLoading = false;
return;
}
}
let createdAccessGrant: AccessGrant;
try {
createdAccessGrant = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, this.name);
} catch (error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(error.message, null);
this.isLoading = false;
return;
}
this.key = createdAccessGrant.secret;
this.name = '';
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`, null);
this.isLoading = false;
}
this.isLoading = false;
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).path);
await this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).name,
params: {
key: this.key,
},
});
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>
<style scoped lang="scss">
.name-step {
height: calc(100% - 60px);
padding: 30px 65px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
background-color: #fff;
border-radius: 0 6px 6px 0;
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 10px;
}
&__sub-title {
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #000;
text-align: center;
margin: 0 0 80px;
}
&__buttons-area {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 130px;
}
}
.cancel-button {
margin-right: 15px;
}
.border-radius {
border-radius: 6px;
}
</style>

View File

@ -1,434 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="permissions" :class="{ 'border-radius': isOnboardingTour }">
<BackIcon class="permissions__back-icon" @click="onBackClick" />
<h1 class="permissions__title">Access Permissions</h1>
<p class="permissions__sub-title">
Assign permissions to this Access Grant.
</p>
<div class="permissions__content">
<div class="permissions__content__left">
<div class="permissions__content__left__item">
<input id="download" v-model="isDownload" type="checkbox" name="download" :checked="isDownload">
<label class="permissions__content__left__item__label" for="download">Download</label>
</div>
<div class="permissions__content__left__item">
<input id="upload" v-model="isUpload" type="checkbox" name="upload" :checked="isUpload">
<label class="permissions__content__left__item__label" for="upload">Upload</label>
</div>
<div class="permissions__content__left__item">
<input id="list" v-model="isList" type="checkbox" name="list" :checked="isList">
<label class="permissions__content__left__item__label" for="list">List</label>
</div>
<div class="permissions__content__left__item">
<input id="delete" v-model="isDelete" type="checkbox" name="delete" :checked="isDelete">
<label class="permissions__content__left__item__label" for="delete">Delete</label>
</div>
</div>
<div class="permissions__content__right">
<div class="permissions__content__right__duration-select">
<p class="permissions__content__right__duration-select__label">Duration</p>
<DurationSelection />
</div>
<div class="permissions__content__right__buckets-select">
<p class="permissions__content__right__buckets-select__label">Buckets</p>
<VLoader v-if="areBucketNamesFetching" width="50px" height="50px" />
<BucketsSelection v-else />
</div>
<div class="permissions__content__right__bucket-bullets">
<div
v-for="(name, index) in selectedBucketNames"
:key="index"
class="permissions__content__right__bucket-bullets__container"
>
<BucketNameBullet :name="name" />
</div>
</div>
</div>
</div>
<VButton
class="permissions__button"
label="Continue in Browser"
width="100%"
height="48px"
:on-press="onContinueInBrowserClick"
:is-disabled="isLoading || !isAccessGrantsWebWorkerReady || areBucketNamesFetching"
/>
<p
class="permissions__cli-link"
:class="{ disabled: !isAccessGrantsWebWorkerReady || isLoading || areBucketNamesFetching }"
@click.stop="onContinueInCLIClick"
>
Continue in CLI
</p>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { DurationPermission } from '@/types/accessGrants';
import { AnalyticsHttpApi } from '@/api/analytics';
import BucketNameBullet from '@/components/accessGrants/permissions/BucketNameBullet.vue';
import BucketsSelection from '@/components/accessGrants/permissions/BucketsSelection.vue';
import DurationSelection from '@/components/accessGrants/permissions/DurationSelection.vue';
import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue';
import BackIcon from '@/../static/images/accessGrants/back.svg';
// @vue/component
@Component({
components: {
BackIcon,
BucketsSelection,
BucketNameBullet,
DurationSelection,
VButton,
VLoader,
},
})
export default class PermissionsStep extends Vue {
private key = '';
private restrictedKey = '';
private worker: Worker;
public isLoading = true;
public isDownload = true;
public isUpload = true;
public isList = true;
public isDelete = true;
public areBucketNamesFetching = true;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Lifecycle hook after initial render.
* Sets local key from props value.
* Initializes web worker's onmessage functionality.
*/
public async mounted(): Promise<void> {
if (!this.$route.params.key) {
this.onBackClick();
return;
}
this.key = this.$route.params.key;
this.setWorker();
try {
await this.$store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES);
this.areBucketNamesFetching = false;
} catch (error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(`Unable to fetch all bucket names. ${error.message}`, null);
}
this.isLoading = false;
}
/**
* Holds on back button click logic.
* Redirects to previous step.
*/
public onBackClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
}
/**
* Sets local worker with worker instantiated in store.
* Also sets worker's onmessage and onerror logic.
*/
public setWorker(): void {
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
this.$notify.error(error.message, null);
};
}
/**
* Holds on continue in CLI button click logic.
*/
public async onContinueInCLIClick(): Promise<void> {
if (this.isLoading || !this.isAccessGrantsWebWorkerReady) return;
this.isLoading = true;
try {
await this.setPermissions();
} catch (error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(error.message, null);
this.isLoading = false;
return;
}
this.isLoading = false;
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.CLIStep)).path);
await this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.CLIStep)).name,
params: {
key: this.key,
restrictedKey: this.restrictedKey,
},
});
}
/**
* Holds on continue in browser button click logic.
*/
public async onContinueInBrowserClick(): Promise<void> {
if (this.isLoading || !this.isAccessGrantsWebWorkerReady) return;
this.isLoading = true;
try {
await this.setPermissions();
} catch (error) {
// we pass null because we don't use this flow anymore. It will be removed entirely soon.
await this.$notify.error(error.message, null);
this.isLoading = false;
return;
}
this.isLoading = false;
if (this.accessGrantsAmount > 1) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.EnterPassphraseStep)).path);
await this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.EnterPassphraseStep)).name,
params: {
key: this.key,
restrictedKey: this.restrictedKey,
},
});
return;
}
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.CreatePassphraseStep)).path);
await this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.CreatePassphraseStep)).name,
params: {
key: this.key,
restrictedKey: this.restrictedKey,
},
});
}
/**
* Indicates if access grants web worker ready to use.
*/
public get isAccessGrantsWebWorkerReady(): boolean {
return this.$store.state.accessGrantsModule.isAccessGrantsWebWorkerReady;
}
/**
* Returns selected bucket names.
*/
public get selectedBucketNames(): string[] {
return this.$store.state.accessGrantsModule.selectedBucketNames;
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**
* Sets chosen permissions for API Key.
*/
private async setPermissions(): Promise<void> {
let permissionsMsg = {
'type': 'SetPermission',
'buckets': this.selectedBucketNames,
'apiKey': this.key,
'isDownload': this.isDownload,
'isUpload': this.isUpload,
'isList': this.isList,
'isDelete': this.isDelete,
};
if (this.notBeforePermission) permissionsMsg = Object.assign(permissionsMsg, { 'notBefore': this.notBeforePermission.toISOString() });
if (this.notAfterPermission) permissionsMsg = Object.assign(permissionsMsg, { 'notAfter': this.notAfterPermission.toISOString() });
await this.worker.postMessage(permissionsMsg);
const keyEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (keyEvent.data.error) {
throw new Error(keyEvent.data.error);
}
this.restrictedKey = keyEvent.data.value;
await this.$notify.success('Permissions were set successfully');
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR_SELECTION);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.SET_DURATION_PERMISSION, new DurationPermission());
}
/**
* Returns amount of access grants from store.
*/
private get accessGrantsAmount(): number {
return this.$store.state.accessGrantsModule.page.accessGrants.length;
}
/**
* Returns not before date permission from store.
*/
private get notBeforePermission(): Date | null {
return this.$store.state.accessGrantsModule.permissionNotBefore;
}
/**
* Returns not after date permission from store.
*/
private get notAfterPermission(): Date | null {
return this.$store.state.accessGrantsModule.permissionNotAfter;
}
}
</script>
<style scoped lang="scss">
::-webkit-scrollbar,
::-webkit-scrollbar-track,
::-webkit-scrollbar-thumb {
margin: 0;
width: 0;
}
.permissions {
height: calc(100% - 60px);
width: calc(100% - 130px);
padding: 30px 65px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background-color: #fff;
border-radius: 0 6px 6px 0;
&__back-icon {
position: absolute;
top: 30px;
left: 65px;
cursor: pointer;
}
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 10px;
}
&__sub-title {
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #000;
text-align: center;
margin: 0 0 70px;
}
&__content {
display: flex;
width: 100%;
&__left {
display: flex;
flex-direction: column;
align-items: flex-start;
&__item {
display: flex;
align-items: center;
flex-wrap: nowrap;
margin-bottom: 15px;
&__label {
margin: 0 0 0 10px;
}
}
}
&__right {
width: 100%;
margin-left: 100px;
&__buckets-select,
&__duration-select {
display: flex;
align-items: center;
width: 100%;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0;
}
}
&__duration-select {
margin-bottom: 40px;
}
&__bucket-bullets {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 15px 0 0 85px;
max-height: 100px;
max-width: 235px;
overflow-x: hidden;
overflow-y: scroll;
}
}
}
&__button {
margin-top: 60px;
}
&__cli-link {
font-family: 'font_medium', sans-serif;
cursor: pointer;
font-weight: 600;
font-size: 16px;
line-height: 23px;
color: #0068dc;
margin-top: 20px;
}
}
.border-radius {
border-radius: 6px;
}
.disabled {
pointer-events: none;
color: rgb(0 0 0 / 40%);
}
</style>

View File

@ -1,308 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="generate-grant" :class="{ 'border-radius': isOnboardingTour }">
<BackIcon class="generate-grant__back-icon" @click="onBackClick" />
<h1 class="generate-grant__title">Generate Access Grant</h1>
<div class="generate-grant__warning">
<div class="generate-grant__warning__header">
<WarningIcon />
<h2 class="generate-grant__warning__header__label">This Information is Only Displayed Once</h2>
</div>
<p class="generate-grant__warning__message">
Save this information in a password manager, or wherever you prefer to store sensitive information.
</p>
</div>
<div class="generate-grant__grant-area">
<h3 class="generate-grant__grant-area__label">Access Grant</h3>
<div class="generate-grant__grant-area__container">
<p class="generate-grant__grant-area__container__value">{{ access }}</p>
<VButton
class="generate-grant__grant-area__container__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyGrantClick"
/>
<VButton
class="generate-grant__grant-area__container__button"
label="Download"
width="80px"
height="30px"
:on-press="onDownloadGrantClick"
/>
</div>
</div>
<VButton
class="generate-grant__done-button"
label="Done"
width="100%"
height="48px"
:on-press="onDoneClick"
/>
<p v-if="isGatewayLinkVisible" class="generate-grant__gateway-link" @click="navigateToGatewayStep">
Generate S3 Gateway Credentials
</p>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { MetaUtils } from '@/utils/meta';
import { Download } from '@/utils/download';
import { AnalyticsHttpApi } from '@/api/analytics';
import VButton from '@/components/common/VButton.vue';
import WarningIcon from '@/../static/images/accessGrants/warning.svg';
import BackIcon from '@/../static/images/accessGrants/back.svg';
// @vue/component
@Component({
components: {
BackIcon,
WarningIcon,
VButton,
},
})
export default class ResultStep extends Vue {
private key = '';
private restrictedKey = '';
public access = '';
public isGatewayLinkVisible = false;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Lifecycle hook after initial render.
* Sets local access from props value.
*/
public mounted(): void {
if (!this.$route.params.access && !this.$route.params.key && !this.$route.params.resctrictedKey) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
this.$router.push(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.NameStep)).path);
return;
}
this.access = this.$route.params.access;
this.key = this.$route.params.key;
this.restrictedKey = this.$route.params.restrictedKey;
const requestURL = MetaUtils.getMetaContent('gateway-credentials-request-url');
if (requestURL) this.isGatewayLinkVisible = true;
}
/**
* Holds on copy access grant button click logic.
* Copies token to clipboard.
*/
public onCopyGrantClick(): void {
this.$copyText(this.access);
this.$notify.success('Token was copied successfully');
}
/**
* Holds on download access grant button click logic.
* Downloads a file with the access called access-grant-<timestamp>.key
*/
public onDownloadGrantClick(): void {
const ts = new Date();
const filename = 'access-grant-' + ts.toJSON() + '.key';
Download.file(this.access, filename);
this.$notify.success('Token was downloaded successfully');
}
/**
* Holds on back button click logic.
* Redirects to previous step.
*/
public onBackClick(): void {
if (this.accessGrantsAmount > 1) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.EnterPassphraseStep)).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.EnterPassphraseStep)).name,
params: {
key: this.key,
restrictedKey: this.restrictedKey,
},
});
return;
}
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.CreatePassphraseStep)).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.CreatePassphraseStep)).name,
params: {
key: this.key,
restrictedKey: this.restrictedKey,
},
});
}
/**
* Holds on done button click logic.
* Proceed to upload data step.
*/
public onDoneClick(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.path);
this.isOnboardingTour ? this.$router.push(RouteConfig.ProjectDashboard.path) : this.$router.push(RouteConfig.AccessGrants.path);
}
/**
* Holds on link click logic.
* Proceed to gateway step.
*/
public navigateToGatewayStep(): void {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.GatewayStep)).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.GatewayStep)).name,
params: {
access: this.access,
key: this.key,
restrictedKey: this.restrictedKey,
},
});
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**
* Returns amount of access grants from store.
*/
private get accessGrantsAmount(): number {
return this.$store.state.accessGrantsModule.page.accessGrants.length;
}
}
</script>
<style scoped lang="scss">
.generate-grant {
height: calc(100% - 60px);
padding: 30px 65px;
max-width: 475px;
min-width: 475px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background-color: #fff;
border-radius: 0 6px 6px 0;
&__back-icon {
position: absolute;
top: 30px;
left: 65px;
cursor: pointer;
}
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 30px;
}
&__warning {
padding: 15px;
width: calc(100% - 32px);
background: #fff9f7;
border: 1px solid #f84b00;
border-radius: 8px;
&__header {
display: flex;
align-items: center;
&__label {
font-style: normal;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 0 0 0 15px;
}
}
&__message {
font-size: 16px;
line-height: 22px;
color: #1b2533;
margin: 8px 0 0;
}
}
&__grant-area {
margin: 20px;
width: 100%;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0 0 10px;
}
&__container {
display: flex;
align-items: center;
border-radius: 9px;
padding: 10px;
width: calc(100% - 22px);
border: 1px solid rgb(56 75 101 / 40%);
&__value {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin: 0;
}
&__button {
min-width: 85px;
min-height: 30px;
margin-left: 10px;
}
}
}
&__done-button {
margin-top: 20px;
}
&__gateway-link {
font-weight: 600;
font-size: 16px;
line-height: 23px;
text-align: center;
color: #0068dc;
margin: 30px 0 0;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
.border-radius {
border-radius: 6px;
}
</style>

View File

@ -86,9 +86,9 @@ export default class OverviewStep extends Vue {
* Redirects to next step (creating access grant).
*/
public onUplinkCLIClick(): void {
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.CLIStep).with(RouteConfig.AGName).path);
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.OnbCLIStep).with(RouteConfig.AGName).path);
this.analytics.linkEventTriggered(AnalyticsEvent.PATH_SELECTED, 'CLI');
this.analytics.pageVisit(RouteConfig.OnboardingTour.with(RouteConfig.CLIStep).with(RouteConfig.AGName).path);
this.analytics.pageVisit(RouteConfig.OnboardingTour.with(RouteConfig.OnbCLIStep).with(RouteConfig.AGName).path);
}
/**

View File

@ -9,14 +9,6 @@ import { MetaUtils } from '@/utils/meta';
import AccessGrants from '@/components/accessGrants/AccessGrants.vue';
import CreateAccessModal from '@/components/accessGrants/CreateAccessModal.vue';
import CreateAccessGrant from '@/components/accessGrants/CreateAccessGrant.vue';
import CLIStep from '@/components/accessGrants/steps/CLIStep.vue';
import CreatePassphraseStep from '@/components/accessGrants/steps/CreatePassphraseStep.vue';
import EnterPassphraseStep from '@/components/accessGrants/steps/EnterPassphraseStep.vue';
import GatewayStep from '@/components/accessGrants/steps/GatewayStep.vue';
import NameStep from '@/components/accessGrants/steps/NameStep.vue';
import PermissionsStep from '@/components/accessGrants/steps/PermissionsStep.vue';
import ResultStep from '@/components/accessGrants/steps/ResultStep.vue';
import AccountArea from '@/components/account/AccountArea.vue';
import AccountBilling from '@/components/account/billing/BillingArea.vue';
import BillingOverview from '@/components/account/billing/billingTabs/Overview.vue';
@ -101,14 +93,6 @@ export abstract class RouteConfig {
// access grant child paths
public static CreateAccessModal = new NavigationLink('create-access-modal', 'Create Access Modal');
public static CreateAccessGrant = new NavigationLink('create-grant', 'Create Access Grant');
public static NameStep = new NavigationLink('name', 'Name Access Grant');
public static PermissionsStep = new NavigationLink('permissions', 'Access Grant Permissions');
public static CreatePassphraseStep = new NavigationLink('create-passphrase', 'Access Grant Create Passphrase');
public static EnterPassphraseStep = new NavigationLink('enter-passphrase', 'Access Grant Enter Passphrase');
public static ResultStep = new NavigationLink('result', 'Access Grant Result');
public static GatewayStep = new NavigationLink('gateway', 'Access Grant Gateway');
public static CLIStep = new NavigationLink('cli', 'Access Grant In CLI');
// onboarding tour child paths
public static OverviewStep = new NavigationLink('overview', 'Onboarding Overview');
@ -370,54 +354,6 @@ export const router = new Router({
name: RouteConfig.CreateAccessModal.name,
component: CreateAccessModal,
},
{
path: RouteConfig.CreateAccessGrant.path,
name: RouteConfig.CreateAccessGrant.name,
component: CreateAccessGrant,
children: [
{
path: RouteConfig.NameStep.path,
name: RouteConfig.NameStep.name,
component: NameStep,
},
{
path: RouteConfig.PermissionsStep.path,
name: RouteConfig.PermissionsStep.name,
component: PermissionsStep,
props: true,
},
{
path: RouteConfig.CreatePassphraseStep.path,
name: RouteConfig.CreatePassphraseStep.name,
component: CreatePassphraseStep,
props: true,
},
{
path: RouteConfig.EnterPassphraseStep.path,
name: RouteConfig.EnterPassphraseStep.name,
component: EnterPassphraseStep,
props: true,
},
{
path: RouteConfig.ResultStep.path,
name: RouteConfig.ResultStep.name,
component: ResultStep,
props: true,
},
{
path: RouteConfig.GatewayStep.path,
name: RouteConfig.GatewayStep.name,
component: GatewayStep,
props: true,
},
{
path: RouteConfig.CLIStep.path,
name: RouteConfig.CLIStep.name,
component: CLIStep,
props: true,
},
],
},
],
},
{

View File

@ -38,7 +38,7 @@ const projectsApi = new ProjectsApiGql();
const paymentsApi = new PaymentsHttpApi();
const abTestingAPI = new ABHttpApi();
// We need to use a WebWorker facotory because jest testing does not support
// We need to use a WebWorker factory because jest testing does not support
// WebWorkers yet. This is a way to avoid a direct dependency to `new Worker`.
const webWorkerFactory = {
create(): Worker {
@ -111,12 +111,6 @@ router.beforeEach(async (to, _, next) => {
return;
}
if (navigateToDefaultSubTab(to.matched, RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant))) {
next(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant).with(RouteConfig.NameStep).path);
return;
}
if (navigateToDefaultSubTab(to.matched, RouteConfig.OnboardingTour.with(RouteConfig.OnbCLIStep))) {
next(RouteConfig.OnboardingTour.path);