web/satellite: migrate webpack to vite

Migrated webpack to vite.
Replaced jest with vitest (because I couldn't resolve 'import.meta' issue)
Replaced legacy vue-svg-loader with vite-svg-loader
Replaced bip39 dep with bip39-english (it includes only english wordlist)
Replaced aws-sdk v2 with aws-sdk V3 (because v2 was throwing some weird error in console. Ref: https://stackoverflow.com/questions/75107933/aws-sdk-contributes-to-build-error-uncaught-typeerror-e-is-not-a-constructor)
Renamed VUE_APP_ENDPOINT_URL env variable to VITE_ENDPOINT_URL
Removed a ton of dependencies (like babel and jest-related stuff).

Tested in Chrome, Safari, Brave, Firefox and Opera browsers.

Additionally fixed logout errors from buckets and object browser routes.

TODO: try to remove util and stream-browserify dependencies and see if it works

Change-Id: I4562649a59eb0ba80c1a672d55c59fceb8c80b23
This commit is contained in:
Vitalii 2023-05-23 17:28:48 +03:00 committed by Vitalii Shpital
parent 4791a6422d
commit e6959004c9
44 changed files with 6528 additions and 24303 deletions

View File

@ -1 +1 @@
VUE_APP_ENDPOINT_URL=/api/v0/graphql VITE_ENDPOINT_URL=/api/v0/graphql

View File

@ -18,11 +18,8 @@ module.exports = {
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
sourceType: 'module', sourceType: 'module',
ecmaVersion: 2020, ecmaVersion: 2020,
vueFeatures: {
filter: true,
},
}, },
plugins: ['storj', 'eslint-plugin-import'], plugins: ['eslint-plugin-import'],
rules: { rules: {
'linebreak-style': ['error', 'unix'], 'linebreak-style': ['error', 'unix'],
@ -89,8 +86,6 @@ module.exports = {
'vue/no-undef-components': ['warn', { ignorePatterns: ['router-link', 'router-view'] }], 'vue/no-undef-components': ['warn', { ignorePatterns: ['router-link', 'router-view'] }],
'storj/vue/require-annotation': 'warn',
'vue/no-v-html': ['error'], 'vue/no-v-html': ['error'],
}, },
settings: { settings: {

View File

@ -8,5 +8,6 @@
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@ -1,20 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
process.env.TZ = 'UTC';
module.exports = {
preset: '@vue/cli-plugin-unit-jest/presets/typescript',
setupFiles: ['./jest.setup.ts'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.svg$': '<rootDir>/tests/unit/mock/svgTransform.js',
},
modulePathIgnorePatterns: ['<rootDir>/tests/unit/ignore'],
moduleFileExtensions: [
'js',
'ts',
'json',
'vue',
],
};

View File

@ -1,14 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { GlobalWithFetchMock } from 'jest-fetch-mock';
const customGlobal = (global as unknown) as
(GlobalWithFetchMock & { console: Record<any,unknown> });
customGlobal.fetch = require('jest-fetch-mock');
customGlobal.fetchMock = customGlobal.fetch;
// Disallow warnings and errors from console.
customGlobal.console.warn = (message) => { throw new Error(message); };
customGlobal.console.error = (message) => { throw new Error(message); };

File diff suppressed because it is too large Load Diff

View File

@ -3,22 +3,25 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "preview": "vite preview",
"lint": "vue-cli-service lint --max-warnings 0 --fix && stylelint . --max-warnings 0 --fix", "dev": "vite",
"lint-ci": "vue-cli-service lint --max-warnings 0 --no-fix && stylelint . --max-warnings 0 --no-fix", "build": "vite build",
"build": "vue-cli-service build", "build-watch": "vite build --watch",
"lint": "eslint --ext .js,.ts,.vue src --fix && stylelint . --max-warnings 0 --fix",
"lint-ci": "eslint --ext .js,.ts,.vue src --no-fix && stylelint . --max-warnings 0 --no-fix",
"wasm": "chmod +x ./scripts/build-wasm.sh && ./scripts/build-wasm.sh", "wasm": "chmod +x ./scripts/build-wasm.sh && ./scripts/build-wasm.sh",
"wasm-dev": "chmod +x ./scripts/build-wasm-dev.sh && ./scripts/build-wasm-dev.sh", "wasm-dev": "chmod +x ./scripts/build-wasm-dev.sh && ./scripts/build-wasm-dev.sh",
"dev": "vue-cli-service build --watch --no-module", "test": "vitest run"
"test": "vue-cli-service test:unit"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "3.7.14", "@apollo/client": "3.7.14",
"@aws-sdk/client-s3": "3.338.0",
"@aws-sdk/lib-storage": "3.338.0",
"@aws-sdk/s3-request-presigner": "3.338.0",
"@aws-sdk/signature-v4": "3.338.0",
"@hcaptcha/vue3-hcaptcha": "1.2.1", "@hcaptcha/vue3-hcaptcha": "1.2.1",
"aws-sdk": "2.1377.0", "bip39-english": "2.5.0",
"bip39": "3.1.0",
"chart.js": "4.2.1", "chart.js": "4.2.1",
"core-js": "3.30.2",
"graphql": "15.3.0", "graphql": "15.3.0",
"pinia": "2.0.23", "pinia": "2.0.23",
"pretty-bytes": "5.6.0", "pretty-bytes": "5.6.0",
@ -32,48 +35,34 @@
}, },
"devDependencies": { "devDependencies": {
"@types/filesystem": "0.0.32", "@types/filesystem": "0.0.32",
"@types/jest": "27.5.2",
"@types/node": "16.18.14", "@types/node": "16.18.14",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.0",
"@typescript-eslint/eslint-plugin": "5.59.5", "@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5", "@typescript-eslint/parser": "5.59.5",
"@vue/cli-plugin-babel": "5.0.8", "@vitejs/plugin-vue": "4.2.3",
"@vue/cli-plugin-eslint": "5.0.8",
"@vue/cli-plugin-typescript": "5.0.8",
"@vue/cli-plugin-unit-jest": "5.0.8",
"@vue/cli-service": "5.0.8",
"@vue/eslint-config-typescript": "11.0.3", "@vue/eslint-config-typescript": "11.0.3",
"@vue/test-utils": "2.3.2",
"@vue/vue3-jest": "27.0.0",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "27.5.1",
"compression-webpack-plugin": "9.2.0",
"eslint": "8.40.0", "eslint": "8.40.0",
"eslint-import-resolver-custom-alias": "1.3.0", "eslint-import-resolver-custom-alias": "1.3.0",
"eslint-import-resolver-typescript": "3.5.5", "eslint-import-resolver-typescript": "3.5.5",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"eslint-plugin-storj": "0.0.2",
"eslint-plugin-vue": "9.12.0", "eslint-plugin-vue": "9.12.0",
"jest": "27.5.1", "jsdom": "22.0.0",
"jest-fetch-mock": "3.0.3",
"postcss-html": "1.5.0", "postcss-html": "1.5.0",
"rollup-plugin-visualizer": "5.9.0",
"sass": "1.62.1", "sass": "1.62.1",
"sass-loader": "13.2.2",
"stylelint": "15.6.1", "stylelint": "15.6.1",
"stylelint-config-standard": "33.0.0", "stylelint-config-standard": "33.0.0",
"stylelint-config-standard-scss": "9.0.0", "stylelint-config-standard-scss": "9.0.0",
"stylelint-config-standard-vue": "1.0.0", "stylelint-config-standard-vue": "1.0.0",
"stylelint-scss": "5.0.0", "stylelint-scss": "5.0.0",
"ts-jest": "27.1.5",
"typescript": "4.9.5", "typescript": "4.9.5",
"vue-eslint-parser": "9.2.1", "vite": "4.3.9",
"vue-svg-loader": "0.17.0-beta.2", "vite-plugin-compression": "0.5.1",
"webpack-bundle-analyzer": "4.8.0" "vite-plugin-require": "1.1.10",
}, "vite-svg-loader": "4.0.0",
"postcss": { "vitest": "0.31.1",
"plugins": { "vitest-fetch-mock": "0.2.2",
"autoprefixer": {} "vue-eslint-parser": "9.3.0"
}
}, },
"browserslist": [ "browserslist": [
"defaults" "defaults"

View File

@ -107,7 +107,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { generateMnemonic } from 'bip39'; import { generateMnemonic } from 'bip39-english';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useNotify } from '@/utils/hooks'; import { useNotify } from '@/utils/hooks';

View File

@ -37,7 +37,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, Component } from 'vue'; import { computed } from 'vue';
import VTableCheckbox from '@/components/common/VTableCheckbox.vue'; import VTableCheckbox from '@/components/common/VTableCheckbox.vue';
import BucketGuide from '@/components/objects/BucketGuide.vue'; import BucketGuide from '@/components/objects/BucketGuide.vue';
@ -84,7 +84,7 @@ const props = withDefaults(defineProps<{
const emit = defineEmits(['selectClicked']); const emit = defineEmits(['selectClicked']);
const icons = new Map<string, Component>([ const icons = new Map<string, string>([
['locked', TableLockedIcon], ['locked', TableLockedIcon],
['bucket', ColorBucketIcon], ['bucket', ColorBucketIcon],
['folder', ColorFolderIcon], ['folder', ColorFolderIcon],

View File

@ -50,7 +50,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, Component } from 'vue'; import { computed } from 'vue';
import WhitePlusIcon from '@/../static/images/common/plusWhite.svg'; import WhitePlusIcon from '@/../static/images/common/plusWhite.svg';
import AddCircleIcon from '@/../static/images/common/addCircle.svg'; import AddCircleIcon from '@/../static/images/common/addCircle.svg';
@ -106,7 +106,7 @@ const props = withDefaults(defineProps<{
onPress: () => {}, onPress: () => {},
}); });
const icons = new Map<string, Component>([ const icons = new Map<string, string>([
['copy', CopyIcon], ['copy', CopyIcon],
['check', CheckIcon], ['check', CheckIcon],
['download', DownloadIcon], ['download', DownloadIcon],
@ -120,7 +120,7 @@ const icons = new Map<string, Component>([
['add', WhitePlusIcon], ['add', WhitePlusIcon],
]); ]);
const iconComponent = computed((): Component | undefined => icons.get(props.icon.toLowerCase())); const iconComponent = computed((): string | undefined => icons.get(props.icon.toLowerCase()));
const containerClassName = computed((): string => { const containerClassName = computed((): string => {
if (props.isDisabled) return 'disabled'; if (props.isDisabled) return 'disabled';

View File

@ -12,7 +12,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, Component } from 'vue'; import { computed } from 'vue';
import { ShareButtonConfig, ShareOptions } from '@/types/browser'; import { ShareButtonConfig, ShareOptions } from '@/types/browser';
@ -29,7 +29,7 @@ import EmailIcon from '@/../static/images/objects/email.svg';
const props = defineProps<{ link: string; }>(); const props = defineProps<{ link: string; }>();
const images: Record<string, Component> = { const images: Record<string, string> = {
[ShareOptions.Reddit]: RedditIcon, [ShareOptions.Reddit]: RedditIcon,
[ShareOptions.Facebook]: FacebookIcon, [ShareOptions.Facebook]: FacebookIcon,
[ShareOptions.Twitter]: TwitterIcon, [ShareOptions.Twitter]: TwitterIcon,

View File

@ -8,7 +8,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed } from 'vue'; import { computed, Component } from 'vue';
import { useAppStore } from '@/store/modules/appStore'; import { useAppStore } from '@/store/modules/appStore';

View File

@ -9,7 +9,7 @@
<img <img
v-if="previewAndMapFailed" v-if="previewAndMapFailed"
class="failed-preview" class="failed-preview"
src="/static/static/images/common/errorNotice.svg" :src="ErrorNoticeIcon"
alt="failed preview" alt="failed preview"
> >
<template v-else> <template v-else>
@ -126,6 +126,7 @@ import VModal from '@/components/common/VModal.vue';
import VButton from '@/components/common/VButton.vue'; import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue'; import VLoader from '@/components/common/VLoader.vue';
import ErrorNoticeIcon from '@/../static/images/common/errorNotice.svg?url';
import PlaceholderImage from '@/../static/images/browser/placeholder.svg'; import PlaceholderImage from '@/../static/images/browser/placeholder.svg';
const appStore = useAppStore(); const appStore = useAppStore();

View File

@ -38,7 +38,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { generateMnemonic } from 'bip39'; import { generateMnemonic } from 'bip39-english';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useNotify } from '@/utils/hooks'; import { useNotify } from '@/utils/hooks';

View File

@ -12,10 +12,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Component } from 'vue';
const props = defineProps<{ const props = defineProps<{
icon?: Component; icon?: string;
title: string; title: string;
}>(); }>();
</script> </script>

View File

@ -13,14 +13,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Component } from 'vue';
import CloseIcon from '@/../static/images/notifications/closeSmall.svg'; import CloseIcon from '@/../static/images/notifications/closeSmall.svg';
const props = defineProps<{ const props = defineProps<{
wordingBold: string; wordingBold: string;
wording: string; wording: string;
notificationIcon: Component; notificationIcon: string;
warningNotification: boolean; warningNotification: boolean;
onCloseClick: () => void; onCloseClick: () => void;
}>(); }>();

View File

@ -70,6 +70,8 @@ const edgeCredentials = computed((): EdgeCredentials => {
* Bucket from store found by router prop. * Bucket from store found by router prop.
*/ */
const bucket = computed((): Bucket => { const bucket = computed((): Bucket => {
if (!projectsStore.state.selectedProject.id) return new Bucket();
const data = bucketsStore.state.page.buckets.find( const data = bucketsStore.state.page.buckets.find(
(bucket: Bucket) => bucket.name === route.query.bucketName, (bucket: Bucket) => bucket.name === route.query.bucketName,
); );

View File

@ -171,7 +171,7 @@ async function fetchBuckets(page = 1, limit: number): Promise<void> {
try { try {
await bucketsStore.getBuckets(page, projectsStore.state.selectedProject.id, limit); await bucketsStore.getBuckets(page, projectsStore.state.selectedProject.id, limit);
} catch (error) { } catch (error) {
await notify.error(`Unable to fetch buckets. ${error.message}`, AnalyticsErrorEventSource.BUCKET_TABLE); notify.error(`Unable to fetch buckets. ${error.message}`, AnalyticsErrorEventSource.BUCKET_TABLE);
} }
} }
@ -180,14 +180,14 @@ async function fetchBuckets(page = 1, limit: number): Promise<void> {
*/ */
async function searchBuckets(searchQuery: string): Promise<void> { async function searchBuckets(searchQuery: string): Promise<void> {
bucketsStore.setBucketsSearch(searchQuery); bucketsStore.setBucketsSearch(searchQuery);
await analytics.eventTriggered(AnalyticsEvent.SEARCH_BUCKETS); analytics.eventTriggered(AnalyticsEvent.SEARCH_BUCKETS);
searchLoading.value = true; searchLoading.value = true;
try { try {
await bucketsStore.getBuckets(1, projectsStore.state.selectedProject.id); await bucketsStore.getBuckets(1, projectsStore.state.selectedProject.id);
} catch (error) { } catch (error) {
await notify.error(`Unable to fetch buckets: ${error.message}`, AnalyticsErrorEventSource.BUCKET_TABLE); notify.error(`Unable to fetch buckets: ${error.message}`, AnalyticsErrorEventSource.BUCKET_TABLE);
} }
searchLoading.value = false; searchLoading.value = false;
@ -219,7 +219,7 @@ async function openBucket(bucketName: string): Promise<void> {
await bucketsStore.setS3Client(projectsStore.state.selectedProject.id); await bucketsStore.setS3Client(projectsStore.state.selectedProject.id);
overallLoading.value = false; overallLoading.value = false;
} catch (error) { } catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BUCKET_TABLE); notify.error(error.message, AnalyticsErrorEventSource.BUCKET_TABLE);
overallLoading.value = false; overallLoading.value = false;
return; return;
} }

View File

@ -138,6 +138,8 @@ onMounted(async (): Promise<void> => {
}); });
watch(selectedProjectID, async () => { watch(selectedProjectID, async () => {
if (!selectedProjectID.value) return;
isLoading.value = true; isLoading.value = true;
bucketsStore.clear(); bucketsStore.clear();

View File

@ -238,17 +238,18 @@ onBeforeMount(() => {
}); });
watch(passphrase, async () => { watch(passphrase, async () => {
const projectID = projectsStore.state.selectedProject.id;
if (!projectID) return;
if (!passphrase.value) { if (!passphrase.value) {
await router.push(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path).catch(() => {return;}); await router.push(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path).catch(() => {return;});
return; return;
} }
const projectID = projectsStore.state.selectedProject.id;
try { try {
await bucketsStore.setS3Client(projectID); await bucketsStore.setS3Client(projectID);
} catch (error) { } catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.UPLOAD_FILE_VIEW); notify.error(error.message, AnalyticsErrorEventSource.UPLOAD_FILE_VIEW);
return; return;
} }
@ -266,7 +267,7 @@ watch(passphrase, async () => {
obStore.getObjectCount(), obStore.getObjectCount(),
]); ]);
} catch (error) { } catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.UPLOAD_FILE_VIEW); notify.error(error.message, AnalyticsErrorEventSource.UPLOAD_FILE_VIEW);
} }
}); });
</script> </script>

View File

@ -17,12 +17,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Component } from 'vue';
import VLoader from '@/components/common/VLoader.vue'; import VLoader from '@/components/common/VLoader.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
icon: Component, icon: string,
isDataFetching: boolean, isDataFetching: boolean,
title: string, title: string,
subtitle: string, subtitle: string,

View File

@ -36,12 +36,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, Component } from 'vue'; import { computed } from 'vue';
import VLoader from '@/components/common/VLoader.vue'; import VLoader from '@/components/common/VLoader.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
icon: Component icon: string
title: string title: string
color: string color: string
usedValue: number usedValue: number

View File

@ -7,7 +7,6 @@
<h1 class="project-dashboard__heading__title" aria-roledescription="title">{{ selectedProject.name }}</h1> <h1 class="project-dashboard__heading__title" aria-roledescription="title">{{ selectedProject.name }}</h1>
<project-ownership-tag :is-owner="selectedProject.ownerId === user.id" /> <project-ownership-tag :is-owner="selectedProject.ownerId === user.id" />
</div> </div>
<p class="project-dashboard__message"> <p class="project-dashboard__message">
Expect a delay of a few hours between network activity and the latest dashboard stats. Expect a delay of a few hours between network activity and the latest dashboard stats.
</p> </p>

View File

@ -42,7 +42,10 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
const configStore = useConfigStore(); const configStore = useConfigStore();
async function startWorker(): Promise<void> { async function startWorker(): Promise<void> {
const worker = new Worker(new URL('@/utils/accessGrant.worker.js', import.meta.url), { type: 'module' }); // TODO(vitalii): create an issue here https://github.com/vitejs/vite
// about worker chunk being auto removed after rebuild in watch mode if using new URL constructor.
// const worker = new Worker(new URL('@/utils/accessGrant.worker.js', import.meta.url));
const worker = new Worker('/static/src/utils/accessGrant.worker.js');
worker.postMessage({ 'type': 'Setup' }); worker.postMessage({ 'type': 'Setup' });
const event: MessageEvent = await new Promise(resolve => worker.onmessage = resolve); const event: MessageEvent = await new Promise(resolve => worker.onmessage = resolve);

View File

@ -169,6 +169,7 @@ export const useAppStore = defineStore('app', () => {
state.selectedPricingPlan = null; state.selectedPricingPlan = null;
state.hasShownPricingPlan = false; state.hasShownPricingPlan = false;
state.activeDropdown = ''; state.activeDropdown = '';
state.isUploadingModal = false;
state.error.visible = false; state.error.visible = false;
} }

View File

@ -3,7 +3,14 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import S3 from 'aws-sdk/clients/s3'; import {
S3Client,
S3ClientConfig,
CreateBucketCommand,
DeleteBucketCommand,
ListObjectsV2Command,
} from '@aws-sdk/client-s3';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets'; import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
import { BucketsApiGql } from '@/api/buckets'; import { BucketsApiGql } from '@/api/buckets';
@ -23,20 +30,17 @@ export class BucketsState {
public edgeCredentials: EdgeCredentials = new EdgeCredentials(); public edgeCredentials: EdgeCredentials = new EdgeCredentials();
public edgeCredentialsForDelete: EdgeCredentials = new EdgeCredentials(); public edgeCredentialsForDelete: EdgeCredentials = new EdgeCredentials();
public edgeCredentialsForCreate: EdgeCredentials = new EdgeCredentials(); public edgeCredentialsForCreate: EdgeCredentials = new EdgeCredentials();
public s3Client: S3 = new S3({ public s3Client: S3Client = new S3Client({
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 },
}); });
public s3ClientForDelete: S3 = new S3({ public s3ClientForDelete: S3Client = new S3Client({
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 },
}); });
public s3ClientForCreate: S3 = new S3({ public s3ClientForCreate: S3Client = new S3Client({
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 },
}); });
public apiKey = ''; public apiKey = '';
public passphrase = ''; public passphrase = '';
@ -81,31 +85,35 @@ export const useBucketsStore = defineStore('buckets', () => {
function setEdgeCredentialsForDelete(credentials: EdgeCredentials): void { function setEdgeCredentialsForDelete(credentials: EdgeCredentials): void {
state.edgeCredentialsForDelete = credentials; state.edgeCredentialsForDelete = credentials;
const s3Config = { const s3Config: S3ClientConfig = {
accessKeyId: state.edgeCredentialsForDelete.accessKeyId, credentials: {
secretAccessKey: state.edgeCredentialsForDelete.secretKey, accessKeyId: state.edgeCredentialsForDelete.accessKeyId,
secretAccessKey: state.edgeCredentialsForDelete.secretKey,
},
endpoint: state.edgeCredentialsForDelete.endpoint, endpoint: state.edgeCredentialsForDelete.endpoint,
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 }, region: 'us-east-1',
}; };
state.s3ClientForDelete = new S3(s3Config); state.s3ClientForDelete = new S3Client(s3Config);
} }
function setEdgeCredentialsForCreate(credentials: EdgeCredentials): void { function setEdgeCredentialsForCreate(credentials: EdgeCredentials): void {
state.edgeCredentialsForCreate = credentials; state.edgeCredentialsForCreate = credentials;
const s3Config = { const s3Config: S3ClientConfig = {
accessKeyId: state.edgeCredentialsForCreate.accessKeyId, credentials: {
secretAccessKey: state.edgeCredentialsForCreate.secretKey, accessKeyId: state.edgeCredentialsForCreate.accessKeyId,
secretAccessKey: state.edgeCredentialsForCreate.secretKey,
},
endpoint: state.edgeCredentialsForCreate.endpoint, endpoint: state.edgeCredentialsForCreate.endpoint,
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 }, region: 'us-east-1',
}; };
state.s3ClientForCreate = new S3(s3Config); state.s3ClientForCreate = new S3Client(s3Config);
} }
async function setS3Client(projectID: string): Promise<void> { async function setS3Client(projectID: string): Promise<void> {
@ -171,16 +179,18 @@ export const useBucketsStore = defineStore('buckets', () => {
const accessGrant = accessGrantEvent.data.value; const accessGrant = accessGrantEvent.data.value;
state.edgeCredentials = await agStore.getEdgeCredentials(accessGrant); state.edgeCredentials = await agStore.getEdgeCredentials(accessGrant);
const s3Config = { const s3Config: S3ClientConfig = {
accessKeyId: state.edgeCredentials.accessKeyId, credentials: {
secretAccessKey: state.edgeCredentials.secretKey, accessKeyId: state.edgeCredentials.accessKeyId,
secretAccessKey: state.edgeCredentials.secretKey,
},
endpoint: state.edgeCredentials.endpoint, endpoint: state.edgeCredentials.endpoint,
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 }, region: 'us-east-1',
}; };
state.s3Client = new S3(s3Config); state.s3Client = new S3Client(s3Config);
} }
function setPassphrase(passphrase: string): void { function setPassphrase(passphrase: string): void {
@ -192,27 +202,27 @@ export const useBucketsStore = defineStore('buckets', () => {
} }
async function createBucket(name: string): Promise<void> { async function createBucket(name: string): Promise<void> {
await state.s3Client.createBucket({ await state.s3Client.send(new CreateBucketCommand({
Bucket: name, Bucket: name,
}).promise(); }));
} }
async function createBucketWithNoPassphrase(name: string): Promise<void> { async function createBucketWithNoPassphrase(name: string): Promise<void> {
await state.s3ClientForCreate.createBucket({ await state.s3ClientForCreate.send(new CreateBucketCommand({
Bucket: name, Bucket: name,
}).promise(); }));
} }
async function deleteBucket(name: string): Promise<void> { async function deleteBucket(name: string): Promise<void> {
await state.s3ClientForDelete.deleteBucket({ await state.s3ClientForDelete.send(new DeleteBucketCommand({
Bucket: name, Bucket: name,
}).promise(); }));
} }
async function getObjectsCount(name: string): Promise<number> { async function getObjectsCount(name: string): Promise<number> {
const response = await state.s3Client.listObjectsV2({ const response = await state.s3Client.send(new ListObjectsV2Command({
Bucket: name, Bucket: name,
}).promise(); }));
return response.KeyCount === undefined ? 0 : response.KeyCount; return response.KeyCount === undefined ? 0 : response.KeyCount;
} }
@ -224,20 +234,17 @@ export const useBucketsStore = defineStore('buckets', () => {
state.edgeCredentials = new EdgeCredentials(); state.edgeCredentials = new EdgeCredentials();
state.edgeCredentialsForDelete = new EdgeCredentials(); state.edgeCredentialsForDelete = new EdgeCredentials();
state.edgeCredentialsForCreate = new EdgeCredentials(); state.edgeCredentialsForCreate = new EdgeCredentials();
state.s3Client = new S3({ state.s3Client = new S3Client({
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 },
}); });
state.s3ClientForDelete = new S3({ state.s3ClientForDelete = new S3Client({
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 },
}); });
state.s3ClientForCreate = new S3({ state.s3ClientForCreate = new S3Client({
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
httpOptions: { timeout: 0 },
}); });
state.fileComponentBucketName = ''; state.fileComponentBucketName = '';
state.leaveRoute = ''; state.leaveRoute = '';

View File

@ -3,7 +3,20 @@
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import S3, { CommonPrefix } from 'aws-sdk/clients/s3'; import {
S3Client,
CommonPrefix,
S3ClientConfig,
ListObjectsCommand,
ListObjectsV2Command,
DeleteObjectCommand,
PutObjectCommand,
_Object,
GetObjectCommand,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { Upload } from '@aws-sdk/lib-storage';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames'; import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { MODALS } from '@/utils/constants/appStatePopUps'; import { MODALS } from '@/utils/constants/appStatePopUps';
@ -46,7 +59,7 @@ export type UploadingBrowserObject = BrowserObject & {
} }
export class FilesState { export class FilesState {
s3: S3 | null = null; s3: S3Client | null = null;
accessKey: null | string = null; accessKey: null | string = null;
path = ''; path = '';
bucket = ''; bucket = '';
@ -70,7 +83,7 @@ export class FilesState {
} }
type InitializedFilesState = FilesState & { type InitializedFilesState = FilesState & {
s3: S3; s3: S3Client;
}; };
function assertIsInitialized( function assertIsInitialized(
@ -157,17 +170,18 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
fetchSharedLink: (arg0: string) => Promisable<string>; fetchSharedLink: (arg0: string) => Promisable<string>;
fetchPreviewAndMapUrl: (arg0: string) => Promisable<string>; fetchPreviewAndMapUrl: (arg0: string) => Promisable<string>;
}): void { }): void {
const s3Config = { const s3Config: S3ClientConfig = {
accessKeyId: accessKey, credentials: {
secretAccessKey: secretKey, accessKeyId: accessKey,
secretAccessKey: secretKey,
},
endpoint, endpoint,
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
connectTimeout: 0, region: 'us-east-1',
httpOptions: { timeout: 0 },
}; };
state.s3 = new S3(s3Config); state.s3 = new S3Client(s3Config);
state.accessKey = accessKey; state.accessKey = accessKey;
state.bucket = bucket; state.bucket = bucket;
state.browserRoot = browserRoot; state.browserRoot = browserRoot;
@ -187,18 +201,19 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
secretKey: string; secretKey: string;
endpoint: string; endpoint: string;
}): void { }): void {
const s3Config = { const s3Config: S3ClientConfig = {
accessKeyId: accessKey, credentials: {
secretAccessKey: secretKey, accessKeyId: accessKey,
secretAccessKey: secretKey,
},
endpoint, endpoint,
s3ForcePathStyle: true, forcePathStyle: true,
signatureVersion: 'v4', signerConstructor: SignatureV4,
connectTimeout: 0, region: 'us-east-1',
httpOptions: { timeout: 0 },
}; };
state.files = []; state.files = [];
state.s3 = new S3(s3Config); state.s3 = new S3Client(s3Config);
state.accessKey = accessKey; state.accessKey = accessKey;
} }
@ -214,24 +229,20 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
assertIsInitialized(state); assertIsInitialized(state);
const response = await state.s3 const response = await state.s3.send(new ListObjectsCommand({
.listObjects({ Bucket: state.bucket,
Bucket: state.bucket, Delimiter: '/',
Delimiter: '/', Prefix: path,
Prefix: path, }));
})
.promise();
const { Contents, CommonPrefixes } = response; let { Contents, CommonPrefixes } = response;
if (Contents === undefined) { if (Contents === undefined) {
throw new Error('Bad S3 listObjects() response: "Contents" undefined'); Contents = [];
} }
if (CommonPrefixes === undefined) { if (CommonPrefixes === undefined) {
throw new Error( CommonPrefixes = [];
'Bad S3 listObjects() response: "CommonPrefixes" undefined',
);
} }
Contents.sort((a, b) => { Contents.sort((a, b) => {
@ -302,11 +313,9 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
async function getObjectCount(): Promise<void> { async function getObjectCount(): Promise<void> {
assertIsInitialized(state); assertIsInitialized(state);
const responseV2 = await state.s3 const responseV2 = await state.s3.send(new ListObjectsV2Command({
.listObjectsV2({ Bucket: state.bucket,
Bucket: state.bucket, }));
})
.promise();
state.objectsCount = responseV2.KeyCount === undefined ? 0 : responseV2.KeyCount; state.objectsCount = responseV2.KeyCount === undefined ? 0 : responseV2.KeyCount;
} }
@ -375,7 +384,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
const fileNames = state.files.map((file) => file.Key); const fileNames = state.files.map((file) => file.Key);
function getUniqueFileName(fileName) { function getUniqueFileName(fileName: string): string {
for (let count = 1; fileNames.includes(fileName); count++) { for (let count = 1; fileNames.includes(fileName); count++) {
if (count > 1) { if (count > 1) {
fileName = fileName.replace(/\((\d+)\)(.*)/, `(${count})$2`); fileName = fileName.replace(/\((\d+)\)(.*)/, `(${count})$2`);
@ -415,10 +424,11 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
appStore.setLargeUploadWarningNotification(true); appStore.setLargeUploadWarningNotification(true);
} }
const upload = state.s3.upload( const upload = new Upload({
{ ...params }, client: state.s3,
{ partSize: 64 * 1024 * 1024 }, partSize: 64 * 1024 * 1024,
); params,
});
upload.on('httpUploadProgress', async (progress) => { upload.on('httpUploadProgress', async (progress) => {
const file = state.uploading.find(file => file.Key === params.Key); const file = state.uploading.find(file => file.Key === params.Key);
@ -426,7 +436,11 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
throw new Error(`No file found with key ${JSON.stringify(params.Key)}`); throw new Error(`No file found with key ${JSON.stringify(params.Key)}`);
} }
file.progress = Math.round((progress.loaded / progress.total) * 100); let p = 0;
if (progress.loaded && progress.total) {
p = Math.round((progress.loaded / progress.total) * 100);
}
file.progress = p;
}); });
if (config.state.config.newUploadModalEnabled) { if (config.state.config.newUploadModalEnabled) {
@ -473,7 +487,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
} }
try { try {
await upload.promise(); await upload.done();
state.uploading[index].status = UploadingStatus.Finished; state.uploading[index].status = UploadingStatus.Finished;
} catch (error) { } catch (error) {
handleUploadError(error.message, index); handleUploadError(error.message, index);
@ -509,10 +523,11 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
Body: item.Body, Body: item.Body,
}; };
const upload = state.s3.upload( const upload = new Upload({
{ ...params }, client: state.s3,
{ partSize: 64 * 1024 * 1024 }, partSize: 64 * 1024 * 1024,
); params,
});
upload.on('httpUploadProgress', async (progress) => { upload.on('httpUploadProgress', async (progress) => {
const file = state.uploading.find(file => file.Key === params.Key); const file = state.uploading.find(file => file.Key === params.Key);
@ -520,7 +535,11 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
throw new Error(`No file found with key ${JSON.stringify(params.Key)}`); throw new Error(`No file found with key ${JSON.stringify(params.Key)}`);
} }
file.progress = Math.round((progress.loaded / progress.total) * 100); let p = 0;
if (progress.loaded && progress.total) {
p = Math.round((progress.loaded / progress.total) * 100);
}
file.progress = p;
}); });
state.uploading[index] = { state.uploading[index] = {
@ -533,7 +552,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
}; };
try { try {
await upload.promise(); await upload.done();
state.uploading[index].status = UploadingStatus.Finished; state.uploading[index].status = UploadingStatus.Finished;
} catch (error) { } catch (error) {
handleUploadError(error.message, index); handleUploadError(error.message, index);
@ -563,29 +582,26 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
async function createFolder(name): Promise<void> { async function createFolder(name): Promise<void> {
assertIsInitialized(state); assertIsInitialized(state);
await state.s3 await state.s3.send(new PutObjectCommand({
.putObject({ Bucket: state.bucket,
Bucket: state.bucket, Key: state.path + name + '/.file_placeholder',
Key: state.path + name + '/.file_placeholder', Body: '',
}) }));
.promise();
list(); list();
} }
async function deleteObject(path: string, file?: S3.Object | BrowserObject, isFolder = false): Promise<void> { async function deleteObject(path: string, file?: _Object | BrowserObject, isFolder = false): Promise<void> {
if (!file) { if (!file) {
return; return;
} }
assertIsInitialized(state); assertIsInitialized(state);
await state.s3 await state.s3.send(new DeleteObjectCommand({
.deleteObject({ Bucket: state.bucket,
Bucket: state.bucket, Key: path + file.Key,
Key: path + file.Key, }));
})
.promise();
const config = useConfigStore(); const config = useConfigStore();
if (config.state.config.newUploadModalEnabled) { if (config.state.config.newUploadModalEnabled) {
@ -604,31 +620,23 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
async function recurse(filePath) { async function recurse(filePath) {
assertIsInitialized(state); assertIsInitialized(state);
const { Contents, CommonPrefixes } = await state.s3 let { Contents, CommonPrefixes } = await state.s3.send(new ListObjectsCommand({
.listObjects({ Bucket: state.bucket,
Bucket: state.bucket, Delimiter: '/',
Delimiter: '/', Prefix: filePath,
Prefix: filePath, }));
})
.promise();
if (Contents === undefined) { if (Contents === undefined) {
throw new Error( Contents = [];
'Bad S3 listObjects() response: "Contents" undefined',
);
} }
if (CommonPrefixes === undefined) { if (CommonPrefixes === undefined) {
throw new Error( CommonPrefixes = [];
'Bad S3 listObjects() response: "CommonPrefixes" undefined',
);
} }
async function thread() { async function thread() {
if (Contents === undefined) { if (Contents === undefined) {
throw new Error( Contents = [];
'Bad S3 listObjects() response: "Contents" undefined',
);
} }
while (Contents.length) { while (Contents.length) {
@ -679,10 +687,10 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => {
function download(file): void { function download(file): void {
assertIsInitialized(state); assertIsInitialized(state);
const url = state.s3.getSignedUrl('getObject', { const url = getSignedUrl(state.s3, new GetObjectCommand({
Bucket: state.bucket, Bucket: state.bucket,
Key: state.path + file.Key, Key: state.path + file.Key,
}); }));
const downloadURL = function(data, fileName) { const downloadURL = function(data, fileName) {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = data; a.href = data;

View File

@ -1,8 +1,6 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { Component } from 'vue';
import { getId } from '@/utils/idGenerator'; import { getId } from '@/utils/idGenerator';
import SuccessIcon from '@/../static/images/notifications/success.svg'; import SuccessIcon from '@/../static/images/notifications/success.svg';
@ -32,7 +30,7 @@ export class DelayedNotification {
public readonly type: string; public readonly type: string;
public readonly message: string; public readonly message: string;
public readonly style: { backgroundColor: string }; public readonly style: { backgroundColor: string };
public readonly icon: Component; public readonly icon: string;
constructor(callback: () => void, type: string, message: string) { constructor(callback: () => void, type: string, message: string) {
this.callback = callback; this.callback = callback;

View File

@ -1,20 +1,8 @@
// Copyright (C) 2021 Storj Labs, Inc. // Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { Component } from 'vue';
import EmailIcon from '../../static/images/objects/email.svg'; import EmailIcon from '../../static/images/objects/email.svg';
/**
* Exposes all properties and methods present and available in the file/browser objects in Browser.
*/
export interface BrowserFile extends File {
Key: string;
LastModified: Date;
Size: number;
type: string;
}
export enum ShareOptions { export enum ShareOptions {
Reddit = 'Reddit', Reddit = 'Reddit',
Facebook = 'Facebook', Facebook = 'Facebook',
@ -31,6 +19,6 @@ export class ShareButtonConfig {
public label: ShareOptions = ShareOptions.Email, public label: ShareOptions = ShareOptions.Email,
public color: string = 'white', public color: string = 'white',
public link: string = '', public link: string = '',
public image: Component = EmailIcon, public image: string = EmailIcon,
) {} ) {}
} }

View File

@ -1,8 +1,6 @@
// Copyright (C) 2023 Storj Labs, Inc. // Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { Component } from 'vue';
import CreateNewAccessIcon from '@/../static/images/accessGrants/newCreateFlow/createNewAccess.svg'; import CreateNewAccessIcon from '@/../static/images/accessGrants/newCreateFlow/createNewAccess.svg';
import ChoosePermissionIcon from '@/../static/images/accessGrants/newCreateFlow/choosePermission.svg'; import ChoosePermissionIcon from '@/../static/images/accessGrants/newCreateFlow/choosePermission.svg';
import AccessEncryptionIcon from '@/../static/images/accessGrants/newCreateFlow/accessEncryption.svg'; import AccessEncryptionIcon from '@/../static/images/accessGrants/newCreateFlow/accessEncryption.svg';
@ -20,7 +18,7 @@ import EndDateIcon from '@/../static/images/accessGrants/newCreateFlow/endDateIc
import EncryptionPassphraseIcon from '@/../static/images/accessGrants/newCreateFlow/encryptionPassphraseIcon.svg'; import EncryptionPassphraseIcon from '@/../static/images/accessGrants/newCreateFlow/encryptionPassphraseIcon.svg';
export interface IconAndTitle { export interface IconAndTitle {
icon: Component; icon: string;
title: string; title: string;
} }

View File

@ -1,14 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { Component } from 'vue';
export class NavigationLink { export class NavigationLink {
private readonly _path: string; private readonly _path: string;
private readonly _name: string; private readonly _name: string;
private readonly _icon: Component | undefined; private readonly _icon: string | undefined;
public constructor(path: string, name: string, icon?: Component) { public constructor(path: string, name: string, icon?: string) {
this._path = path; this._path = path;
this._name = name; this._name = name;
this._icon = icon; this._icon = icon;
@ -22,7 +20,7 @@ export class NavigationLink {
return this._name; return this._name;
} }
public get icon(): Component | undefined { public get icon(): string | undefined {
return this._icon; return this._icon;
} }
@ -30,7 +28,7 @@ export class NavigationLink {
return this._path[0] !== '/'; return this._path[0] !== '/';
} }
public withIcon(icon: Component): NavigationLink { public withIcon(icon: string): NavigationLink {
return new NavigationLink(this._path, this._name, icon); return new NavigationLink(this._path, this._name, icon);
} }

View File

@ -1,8 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
declare module '*.svg' {
import { Component } from 'vue';
const content: Component;
export default content;
}

View File

@ -1,9 +1,11 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
/// <reference types="vite/client" />
declare module '*.vue' { declare module '*.vue' {
import type { DefineComponent } from 'vue'; import type { DefineComponent } from 'vue';
// eslint-disable-next-line // eslint-disable-next-line
const component: DefineComponent<any, any, any>; const component: DefineComponent<{}, {}, any>;
export default component; export default component;
} }

View File

@ -15,7 +15,7 @@ self.onmessage = async function (event) {
switch (data.type) { switch (data.type) {
case 'Setup': case 'Setup':
try { try {
const go = new global.Go(); const go = new self.Go();
const response = await fetch('/static/static/wasm/access.wasm'); const response = await fetch('/static/static/wasm/access.wasm');
const buffer = await response.arrayBuffer(); const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer); const module = await WebAssembly.compile(buffer);

View File

@ -13,7 +13,7 @@ import { useNotificationsStore } from '@/store/modules/notificationsStore';
* Satellite url. * Satellite url.
*/ */
const satelliteUrl = new HttpLink({ const satelliteUrl = new HttpLink({
uri: process.env.VUE_APP_ENDPOINT_URL, uri: import.meta.env.VITE_ENDPOINT_URL,
}); });
/** /**

View File

@ -96,7 +96,7 @@ onMounted(() => {
padding: 52px 24px; padding: 52px 24px;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
background: url('~@/../static/images/errors/dotWorld.png') no-repeat center 178px; background: url('../../static/images/errors/dotWorld.png') no-repeat center 178px;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
overflow-y: auto; overflow-y: auto;

View File

@ -281,11 +281,6 @@ import { computed, onBeforeMount, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'; import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import BottomArrowIcon from '../../../static/images/common/lightBottomArrow.svg';
import SelectedCheckIcon from '../../../static/images/common/selectedCheck.svg';
import LogoIcon from '../../../static/images/logo.svg';
import LogoWithPartnerIcon from '../../../static/images/partnerStorjLogo.svg';
import { AuthHttpApi } from '@/api/auth'; import { AuthHttpApi } from '@/api/auth';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { MultiCaptchaConfig, PartneredSatellite } from '@/types/config'; import { MultiCaptchaConfig, PartneredSatellite } from '@/types/config';
@ -299,6 +294,10 @@ import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue'; import VInput from '@/components/common/VInput.vue';
import AddCouponCodeInput from '@/components/common/AddCouponCodeInput.vue'; import AddCouponCodeInput from '@/components/common/AddCouponCodeInput.vue';
import LogoWithPartnerIcon from '@/../static/images/partnerStorjLogo.svg';
import LogoIcon from '@/../static/images/logo.svg';
import SelectedCheckIcon from '@/../static/images/common/selectedCheck.svg';
import BottomArrowIcon from '@/../static/images/common/lightBottomArrow.svg';
import RegisterGlobe from '@/../static/images/register/RegisterGlobe.svg'; import RegisterGlobe from '@/../static/images/register/RegisterGlobe.svg';
import InfoIcon from '@/../static/images/register/info.svg'; import InfoIcon from '@/../static/images/register/info.svg';

View File

@ -1,4 +1,3 @@
{ {
"default": { "default": {
"title": "Welcome to the distributed cloud.", "title": "Welcome to the distributed cloud.",

View File

@ -1,8 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
module.exports = {
process () {
return `module.exports = { render: function(){ return this._c("svg") } }`;
},
};

View File

@ -1,6 +1,7 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { vi } from 'vitest';
import { createPinia, setActivePinia } from 'pinia'; import { createPinia, setActivePinia } from 'pinia';
import { ProjectMembersApiGql } from '@/api/projectMembers'; import { ProjectMembersApiGql } from '@/api/projectMembers';
@ -23,7 +24,7 @@ const projectMember2 = new ProjectMember('testFullName2', 'testShortName2', 'tes
describe('actions', () => { describe('actions', () => {
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()); setActivePinia(createPinia());
jest.resetAllMocks(); vi.resetAllMocks();
}); });
it('fetch project members', async function () { it('fetch project members', async function () {
@ -34,7 +35,7 @@ describe('actions', () => {
testProjectMembersPage.totalCount = 1; testProjectMembersPage.totalCount = 1;
testProjectMembersPage.pageCount = 1; testProjectMembersPage.pageCount = 1;
jest.spyOn(ProjectMembersApiGql.prototype, 'get') vi.spyOn(ProjectMembersApiGql.prototype, 'get')
.mockImplementation(() => Promise.resolve(testProjectMembersPage)); .mockImplementation(() => Promise.resolve(testProjectMembersPage));
await store.getProjectMembers(FIRST_PAGE, selectedProject.id); await store.getProjectMembers(FIRST_PAGE, selectedProject.id);
@ -95,7 +96,7 @@ describe('actions', () => {
expect(store.state.page.projectMembers[0].isSelected).toBe(true); expect(store.state.page.projectMembers[0].isSelected).toBe(true);
expect(store.state.selectedProjectMembersEmails.length).toBe(1); expect(store.state.selectedProjectMembersEmails.length).toBe(1);
jest.spyOn(ProjectMembersApiGql.prototype, 'get') vi.spyOn(ProjectMembersApiGql.prototype, 'get')
.mockImplementation(() => Promise.resolve(testProjectMembersPage)); .mockImplementation(() => Promise.resolve(testProjectMembersPage));
await store.getProjectMembers(FIRST_PAGE, selectedProject.id); await store.getProjectMembers(FIRST_PAGE, selectedProject.id);
@ -130,7 +131,7 @@ describe('actions', () => {
it('add project members', async function () { it('add project members', async function () {
const store = useProjectMembersStore(); const store = useProjectMembersStore();
jest.spyOn(ProjectMembersApiGql.prototype, 'add').mockReturnValue(Promise.resolve()); vi.spyOn(ProjectMembersApiGql.prototype, 'add').mockReturnValue(Promise.resolve());
try { try {
await store.addProjectMembers([projectMember1.user.email], selectedProject.id); await store.addProjectMembers([projectMember1.user.email], selectedProject.id);
@ -143,7 +144,7 @@ describe('actions', () => {
it('add project member throws error when api call fails', async function () { it('add project member throws error when api call fails', async function () {
const store = useProjectMembersStore(); const store = useProjectMembersStore();
jest.spyOn(ProjectMembersApiGql.prototype, 'add').mockImplementation(() => { vi.spyOn(ProjectMembersApiGql.prototype, 'add').mockImplementation(() => {
throw TEST_ERROR; throw TEST_ERROR;
}); });
@ -164,7 +165,7 @@ describe('actions', () => {
it('delete project members', async function () { it('delete project members', async function () {
const store = useProjectMembersStore(); const store = useProjectMembersStore();
jest.spyOn(ProjectMembersApiGql.prototype, 'delete').mockReturnValue(Promise.resolve()); vi.spyOn(ProjectMembersApiGql.prototype, 'delete').mockReturnValue(Promise.resolve());
try { try {
await store.deleteProjectMembers(selectedProject.id); await store.deleteProjectMembers(selectedProject.id);
@ -177,7 +178,7 @@ describe('actions', () => {
it('delete project member throws error when api call fails', async function () { it('delete project member throws error when api call fails', async function () {
const store = useProjectMembersStore(); const store = useProjectMembersStore();
jest.spyOn(ProjectMembersApiGql.prototype, 'delete').mockImplementation(() => { vi.spyOn(ProjectMembersApiGql.prototype, 'delete').mockImplementation(() => {
throw TEST_ERROR; throw TEST_ERROR;
}); });
@ -198,7 +199,7 @@ describe('actions', () => {
it('fetch project members', async function () { it('fetch project members', async function () {
const store = useProjectMembersStore(); const store = useProjectMembersStore();
jest.spyOn(ProjectMembersApiGql.prototype, 'get').mockReturnValue( vi.spyOn(ProjectMembersApiGql.prototype, 'get').mockReturnValue(
Promise.resolve(new ProjectMembersPage( Promise.resolve(new ProjectMembersPage(
[projectMember1], [projectMember1],
'', '',
@ -223,7 +224,7 @@ describe('actions', () => {
it('fetch project members throws error when api call fails', async function () { it('fetch project members throws error when api call fails', async function () {
const store = useProjectMembersStore(); const store = useProjectMembersStore();
jest.spyOn(ProjectMembersApiGql.prototype, 'get').mockImplementation(() => { vi.spyOn(ProjectMembersApiGql.prototype, 'get').mockImplementation(() => {
throw TEST_ERROR; throw TEST_ERROR;
}); });

View File

@ -1,6 +1,7 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { vi } from 'vitest';
import { createPinia, setActivePinia } from 'pinia'; import { createPinia, setActivePinia } from 'pinia';
import { ProjectsApiGql } from '@/api/projects'; import { ProjectsApiGql } from '@/api/projects';
@ -31,7 +32,7 @@ const projects = [
describe('actions', () => { describe('actions', () => {
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()); setActivePinia(createPinia());
jest.resetAllMocks(); vi.resetAllMocks();
}); });
it('select project', () => { it('select project', () => {
@ -47,7 +48,7 @@ describe('actions', () => {
it('success fetch projects', async () => { it('success fetch projects', async () => {
const store = useProjectsStore(); const store = useProjectsStore();
jest.spyOn(ProjectsApiGql.prototype, 'get').mockReturnValue( vi.spyOn(ProjectsApiGql.prototype, 'get').mockReturnValue(
Promise.resolve(projects), Promise.resolve(projects),
); );
@ -59,7 +60,7 @@ describe('actions', () => {
it('fetch throws an error when api call fails', async () => { it('fetch throws an error when api call fails', async () => {
const store = useProjectsStore(); const store = useProjectsStore();
jest.spyOn(ProjectsApiGql.prototype, 'get').mockImplementation(() => { throw new Error(); }); vi.spyOn(ProjectsApiGql.prototype, 'get').mockImplementation(() => { throw new Error(); });
try { try {
await store.getProjects(); await store.getProjects();
@ -72,7 +73,7 @@ describe('actions', () => {
it('success create project', async () => { it('success create project', async () => {
const store = useProjectsStore(); const store = useProjectsStore();
jest.spyOn(ProjectsApiGql.prototype, 'create').mockReturnValue( vi.spyOn(ProjectsApiGql.prototype, 'create').mockReturnValue(
Promise.resolve(project), Promise.resolve(project),
); );
@ -85,7 +86,7 @@ describe('actions', () => {
it('create throws an error when create api call fails', async () => { it('create throws an error when create api call fails', async () => {
const store = useProjectsStore(); const store = useProjectsStore();
jest.spyOn(ProjectsApiGql.prototype, 'create').mockImplementation(() => { throw new Error(); }); vi.spyOn(ProjectsApiGql.prototype, 'create').mockImplementation(() => { throw new Error(); });
try { try {
await store.createProject(new ProjectFields()); await store.createProject(new ProjectFields());
@ -99,7 +100,7 @@ describe('actions', () => {
it('success update project name', async () => { it('success update project name', async () => {
const store = useProjectsStore(); const store = useProjectsStore();
jest.spyOn(ProjectsApiGql.prototype, 'update').mockReturnValue( vi.spyOn(ProjectsApiGql.prototype, 'update').mockReturnValue(
Promise.resolve(), Promise.resolve(),
); );
@ -115,7 +116,7 @@ describe('actions', () => {
it('success update project description', async () => { it('success update project description', async () => {
const store = useProjectsStore(); const store = useProjectsStore();
jest.spyOn(ProjectsApiGql.prototype, 'update').mockReturnValue( vi.spyOn(ProjectsApiGql.prototype, 'update').mockReturnValue(
Promise.resolve(), Promise.resolve(),
); );
@ -131,7 +132,7 @@ describe('actions', () => {
it('success get project limits', async () => { it('success get project limits', async () => {
const store = useProjectsStore(); const store = useProjectsStore();
jest.spyOn(ProjectsApiGql.prototype, 'getLimits').mockReturnValue( vi.spyOn(ProjectsApiGql.prototype, 'getLimits').mockReturnValue(
Promise.resolve(limits), Promise.resolve(limits),
); );

View File

@ -0,0 +1,77 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
import { resolve } from 'path';
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
import vue from '@vitejs/plugin-vue';
import viteCompression from 'vite-plugin-compression';
import vitePluginRequire from 'vite-plugin-require';
import svgLoader from 'vite-svg-loader';
const productionBrotliExtensions = ['js', 'css', 'ttf', 'woff', 'woff2'];
const plugins = [
vue(),
viteCompression({
algorithm: 'brotliCompress',
threshold: 1024,
ext: '.br',
filter: new RegExp('\\.(' + productionBrotliExtensions.join('|') + ')$'),
}),
svgLoader({
svgoConfig: {
plugins: [{ name: 'removeViewBox', fn: () => {} }],
},
}),
vitePluginRequire(),
];
if (process.env['STORJ_DEBUG_BUNDLE_SIZE']) {
plugins.push(visualizer({
template: 'treemap', // or sunburst
open: true,
brotliSize: true,
filename: 'analyse.html', // will be saved in project's root
}));
}
export default defineConfig(({ mode }) => {
return {
base: '/static/dist',
plugins,
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'stream': 'stream-browserify',
'util': 'util/',
},
extensions: ['.js', '.ts', '.svg', '.vue'],
},
build: {
outDir: resolve(__dirname, 'dist'),
emptyOutDir: true,
rollupOptions: {
output: {
experimentalMinChunkSize: 50*1024,
},
},
},
define: {
'process.env': {},
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: [
'./vitest.setup.ts',
],
exclude: [
'**/node_modules/**',
'**/dist/**',
'**/tests/unit/ignore/**',
],
},
};
});

View File

@ -0,0 +1,9 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
import createFetchMock from 'vitest-fetch-mock';
import { vi } from 'vitest';
const fetchMocker = createFetchMock(vi);
fetchMocker.enableMocks();

View File

@ -1,85 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
const path = require('path');
const webpack = require('webpack');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const productionBrotliExtensions = ['js', 'css', 'ttf', 'woff', 'woff2'];
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const plugins = [
new CompressionWebpackPlugin({
algorithm: 'brotliCompress',
filename: '[path][name].br',
test: new RegExp('\\.(' + productionBrotliExtensions.join('|') + ')$'),
threshold: 1024,
minRatio: 0.8,
}),
new webpack.optimize.MinChunkSizePlugin({
minChunkSize: 50*1024,
}),
new webpack.IgnorePlugin({
contextRegExp: /bip39[\\/]src$/,
resourceRegExp: /^\.\/wordlists\/(?!english)/,
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
];
if (process.env['STORJ_DEBUG_BUNDLE_SIZE']) {
plugins.push(new BundleAnalyzerPlugin());
}
module.exports = {
publicPath: '/static/dist',
productionSourceMap: false,
parallel: true,
lintOnSave: process.env.NODE_ENV !== 'production', // disables eslint for builds
configureWebpack: {
plugins,
resolve: {
fallback: {
'util': require.resolve('util/'),
'stream': require.resolve('stream-browserify'),
'buffer': require.resolve('buffer'),
},
},
},
chainWebpack: config => {
// Avoid breaking browser UI cache.
config.output.chunkFilename(`js/vendors_[name]_[chunkhash].js`);
config.output.filename(`js/app_[name]_[chunkhash].js`);
config.resolve.alias
.set('@', path.resolve('src'));
// Disable node_modules/.cache directory usage due to permissions.
// This is enabled by default in https://cli.vuejs.org/core-plugins/babel.html#caching.
config.module.rule('js').use('babel-loader')
.tap(options => Object.assign(options, { cacheDirectory: false }));
config
.plugin('html')
.tap(args => {
args[0].template = './index.html';
return args;
});
const svgRule = config.module.rule('svg');
svgRule.uses.clear();
svgRule.type(); // Disable webpack 5 asset management.
svgRule
.use('vue-loader')
.loader('vue-loader')
.end()
.use('vue-svg-loader')
.loader('vue-svg-loader')
.options({
svgo: {
plugins: [{ removeViewBox: false }],
},
});
},
};