web/satellite: reworked delete bucket modal to use common VModal component
Reworked delete bucket modal to use common VModal component to improve UX. Issue: https://github.com/storj/storj/issues/5104 Change-Id: I74acd07a0cb3f7e0231f88bf1255de9ac000ace5
This commit is contained in:
parent
8152920a51
commit
db864a8605
@ -18,6 +18,7 @@
|
|||||||
<ShareObjectModal v-if="isShareObjectModal" />
|
<ShareObjectModal v-if="isShareObjectModal" />
|
||||||
<ObjectDetailsModal v-if="isObjectDetailsModal" />
|
<ObjectDetailsModal v-if="isObjectDetailsModal" />
|
||||||
<NewFolderModal v-if="isNewFolderModal" />
|
<NewFolderModal v-if="isNewFolderModal" />
|
||||||
|
<DeleteBucketModal v-if="isDeleteBucketModal" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -39,10 +40,12 @@ import ShareBucketModal from '@/components/modals/ShareBucketModal.vue';
|
|||||||
import ObjectDetailsModal from '@/components/modals/ObjectDetailsModal.vue';
|
import ObjectDetailsModal from '@/components/modals/ObjectDetailsModal.vue';
|
||||||
import NewFolderModal from '@/components/modals/NewFolderModal.vue';
|
import NewFolderModal from '@/components/modals/NewFolderModal.vue';
|
||||||
import ShareObjectModal from '@/components/modals/ShareObjectModal.vue';
|
import ShareObjectModal from '@/components/modals/ShareObjectModal.vue';
|
||||||
|
import DeleteBucketModal from '@/components/modals/DeleteBucketModal.vue';
|
||||||
|
|
||||||
// @vue/component
|
// @vue/component
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
|
DeleteBucketModal,
|
||||||
CreateProjectPromptModal,
|
CreateProjectPromptModal,
|
||||||
CreateProjectModal,
|
CreateProjectModal,
|
||||||
AddPaymentMethodModal,
|
AddPaymentMethodModal,
|
||||||
@ -153,6 +156,13 @@ export default class AllModals extends Vue {
|
|||||||
return this.$store.state.appStateModule.appState.isShareObjectModalShown;
|
return this.$store.state.appStateModule.appState.isShareObjectModalShown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if delete bucket modal is shown.
|
||||||
|
*/
|
||||||
|
public get isDeleteBucketModal(): boolean {
|
||||||
|
return this.$store.state.appStateModule.appState.isDeleteBucketModalShown;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if object details modal is shown.
|
* Indicates if object details modal is shown.
|
||||||
*/
|
*/
|
||||||
|
224
web/satellite/src/components/modals/DeleteBucketModal.vue
Normal file
224
web/satellite/src/components/modals/DeleteBucketModal.vue
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
// Copyright (C) 2022 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VModal :on-close="closeModal">
|
||||||
|
<template #content>
|
||||||
|
<div class="modal">
|
||||||
|
<h1 class="modal__title">Are you sure?</h1>
|
||||||
|
<p class="modal__subtitle">
|
||||||
|
Deleting bucket will delete all metadata related to this bucket.
|
||||||
|
</p>
|
||||||
|
<VInput
|
||||||
|
class="modal__input"
|
||||||
|
label="Bucket Name"
|
||||||
|
placeholder="Enter bucket name"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
@setData="onChangeName"
|
||||||
|
/>
|
||||||
|
<VButton
|
||||||
|
label="Confirm Delete Bucket"
|
||||||
|
width="100%"
|
||||||
|
height="48px"
|
||||||
|
:on-press="onDelete"
|
||||||
|
:is-disabled="isLoading || !name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
|
|
||||||
|
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||||
|
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||||
|
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||||
|
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||||
|
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
|
||||||
|
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||||
|
import { MetaUtils } from '@/utils/meta';
|
||||||
|
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||||
|
|
||||||
|
import VModal from '@/components/common/VModal.vue';
|
||||||
|
import VButton from '@/components/common/VButton.vue';
|
||||||
|
import VInput from '@/components/common/VInput.vue';
|
||||||
|
|
||||||
|
// @vue/component
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
VInput,
|
||||||
|
VButton,
|
||||||
|
VModal,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class DeleteBucketModal extends Vue {
|
||||||
|
private worker: Worker;
|
||||||
|
private name = '';
|
||||||
|
private readonly FILE_BROWSER_AG_NAME: string = 'Web file browser API key';
|
||||||
|
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
|
||||||
|
|
||||||
|
public isLoading = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook after initial render.
|
||||||
|
* Sets local worker.
|
||||||
|
*/
|
||||||
|
public mounted(): void {
|
||||||
|
this.setWorker();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds on delete button click logic.
|
||||||
|
* Creates unrestricted access grant and deletes bucket.
|
||||||
|
*/
|
||||||
|
public async onDelete(): Promise<void> {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.apiKey) {
|
||||||
|
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE_BY_NAME_AND_PROJECT_ID, this.FILE_BROWSER_AG_NAME);
|
||||||
|
const cleanAPIKey: AccessGrant = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, this.FILE_BROWSER_AG_NAME);
|
||||||
|
await this.$store.dispatch(OBJECTS_ACTIONS.SET_API_KEY, cleanAPIKey.secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const inOneDay = new Date(now.setDate(now.getDate() + 1));
|
||||||
|
|
||||||
|
await this.worker.postMessage({
|
||||||
|
'type': 'SetPermission',
|
||||||
|
'isDownload': false,
|
||||||
|
'isUpload': false,
|
||||||
|
'isList': true,
|
||||||
|
'isDelete': true,
|
||||||
|
'notAfter': inOneDay.toISOString(),
|
||||||
|
'buckets': [],
|
||||||
|
'apiKey': this.apiKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
|
||||||
|
if (grantEvent.data.error) {
|
||||||
|
await this.$notify.error(grantEvent.data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id);
|
||||||
|
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
|
||||||
|
|
||||||
|
this.worker.postMessage({
|
||||||
|
'type': 'GenerateAccess',
|
||||||
|
'apiKey': grantEvent.data.value,
|
||||||
|
'passphrase': '',
|
||||||
|
'salt': salt,
|
||||||
|
'satelliteNodeURL': satelliteNodeURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
const accessGrantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
|
||||||
|
if (accessGrantEvent.data.error) {
|
||||||
|
await this.$notify.error(accessGrantEvent.data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessGrant = accessGrantEvent.data.value;
|
||||||
|
|
||||||
|
const gatewayCredentials: EdgeCredentials = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant });
|
||||||
|
await this.$store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
|
||||||
|
await this.$store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
|
||||||
|
await this.$store.dispatch(OBJECTS_ACTIONS.DELETE_BUCKET, this.name);
|
||||||
|
this.analytics.eventTriggered(AnalyticsEvent.BUCKET_DELETED);
|
||||||
|
await this.fetchBuckets();
|
||||||
|
} catch (error) {
|
||||||
|
await this.$notify.error(error.message);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches bucket using api.
|
||||||
|
*/
|
||||||
|
private async fetchBuckets(page = 1): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, page);
|
||||||
|
} catch (error) {
|
||||||
|
await this.$notify.error(`Unable to fetch buckets. ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets local worker with worker instantiated in store.
|
||||||
|
*/
|
||||||
|
public setWorker(): void {
|
||||||
|
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
|
||||||
|
this.worker.onerror = (error: ErrorEvent) => {
|
||||||
|
this.$notify.error(error.message);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets name from input.
|
||||||
|
*/
|
||||||
|
public onChangeName(value: string): void {
|
||||||
|
this.name = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes modal.
|
||||||
|
*/
|
||||||
|
public closeModal(): void {
|
||||||
|
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_DELETE_BUCKET_MODAL_SHOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns apiKey from store.
|
||||||
|
*/
|
||||||
|
private get apiKey(): string {
|
||||||
|
return this.$store.state.objectsModule.apiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.modal {
|
||||||
|
padding: 45px 70px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-family: 'font_regular', sans-serif;
|
||||||
|
font-style: normal;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
max-width: 480px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-family: 'font_bold', sans-serif;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 27px;
|
||||||
|
color: #000;
|
||||||
|
margin: 0 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subtitle {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.1007px;
|
||||||
|
color: rgb(37 37 37 / 70%);
|
||||||
|
margin: 0 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -59,13 +59,13 @@ import { APP_STATE_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
|
|||||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
import { LocalData } from '@/utils/localData';
|
import { LocalData } from '@/utils/localData';
|
||||||
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
|
||||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||||
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||||
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||||
import { Project } from '@/types/projects';
|
import { Project } from '@/types/projects';
|
||||||
import { User } from '@/types/users';
|
import { User } from '@/types/users';
|
||||||
|
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||||
|
|
||||||
import VLoader from '@/components/common/VLoader.vue';
|
import VLoader from '@/components/common/VLoader.vue';
|
||||||
|
|
||||||
@ -146,15 +146,8 @@ export default class ProjectSelection extends Vue {
|
|||||||
|
|
||||||
if (this.isBucketsView) {
|
if (this.isBucketsView) {
|
||||||
await this.$store.dispatch(OBJECTS_ACTIONS.CLEAR);
|
await this.$store.dispatch(OBJECTS_ACTIONS.CLEAR);
|
||||||
this.analytics.pageVisit(RouteConfig.Buckets.path);
|
|
||||||
await this.$router.push(RouteConfig.Buckets.path).catch(() => {return; });
|
await this.$router.push(RouteConfig.Buckets.path).catch(() => {return; });
|
||||||
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, this.FIRST_PAGE);
|
|
||||||
} catch (error) {
|
|
||||||
await this.$notify.error(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +278,7 @@ export default class ProjectSelection extends Vue {
|
|||||||
private get isBucketsView(): boolean {
|
private get isBucketsView(): boolean {
|
||||||
const currentRoute = this.$route.path;
|
const currentRoute = this.$route.path;
|
||||||
|
|
||||||
return currentRoute.includes(RouteConfig.BucketsManagement.path);
|
return currentRoute.includes(RouteConfig.Buckets.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -31,6 +31,7 @@ import { Component, Prop } from 'vue-property-decorator';
|
|||||||
import { RouteConfig } from '@/router';
|
import { RouteConfig } from '@/router';
|
||||||
import { Bucket } from '@/types/buckets';
|
import { Bucket } from '@/types/buckets';
|
||||||
import { LocalData } from '@/utils/localData';
|
import { LocalData } from '@/utils/localData';
|
||||||
|
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||||
|
|
||||||
import TableItem from '@/components/common/TableItem.vue';
|
import TableItem from '@/components/common/TableItem.vue';
|
||||||
import Resizable from '@/components/common/Resizable.vue';
|
import Resizable from '@/components/common/Resizable.vue';
|
||||||
@ -52,8 +53,6 @@ export default class BucketItem extends Resizable {
|
|||||||
@Prop({ default: null })
|
@Prop({ default: null })
|
||||||
public readonly itemData: Bucket;
|
public readonly itemData: Bucket;
|
||||||
@Prop({ default: () => () => {} })
|
@Prop({ default: () => () => {} })
|
||||||
public readonly showDeleteBucketPopup: () => void;
|
|
||||||
@Prop({ default: () => () => {} })
|
|
||||||
public readonly openDropdown;
|
public readonly openDropdown;
|
||||||
@Prop({ default: () => (_: string) => {} })
|
@Prop({ default: () => (_: string) => {} })
|
||||||
public readonly onClick: (bucket: string) => void;
|
public readonly onClick: (bucket: string) => void;
|
||||||
@ -106,7 +105,7 @@ export default class BucketItem extends Resizable {
|
|||||||
* Holds on delete click logic.
|
* Holds on delete click logic.
|
||||||
*/
|
*/
|
||||||
public onDeleteClick(): void {
|
public onDeleteClick(): void {
|
||||||
this.showDeleteBucketPopup();
|
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_DELETE_BUCKET_MODAL_SHOWN);
|
||||||
this.closeDropdown();
|
this.closeDropdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@
|
|||||||
v-for="(bucket, key) in bucketsPage.buckets"
|
v-for="(bucket, key) in bucketsPage.buckets"
|
||||||
:key="key"
|
:key="key"
|
||||||
:item-data="bucket"
|
:item-data="bucket"
|
||||||
:show-delete-bucket-popup="showDeleteBucketPopup"
|
|
||||||
:dropdown-key="key"
|
:dropdown-key="key"
|
||||||
:open-dropdown="openDropdown"
|
:open-dropdown="openDropdown"
|
||||||
:is-dropdown-open="activeDropdown === key"
|
:is-dropdown-open="activeDropdown === key"
|
||||||
@ -60,17 +59,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-table>
|
</v-table>
|
||||||
<EncryptionBanner v-if="!isServerSideEncryptionBannerHidden" :hide="hideBanner" />
|
<EncryptionBanner v-if="!isServerSideEncryptionBannerHidden" :hide="hideBanner" />
|
||||||
<ObjectsPopup
|
|
||||||
v-if="isDeletePopupVisible"
|
|
||||||
:on-click="onDeleteBucketClick"
|
|
||||||
title="Are you sure?"
|
|
||||||
sub-title="Deleting this bucket will delete all metadata related to this bucket."
|
|
||||||
button-label="Confirm Delete Bucket"
|
|
||||||
:error-message="errorMessage"
|
|
||||||
:is-loading="isRequestProcessing"
|
|
||||||
@setName="setDeleteBucketName"
|
|
||||||
@close="hideDeleteBucketPopup"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -80,20 +68,14 @@ import { Component, Vue, Watch } from 'vue-property-decorator';
|
|||||||
import { RouteConfig } from '@/router';
|
import { RouteConfig } from '@/router';
|
||||||
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||||
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
|
||||||
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
|
|
||||||
import { MetaUtils } from '@/utils/meta';
|
|
||||||
import { Validator } from '@/utils/validation';
|
|
||||||
import { LocalData } from '@/utils/localData';
|
import { LocalData } from '@/utils/localData';
|
||||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||||
import { BucketPage } from '@/types/buckets';
|
import { BucketPage } from '@/types/buckets';
|
||||||
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||||
import { AnalyticsHttpApi } from '@/api/analytics';
|
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
|
||||||
|
|
||||||
import VLoader from '@/components/common/VLoader.vue';
|
import VLoader from '@/components/common/VLoader.vue';
|
||||||
import BucketItem from '@/components/objects/BucketItem.vue';
|
import BucketItem from '@/components/objects/BucketItem.vue';
|
||||||
import ObjectsPopup from '@/components/objects/ObjectsPopup.vue';
|
|
||||||
import VTable from '@/components/common/VTable.vue';
|
import VTable from '@/components/common/VTable.vue';
|
||||||
import EncryptionBanner from '@/components/objects/EncryptionBanner.vue';
|
import EncryptionBanner from '@/components/objects/EncryptionBanner.vue';
|
||||||
|
|
||||||
@ -106,7 +88,6 @@ import EmptyBucketIcon from '@/../static/images/objects/emptyBucket.svg';
|
|||||||
VTable,
|
VTable,
|
||||||
WhitePlusIcon,
|
WhitePlusIcon,
|
||||||
EmptyBucketIcon,
|
EmptyBucketIcon,
|
||||||
ObjectsPopup,
|
|
||||||
BucketItem,
|
BucketItem,
|
||||||
VLoader,
|
VLoader,
|
||||||
EncryptionBanner,
|
EncryptionBanner,
|
||||||
@ -114,14 +95,8 @@ import EmptyBucketIcon from '@/../static/images/objects/emptyBucket.svg';
|
|||||||
})
|
})
|
||||||
export default class BucketsView extends Vue {
|
export default class BucketsView extends Vue {
|
||||||
private readonly FILE_BROWSER_AG_NAME: string = 'Web file browser API key';
|
private readonly FILE_BROWSER_AG_NAME: string = 'Web file browser API key';
|
||||||
private worker: Worker;
|
|
||||||
private grantWithPermissions = '';
|
|
||||||
private deleteBucketName = '';
|
|
||||||
|
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
public isDeletePopupVisible = false;
|
|
||||||
public isRequestProcessing = false;
|
|
||||||
public errorMessage = '';
|
|
||||||
public activeDropdown = -1;
|
public activeDropdown = -1;
|
||||||
public isServerSideEncryptionBannerHidden = true;
|
public isServerSideEncryptionBannerHidden = true;
|
||||||
|
|
||||||
@ -149,9 +124,6 @@ export default class BucketsView extends Vue {
|
|||||||
*/
|
*/
|
||||||
public async setBucketsView(): Promise<void> {
|
public async setBucketsView(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this.setWorker();
|
|
||||||
await this.removeTemporaryAccessGrant();
|
|
||||||
await this.setAccess();
|
|
||||||
await this.fetchBuckets();
|
await this.fetchBuckets();
|
||||||
|
|
||||||
const wasDemoBucketCreated = LocalData.getDemoBucketCreatedStatus();
|
const wasDemoBucketCreated = LocalData.getDemoBucketCreatedStatus();
|
||||||
@ -179,56 +151,6 @@ export default class BucketsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets access to S3 client.
|
|
||||||
*/
|
|
||||||
public async setAccess(): Promise<void> {
|
|
||||||
const cleanAPIKey: AccessGrant = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, this.FILE_BROWSER_AG_NAME);
|
|
||||||
await this.$store.dispatch(OBJECTS_ACTIONS.SET_API_KEY, cleanAPIKey.secret);
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const inThreeDays = new Date(now.setDate(now.getDate() + 3));
|
|
||||||
|
|
||||||
await this.worker.postMessage({
|
|
||||||
'type': 'SetPermission',
|
|
||||||
'isDownload': true,
|
|
||||||
'isUpload': true,
|
|
||||||
'isList': true,
|
|
||||||
'isDelete': true,
|
|
||||||
'notAfter': inThreeDays.toISOString(),
|
|
||||||
'buckets': [],
|
|
||||||
'apiKey': cleanAPIKey.secret,
|
|
||||||
});
|
|
||||||
|
|
||||||
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
|
|
||||||
this.grantWithPermissions = grantEvent.data.value;
|
|
||||||
if (grantEvent.data.error) {
|
|
||||||
throw new Error(grantEvent.data.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id);
|
|
||||||
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
|
|
||||||
|
|
||||||
this.worker.postMessage({
|
|
||||||
'type': 'GenerateAccess',
|
|
||||||
'apiKey': this.grantWithPermissions,
|
|
||||||
'passphrase': '',
|
|
||||||
'salt': salt,
|
|
||||||
'satelliteNodeURL': satelliteNodeURL,
|
|
||||||
});
|
|
||||||
|
|
||||||
const accessGrantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
|
|
||||||
if (accessGrantEvent.data.error) {
|
|
||||||
throw new Error(accessGrantEvent.data.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessGrant = accessGrantEvent.data.value;
|
|
||||||
|
|
||||||
const gatewayCredentials: EdgeCredentials = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant, isPublic: false });
|
|
||||||
await this.$store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
|
|
||||||
await this.$store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches bucket using api.
|
* Fetches bucket using api.
|
||||||
*/
|
*/
|
||||||
@ -240,70 +162,11 @@ export default class BucketsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets local worker with worker instantiated in store.
|
|
||||||
*/
|
|
||||||
public setWorker(): void {
|
|
||||||
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
|
|
||||||
this.worker.onerror = (error: ErrorEvent) => {
|
|
||||||
this.$notify.error(error.message);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public onNewBucketButtonClick(): void {
|
public onNewBucketButtonClick(): void {
|
||||||
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
|
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
|
||||||
this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
|
this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates first ever demo bucket for user.
|
|
||||||
*/
|
|
||||||
public async createDemoBucket(): Promise<void> {
|
|
||||||
if (this.isRequestProcessing) return;
|
|
||||||
|
|
||||||
this.isRequestProcessing = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch(OBJECTS_ACTIONS.CREATE_DEMO_BUCKET);
|
|
||||||
await this.fetchBuckets();
|
|
||||||
|
|
||||||
LocalData.setDemoBucketCreatedStatus();
|
|
||||||
} catch (error) {
|
|
||||||
await this.$notify.error(error.message);
|
|
||||||
} finally {
|
|
||||||
this.isRequestProcessing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds delete bucket click logic.
|
|
||||||
*/
|
|
||||||
public async onDeleteBucketClick(): Promise<void> {
|
|
||||||
if (this.isRequestProcessing) return;
|
|
||||||
|
|
||||||
if (!this.isBucketNameValid(this.deleteBucketName)) return;
|
|
||||||
|
|
||||||
this.isRequestProcessing = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!this.edgeCredentials.accessKeyId) {
|
|
||||||
await this.setAccess();
|
|
||||||
}
|
|
||||||
await this.$store.dispatch(OBJECTS_ACTIONS.DELETE_BUCKET, this.deleteBucketName);
|
|
||||||
await this.fetchBuckets();
|
|
||||||
} catch (error) {
|
|
||||||
await this.$notify.error(error.message);
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
this.isRequestProcessing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.analytics.eventTriggered(AnalyticsEvent.BUCKET_DELETED);
|
|
||||||
|
|
||||||
this.deleteBucketName = '';
|
|
||||||
this.hideDeleteBucketPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes temporary created access grant.
|
* Removes temporary created access grant.
|
||||||
*/
|
*/
|
||||||
@ -329,30 +192,6 @@ export default class BucketsView extends Vue {
|
|||||||
this.activeDropdown = key;
|
this.activeDropdown = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes delete bucket popup visible.
|
|
||||||
*/
|
|
||||||
public showDeleteBucketPopup(): void {
|
|
||||||
this.deleteBucketName = '';
|
|
||||||
this.isDeletePopupVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides delete bucket popup.
|
|
||||||
*/
|
|
||||||
public hideDeleteBucketPopup(): void {
|
|
||||||
this.errorMessage = '';
|
|
||||||
this.isDeletePopupVisible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set delete bucket name form input.
|
|
||||||
*/
|
|
||||||
public setDeleteBucketName(name: string): void {
|
|
||||||
this.errorMessage = '';
|
|
||||||
this.deleteBucketName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides server-side encryption banner.
|
* Hides server-side encryption banner.
|
||||||
*/
|
*/
|
||||||
@ -382,32 +221,6 @@ export default class BucketsView extends Vue {
|
|||||||
private get selectedProjectID(): string {
|
private get selectedProjectID(): string {
|
||||||
return this.$store.getters.selectedProject.id;
|
return this.$store.getters.selectedProject.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns edge credentials from store.
|
|
||||||
*/
|
|
||||||
private get edgeCredentials(): EdgeCredentials {
|
|
||||||
return this.$store.state.objectsModule.gatewayCredentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns validation status of a bucket name.
|
|
||||||
*/
|
|
||||||
private isBucketNameValid(name: string): boolean {
|
|
||||||
switch (true) {
|
|
||||||
case name.length < 3 || name.length > 63:
|
|
||||||
this.errorMessage = 'Name must be not less than 3 and not more than 63 characters length';
|
|
||||||
|
|
||||||
return false;
|
|
||||||
case !Validator.bucketName(name):
|
|
||||||
this.errorMessage = 'Name must contain only lowercase latin characters, numbers, a hyphen or a period';
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
// Copyright (C) 2021 Storj Labs, Inc.
|
|
||||||
// See LICENSE for copying information.
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="objects-popup" @click.self="onCloseClick">
|
|
||||||
<div class="objects-popup__container">
|
|
||||||
<h1 class="objects-popup__container__title">{{ title }}</h1>
|
|
||||||
<p class="objects-popup__container__sub-title">{{ subTitle }}</p>
|
|
||||||
<div class="objects-popup__container__info">
|
|
||||||
<WarningIcon />
|
|
||||||
<p class="objects-popup__container__info__msg">Only lowercase alphanumeric characters are allowed.</p>
|
|
||||||
</div>
|
|
||||||
<VInput
|
|
||||||
class="objects-popup__container__input"
|
|
||||||
label="Bucket Name"
|
|
||||||
placeholder="Enter bucket name"
|
|
||||||
:error="errorMessage"
|
|
||||||
:is-loading="isLoading"
|
|
||||||
@setData="onChangeName"
|
|
||||||
/>
|
|
||||||
<VButton
|
|
||||||
:label="buttonLabel"
|
|
||||||
width="100%"
|
|
||||||
height="48px"
|
|
||||||
:on-press="onClick"
|
|
||||||
:is-disabled="isLoading"
|
|
||||||
/>
|
|
||||||
<div class="objects-popup__container__close-cross-container" @click="onCloseClick">
|
|
||||||
<CloseCrossIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
|
||||||
|
|
||||||
import VInput from '@/components/common/VInput.vue';
|
|
||||||
import VButton from '@/components/common/VButton.vue';
|
|
||||||
|
|
||||||
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
|
|
||||||
import WarningIcon from '@/../static/images/objects/warning.svg';
|
|
||||||
|
|
||||||
// @vue/component
|
|
||||||
@Component({
|
|
||||||
components: {
|
|
||||||
VInput,
|
|
||||||
VButton,
|
|
||||||
CloseCrossIcon,
|
|
||||||
WarningIcon,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export default class ObjectsPopup extends Vue {
|
|
||||||
@Prop({ default: () => () => {} })
|
|
||||||
public readonly onClick: () => void;
|
|
||||||
@Prop({ default: '' })
|
|
||||||
public readonly title: string;
|
|
||||||
@Prop({ default: '' })
|
|
||||||
public readonly subTitle: string;
|
|
||||||
@Prop({ default: '' })
|
|
||||||
public readonly buttonLabel: string;
|
|
||||||
@Prop({ default: '' })
|
|
||||||
public readonly errorMessage: string;
|
|
||||||
@Prop({ default: false })
|
|
||||||
public readonly isLoading: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets bucket name from input.
|
|
||||||
*/
|
|
||||||
public onChangeName(value: string): void {
|
|
||||||
this.$emit('setName', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes popup.
|
|
||||||
*/
|
|
||||||
public onCloseClick(): void {
|
|
||||||
this.$emit('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.objects-popup {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: rgb(27 37 51 / 75%);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&__container {
|
|
||||||
padding: 45px 70px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-family: 'font_regular', sans-serif;
|
|
||||||
font-style: normal;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #fff;
|
|
||||||
max-width: 480px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-family: 'font_bold', sans-serif;
|
|
||||||
font-size: 22px;
|
|
||||||
line-height: 27px;
|
|
||||||
color: #000;
|
|
||||||
margin: 0 0 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__sub-title {
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 30px;
|
|
||||||
text-align: center;
|
|
||||||
letter-spacing: -0.1007px;
|
|
||||||
color: rgb(37 37 37 / 70%);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 23px 14px;
|
|
||||||
background: #f5f6fa;
|
|
||||||
border: 1px solid #a9b5c1;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 9px;
|
|
||||||
|
|
||||||
&__msg {
|
|
||||||
font-family: 'font_medium', sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 19px;
|
|
||||||
color: #1b2533;
|
|
||||||
margin: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -220,20 +220,6 @@ export default class UploadFile extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.back {
|
|
||||||
font-family: 'font_medium', sans-serif;
|
|
||||||
color: #000;
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0 0 30px 15px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back:hover {
|
|
||||||
color: #007bff;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-browser {
|
.file-browser {
|
||||||
font-family: 'font_regular', sans-serif;
|
font-family: 'font_regular', sans-serif;
|
||||||
padding-bottom: 200px;
|
padding-bottom: 200px;
|
||||||
|
@ -39,6 +39,7 @@ class ViewsState {
|
|||||||
public isAddTokenFundsModalShown = false,
|
public isAddTokenFundsModalShown = false,
|
||||||
public isShareBucketModalShown = false,
|
public isShareBucketModalShown = false,
|
||||||
public isShareObjectModalShown = false,
|
public isShareObjectModalShown = false,
|
||||||
|
public isDeleteBucketModalShown = false,
|
||||||
public isNewFolderModalShown = false,
|
public isNewFolderModalShown = false,
|
||||||
public isObjectDetailsModalShown = false,
|
public isObjectDetailsModalShown = false,
|
||||||
public isBillingNotificationShown = true,
|
public isBillingNotificationShown = true,
|
||||||
@ -157,6 +158,9 @@ export const appStateModule = {
|
|||||||
[APP_STATE_MUTATIONS.TOGGLE_SHARE_OBJECT_MODAL_SHOWN](state: State): void {
|
[APP_STATE_MUTATIONS.TOGGLE_SHARE_OBJECT_MODAL_SHOWN](state: State): void {
|
||||||
state.appState.isShareObjectModalShown = !state.appState.isShareObjectModalShown;
|
state.appState.isShareObjectModalShown = !state.appState.isShareObjectModalShown;
|
||||||
},
|
},
|
||||||
|
[APP_STATE_MUTATIONS.TOGGLE_DELETE_BUCKET_MODAL_SHOWN](state: State): void {
|
||||||
|
state.appState.isDeleteBucketModalShown = !state.appState.isDeleteBucketModalShown;
|
||||||
|
},
|
||||||
[APP_STATE_MUTATIONS.TOGGLE_OBJECT_DETAILS_MODAL_SHOWN](state: State): void {
|
[APP_STATE_MUTATIONS.TOGGLE_OBJECT_DETAILS_MODAL_SHOWN](state: State): void {
|
||||||
state.appState.isObjectDetailsModalShown = !state.appState.isObjectDetailsModalShown;
|
state.appState.isObjectDetailsModalShown = !state.appState.isObjectDetailsModalShown;
|
||||||
},
|
},
|
||||||
|
@ -17,7 +17,6 @@ export const OBJECTS_ACTIONS = {
|
|||||||
SET_FILE_COMPONENT_BUCKET_NAME: 'setFileComponentBucketName',
|
SET_FILE_COMPONENT_BUCKET_NAME: 'setFileComponentBucketName',
|
||||||
FETCH_BUCKETS: 'fetchBuckets',
|
FETCH_BUCKETS: 'fetchBuckets',
|
||||||
CREATE_BUCKET: 'createBucket',
|
CREATE_BUCKET: 'createBucket',
|
||||||
CREATE_DEMO_BUCKET: 'createDemoBucket',
|
|
||||||
DELETE_BUCKET: 'deleteBucket',
|
DELETE_BUCKET: 'deleteBucket',
|
||||||
CHECK_ONGOING_UPLOADS: 'checkOngoingUploads',
|
CHECK_ONGOING_UPLOADS: 'checkOngoingUploads',
|
||||||
};
|
};
|
||||||
@ -145,11 +144,6 @@ export function makeObjectsModule(): StoreModule<ObjectsState, ObjectsContext> {
|
|||||||
Bucket: name,
|
Bucket: name,
|
||||||
}).promise();
|
}).promise();
|
||||||
},
|
},
|
||||||
createDemoBucket: async function(ctx): Promise<void> {
|
|
||||||
await ctx.state.s3Client.createBucket({
|
|
||||||
Bucket: DEMO_BUCKET_NAME,
|
|
||||||
}).promise();
|
|
||||||
},
|
|
||||||
deleteBucket: async function(ctx, name: string): Promise<void> {
|
deleteBucket: async function(ctx, name: string): Promise<void> {
|
||||||
await ctx.state.s3Client.deleteBucket({
|
await ctx.state.s3Client.deleteBucket({
|
||||||
Bucket: name,
|
Bucket: name,
|
||||||
|
@ -39,6 +39,7 @@ export const APP_STATE_MUTATIONS = {
|
|||||||
TOGGLE_ADD_TOKEN_FUNDS_MODAL_SHOWN: 'TOGGLE_ADD_TOKEN_FUNDS_MODAL_SHOWN',
|
TOGGLE_ADD_TOKEN_FUNDS_MODAL_SHOWN: 'TOGGLE_ADD_TOKEN_FUNDS_MODAL_SHOWN',
|
||||||
TOGGLE_SHARE_BUCKET_MODAL_SHOWN: 'TOGGLE_SHARE_BUCKET_MODAL_SHOWN',
|
TOGGLE_SHARE_BUCKET_MODAL_SHOWN: 'TOGGLE_SHARE_BUCKET_MODAL_SHOWN',
|
||||||
TOGGLE_SHARE_OBJECT_MODAL_SHOWN: 'TOGGLE_SHARE_OBJECT_MODAL_SHOWN',
|
TOGGLE_SHARE_OBJECT_MODAL_SHOWN: 'TOGGLE_SHARE_OBJECT_MODAL_SHOWN',
|
||||||
|
TOGGLE_DELETE_BUCKET_MODAL_SHOWN: 'TOGGLE_DELETE_BUCKET_MODAL_SHOWN',
|
||||||
TOGGLE_OBJECT_DETAILS_MODAL_SHOWN: 'TOGGLE_OBJECT_DETAILS_MODAL_SHOWN',
|
TOGGLE_OBJECT_DETAILS_MODAL_SHOWN: 'TOGGLE_OBJECT_DETAILS_MODAL_SHOWN',
|
||||||
TOGGLE_NEW_FOLDER_MODAL_SHOWN: 'TOGGLE_NEW_FOLDER_MODAL_SHOWN',
|
TOGGLE_NEW_FOLDER_MODAL_SHOWN: 'TOGGLE_NEW_FOLDER_MODAL_SHOWN',
|
||||||
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'SHOW_DELETE_PAYMENT_METHOD_POPUP',
|
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'SHOW_DELETE_PAYMENT_METHOD_POPUP',
|
||||||
|
Loading…
Reference in New Issue
Block a user