web/satellite: new bucket creation flow
old bucket creation flow removed new flow added name and passphrase splitted into separate views demo bucket will not be created automatically bucket creation progress bar added Change-Id: I2a1d7d77c3038caaafb3c06bdb0ac5dd1ad17599
This commit is contained in:
parent
951a842fd4
commit
99237d5c78
@ -94,7 +94,7 @@ type Config struct {
|
||||
PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"`
|
||||
NewProjectDashboard bool `help:"indicates if new project dashboard should be used" default:"false"`
|
||||
NewNavigation bool `help:"indicates if new navigation structure should be rendered" default:"true"`
|
||||
NewObjectsFlow bool `help:"indicates if new objects flow should be used" default:"true"`
|
||||
NewObjectsFlow bool `help:"indicates if new objects flow should be used" default:"false"`
|
||||
NewAccessGrantFlow bool `help:"indicates if new access grant flow should be used" default:"false"`
|
||||
GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"`
|
||||
InactivityTimerEnabled bool `help:"indicates if session can be timed out due inactivity" default:"false"`
|
||||
|
2
scripts/testdata/satellite-config.yaml.lock
vendored
2
scripts/testdata/satellite-config.yaml.lock
vendored
@ -242,7 +242,7 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
||||
# console.new-navigation: true
|
||||
|
||||
# indicates if new objects flow should be used
|
||||
# console.new-objects-flow: true
|
||||
# console.new-objects-flow: false
|
||||
|
||||
# indicates if new project dashboard should be used
|
||||
# console.new-project-dashboard: false
|
||||
|
@ -28,11 +28,15 @@ func TestBrowser_Features(t *testing.T) {
|
||||
// Navigate into browser with new onboarding.
|
||||
page.MustElementR("a", "Skip and go directly to dashboard").MustClick()
|
||||
page.MustElementR("p", "Buckets").MustClick()
|
||||
wait := page.MustWaitRequestIdle()
|
||||
page.MustElementR("p", "demo-bucket").MustClick()
|
||||
wait()
|
||||
page.MustElementR("[aria-roledescription=title]", "Create a bucket")
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementR("[aria-roledescription=title]", "Encrypt your bucket")
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementR("[aria-roledescription=title]", "Generate a passphrase")
|
||||
page.MustElementR("label", "I understand, and I have saved the passphrase.").MustClick()
|
||||
page.MustElementR("span", "Next >").MustClick()
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
|
||||
// Verify that browser component has loaded and that the dropzone is present.
|
||||
page.MustElementR("p", "Drop Files Here to Upload")
|
||||
@ -64,6 +68,7 @@ func TestBrowser_Features(t *testing.T) {
|
||||
page.MustElementR("button", "Cancel").MustClick()
|
||||
|
||||
// Add a file into folder and check that dropzone is still visible.
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
wait1 := page.MustWaitRequestIdle()
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/img.png")
|
||||
wait1()
|
||||
@ -74,7 +79,7 @@ func TestBrowser_Features(t *testing.T) {
|
||||
|
||||
// Click on the file name.
|
||||
page.MustElementR("[aria-roledescription=file]", "img.png").MustClick()
|
||||
require.Contains(t, page.MustElement("[aria-roledescription=image-preview]").MustProperty("src").Str(), "img.png", "The modal did not open on file click")
|
||||
page.MustElement("[aria-roledescription=image-preview]")
|
||||
|
||||
// Share a file.
|
||||
page.MustElementR("span", "Share").MustClick()
|
||||
@ -90,7 +95,7 @@ func TestBrowser_Features(t *testing.T) {
|
||||
// Click on the hamburger and then details.
|
||||
page.MustElement("button[aria-roledescription=dropdown]").MustClick()
|
||||
page.MustElementR("button", "Details").MustClick()
|
||||
require.Contains(t, page.MustElement("[aria-roledescription=image-preview]").MustProperty("src").Str(), "img.png", "The dropdown details functionality is not working")
|
||||
page.MustElement("[aria-roledescription=image-preview]")
|
||||
page.MustElementR("span", "Share").MustClick()
|
||||
page.MustElement("#generateShareLink")
|
||||
page.MustElement("#close-modal").MustClick()
|
||||
@ -106,6 +111,7 @@ func TestBrowser_Features(t *testing.T) {
|
||||
page.MustElementR("[aria-roledescription=folder]", "go-rod-test3")
|
||||
|
||||
// Add two files.
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/img2.png")
|
||||
page.MustElement("#close-modal").MustClick()
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/img.png")
|
||||
@ -225,6 +231,7 @@ func TestBrowser_Features(t *testing.T) {
|
||||
page.MustElementR("[aria-roledescription=folder]", "Свобода")
|
||||
|
||||
// upload a video.
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
wait3 := page.MustWaitRequestIdle()
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/movie.mp4")
|
||||
wait3()
|
||||
@ -234,7 +241,7 @@ func TestBrowser_Features(t *testing.T) {
|
||||
page.MustElementR("[aria-roledescription=file-size]", "1.48 kB")
|
||||
page.MustElement("[aria-roledescription=file-upload-date]")
|
||||
page.MustElementR("[aria-roledescription=file]", "movie.mp4").MustClick()
|
||||
require.Contains(t, page.MustElement("[aria-roledescription=video-preview]").MustProperty("src").Str(), "movie.mp4", "The modal did not open on video file click")
|
||||
page.MustElement("[aria-roledescription=video-preview]")
|
||||
page.MustElement("#close-modal").MustClick()
|
||||
|
||||
// Upload an audio file.
|
||||
@ -242,7 +249,7 @@ func TestBrowser_Features(t *testing.T) {
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/audio.mp3")
|
||||
wait4()
|
||||
page.MustElementR("[aria-roledescription=file]", "audio.mp3").MustClick()
|
||||
require.Contains(t, page.MustElement("[aria-roledescription=audio-preview]").MustProperty("src").Str(), "audio.mp3", "The modal did not open on video file click")
|
||||
page.MustElement("[aria-roledescription=audio-preview]")
|
||||
page.MustElement("#close-modal").MustClick()
|
||||
|
||||
// Navigate out of nested folder and delete everything.
|
||||
@ -266,11 +273,15 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
// Navigate into browser with new onboarding.
|
||||
page.MustElementR("a", "Skip and go directly to dashboard").MustClick()
|
||||
page.MustElementR("p", "Buckets").MustClick()
|
||||
wait := page.MustWaitRequestIdle()
|
||||
page.MustElementR("p", "demo-bucket").MustClick()
|
||||
wait()
|
||||
page.MustElementR("[aria-roledescription=title]", "Create a bucket")
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementR("[aria-roledescription=title]", "Encrypt your bucket")
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementR("[aria-roledescription=title]", "Generate a passphrase")
|
||||
page.MustElementR("label", "I understand, and I have saved the passphrase.").MustClick()
|
||||
page.MustElementR("span", "Next >").MustClick()
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
|
||||
// Verify that browser component has loaded and that the dropzone is present.
|
||||
page.MustElementR("p", "Drop Files Here to Upload")
|
||||
@ -307,6 +318,7 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
require.Equal(t, " ", page.MustElement("[placeholder=\"Name of the folder\"]").MustText(), "Folder input does not contain the empty invalid name")
|
||||
page.MustElementR("button", "Cancel").MustClick()
|
||||
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
wait1 := page.MustWaitRequestIdle()
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/test0bytes.txt")
|
||||
wait1()
|
||||
@ -315,6 +327,7 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
page.MustElementR("[aria-roledescription=file]", "test0bytes.txt").MustClick()
|
||||
require.Contains(t, page.MustElement("[aria-roledescription=preview-placeholder]").String(), "svg", "The modal did not open upon clicking the test0bytes.txt file")
|
||||
} else {
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
wait2 := page.MustWaitRequestIdle()
|
||||
page.MustElement("input[aria-roledescription=folder-upload]").MustSetFiles("./testdata")
|
||||
wait2()
|
||||
@ -345,6 +358,7 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
require.Equal(t, " ", page.MustElement("[placeholder=\"Name of the folder\"]").MustText(), "Folder input does not contain the empty invalid name")
|
||||
page.MustElementR("button", "Cancel").MustClick()
|
||||
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
wait3 := page.MustWaitRequestIdle()
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/test0bytes.txt")
|
||||
wait3()
|
||||
@ -353,11 +367,13 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
page.MustElementR("[aria-roledescription=file]", "test0bytes.txt").MustClick()
|
||||
require.Contains(t, page.MustElement("[aria-roledescription=preview-placeholder]").String(), "svg", "The modal did not open upon clicking the test0bytes.txt file")
|
||||
} else {
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
wait4 := page.MustWaitRequestIdle()
|
||||
page.MustElement("input[aria-roledescription=folder-upload]").MustSetFiles("./testdata")
|
||||
wait4()
|
||||
page.MustElementR("table > tbody > tr:nth-child(1) > td", "..")
|
||||
page.MustElementR("[aria-roledescription=folder]", "testdata \\(1\\)").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementR("[aria-roledescription=file]", "test0bytes.txt").MustClick()
|
||||
require.Contains(t, page.MustElement("[aria-roledescription=preview-placeholder]").String(), "svg", "The uploaded folder did not upload the files correctly")
|
||||
}
|
||||
@ -366,6 +382,7 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
page.MustElement("#navigate-back").MustClick()
|
||||
|
||||
// Upload a 0 byte file.
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
page.MustElement("input[aria-roledescription=file-upload]").MustSetFiles("./testdata/test0bytes.txt")
|
||||
page.MustElementR("span", "testing/test0bytes.txt")
|
||||
page.MustElement("#close-modal").MustClick()
|
||||
@ -382,7 +399,7 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
if !testing.Short() {
|
||||
slowpage := page.Sleeper(uitest.MaxDuration(20 * time.Second))
|
||||
|
||||
// Upload a 50 MB file.
|
||||
// Upload a 5 MB file.
|
||||
testFile := generateEmptyFile(t, ctx, "testFile", 5*memory.MiB)
|
||||
wait5 := slowpage.MustWaitRequestIdle()
|
||||
slowpage.MustElement("input[aria-roledescription=file-upload]").MustSetFiles(testFile)
|
||||
@ -403,6 +420,7 @@ func TestBrowser_FolderAndDifferentFileSizesUpload(t *testing.T) {
|
||||
slowpage.MustElement("#close-modal").MustClick()
|
||||
|
||||
// Upload a 130MB file.
|
||||
page.MustElementR("button", "Upload").MustClick()
|
||||
wait6 := slowpage.MustWaitRequestIdle()
|
||||
slowpage.MustElement("input[aria-roledescription=file-upload]").MustSetFiles(testFile2)
|
||||
require.Equal(t, " testing/testFile2", slowpage.MustElement("[aria-roledescription=file-uploading]").MustText(), "The testFile2 file has not started uploading")
|
||||
|
@ -29,25 +29,26 @@ func TestOnboarding_WizardBrowser(t *testing.T) {
|
||||
|
||||
// Buckets Page
|
||||
bucketsTitle := page.MustElement("[aria-roledescription=title]").MustText()
|
||||
require.Contains(t, bucketsTitle, "Buckets")
|
||||
page.Race().ElementR("p", "demo-bucket").MustHandle(func(el *rod.Element) {
|
||||
el.MustClick()
|
||||
waitVueTick(page)
|
||||
}).MustDo()
|
||||
|
||||
// Passphrase screen
|
||||
encryptionTitle := page.MustElement("[aria-roledescription=objects-title]").MustText()
|
||||
require.Contains(t, encryptionTitle, "The object browser uses server side encryption.")
|
||||
customPassphrase := page.MustElement("[aria-roledescription=enter-passphrase-label]")
|
||||
customPassphraseLabel := customPassphrase.MustText()
|
||||
require.Contains(t, customPassphraseLabel, "Enter your own passphrase")
|
||||
customPassphrase.MustClick()
|
||||
require.Contains(t, bucketsTitle, "Create a bucket")
|
||||
page.MustElementR("[aria-roledescription=title]", "Create a bucket")
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElement("[aria-roledescription=passphrase] input").MustInput("password123")
|
||||
page.MustElement(".checkmark").MustClick()
|
||||
page.MustElementR("[aria-roledescription=title]", "Encrypt your bucket")
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementX("(//span[text()=\"Next >\"])").MustClick()
|
||||
page.MustElementR("[aria-roledescription=title]", "Generate a passphrase")
|
||||
mnemonic := page.MustElement("[aria-roledescription=mnemonic]").MustText()
|
||||
require.NotEmpty(t, mnemonic)
|
||||
page.MustElementR("span", "Back").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementR("[aria-roledescription=title]", "Encrypt your bucket")
|
||||
page.MustElement("[aria-roledescription=manual]").MustClick()
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
waitVueTick(page)
|
||||
page.MustElementR("[aria-roledescription=title]", "Enter a passphrase")
|
||||
page.MustElement("[aria-roledescription=passphrase] input").MustInput("1")
|
||||
page.MustElementR("label", "I understand, and I have saved the passphrase.").MustClick()
|
||||
page.MustElementR("span", "Continue").MustClick()
|
||||
|
||||
// Verify that browser component has loaded and that the dropzone is present
|
||||
page.MustElementR("p", "Drop Files Here to Upload")
|
||||
|
@ -58,9 +58,8 @@ import HeaderlessInput from './HeaderlessInput.vue';
|
||||
ErrorIcon,
|
||||
},
|
||||
})
|
||||
// TODO: merge these two components to have one single source of truth.
|
||||
export default class HeaderedInput extends HeaderlessInput {
|
||||
@Prop({default: ''})
|
||||
private readonly initValue: string;
|
||||
@Prop({default: ''})
|
||||
private readonly additionalLabel: string;
|
||||
@Prop({default: 0})
|
||||
@ -73,10 +72,6 @@ export default class HeaderedInput extends HeaderlessInput {
|
||||
private readonly isMultiline: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isLoading: boolean;
|
||||
|
||||
public created() {
|
||||
this.setValue(this.initValue);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -78,8 +78,10 @@ export default class HeaderlessInput extends Vue {
|
||||
private type: string = this.textType;
|
||||
private isPasswordShown = false;
|
||||
|
||||
public value: string;
|
||||
|
||||
@Prop({default: ''})
|
||||
protected value: string;
|
||||
protected readonly initValue: string;
|
||||
|
||||
@Prop({default: ''})
|
||||
protected readonly label: string;
|
||||
@ -111,6 +113,7 @@ export default class HeaderlessInput extends Vue {
|
||||
|
||||
public created() {
|
||||
this.type = this.isPassword ? this.passwordType : this.textType;
|
||||
this.value = this.initValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
203
web/satellite/src/components/objects/BucketCreation.vue
Normal file
203
web/satellite/src/components/objects/BucketCreation.vue
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="bucket-creation">
|
||||
<bucket-creation-progress class="bucket-creation__progress" :creation-step="creationStep" />
|
||||
<bucket-creation-name-step
|
||||
v-if="creationStep === BucketCreationSteps.Name"
|
||||
@setName="setName"
|
||||
/>
|
||||
<bucket-creation-generate-passphrase
|
||||
v-if="creationStep === BucketCreationSteps.Passphrase"
|
||||
:on-next-click="onPassphraseGenerationNextClick"
|
||||
:on-back-click="onGenerationBackClick"
|
||||
:set-parent-passphrase="setPassphrase"
|
||||
:is-loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||
import { AccessGrant, EdgeCredentials } from "@/types/accessGrants";
|
||||
import { ACCESS_GRANTS_ACTIONS } from "@/store/modules/accessGrants";
|
||||
import { MetaUtils } from "@/utils/meta";
|
||||
|
||||
import BucketCreationGeneratePassphrase from "@/components/objects/BucketCreationGeneratePassphrase.vue";
|
||||
import BucketCreationNameStep from "@/components/objects/BucketCreationNameStep.vue";
|
||||
import BucketCreationProgress from "@/components/objects/BucketCreationProgress.vue";
|
||||
|
||||
export enum BucketCreationSteps {
|
||||
Name = 0,
|
||||
Passphrase,
|
||||
Upload
|
||||
}
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
BucketCreationProgress,
|
||||
BucketCreationNameStep,
|
||||
BucketCreationGeneratePassphrase,
|
||||
},
|
||||
})
|
||||
export default class BucketCreation extends Vue {
|
||||
private worker: Worker;
|
||||
|
||||
private readonly FILE_BROWSER_AG_NAME: string = 'Web file browser API key';
|
||||
public readonly BucketCreationSteps = BucketCreationSteps;
|
||||
public creationStep: BucketCreationSteps = BucketCreationSteps.Name;
|
||||
public isLoading = false;
|
||||
public bucketName = '';
|
||||
public passphrase = '';
|
||||
|
||||
/**
|
||||
* Sets bucket name from child component.
|
||||
*/
|
||||
public setName(name: string): void {
|
||||
this.bucketName = name;
|
||||
this.creationStep = BucketCreationSteps.Passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets passphrase from child component.
|
||||
*/
|
||||
public setPassphrase(passphrase: string): void {
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds on next button click logic on passphrase generation step.
|
||||
*/
|
||||
public async onPassphraseGenerationNextClick(): Promise<void> {
|
||||
if (this.isLoading) return;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
this.setWorker();
|
||||
await this.setAccess();
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.CREATE_BUCKET, this.bucketName);
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, this.bucketName);
|
||||
await this.$router.push(RouteConfig.UploadFile.path);
|
||||
} catch (e) {
|
||||
await this.$notify.error(e.message);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds on back button click logic on passphrase generation step.
|
||||
*/
|
||||
public onGenerationBackClick(): void {
|
||||
this.creationStep = BucketCreationSteps.Name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes temporary created access grant.
|
||||
*/
|
||||
public async removeTemporaryAccessGrant(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE_BY_NAME_AND_PROJECT_ID, this.FILE_BROWSER_AG_NAME);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets access to S3 client.
|
||||
*/
|
||||
public async setAccess(): Promise<void> {
|
||||
if (!this.apiKey) {
|
||||
await this.removeTemporaryAccessGrant();
|
||||
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': [this.bucketName],
|
||||
'apiKey': this.apiKey,
|
||||
});
|
||||
|
||||
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
|
||||
if (grantEvent.data.error) {
|
||||
throw new Error(grantEvent.data.error);
|
||||
}
|
||||
|
||||
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
|
||||
this.worker.postMessage({
|
||||
'type': 'GenerateAccess',
|
||||
'apiKey': grantEvent.data.value,
|
||||
'passphrase': this.passphrase,
|
||||
'projectID': this.$store.getters.selectedProject.id,
|
||||
'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});
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns edge credentials from store.
|
||||
*/
|
||||
private get edgeCredentials(): EdgeCredentials {
|
||||
return this.$store.state.objectsModule.gatewayCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns apiKey from store.
|
||||
*/
|
||||
private get apiKey(): string {
|
||||
return this.$store.state.objectsModule.apiKey;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bucket-creation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
padding-bottom: 30px;
|
||||
|
||||
&__progress {
|
||||
margin-bottom: 44px;
|
||||
width: 460px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,399 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="encrypt-container">
|
||||
<bucket-icon />
|
||||
<div v-if="generationStep === GenerationSteps.TypeSelection" class="encrypt-container__functional">
|
||||
<div class="encrypt-container__functional__header">
|
||||
<p class="encrypt-container__functional__header__title" aria-roledescription="title">
|
||||
Encrypt your bucket
|
||||
</p>
|
||||
<p class="encrypt-container__functional__header__info">
|
||||
We encourage you to generate the encryption passphrase.
|
||||
You can also enter your own passphrase for this bucket.
|
||||
</p>
|
||||
</div>
|
||||
<div class="encrypt-container__functional__variant" aria-roledescription="generate" @click="selectedType = GenerationSteps.Generation">
|
||||
<input
|
||||
class="encrypt-container__functional__variant__radio"
|
||||
type="radio"
|
||||
name="radio"
|
||||
:checked="selectedType === GenerationSteps.Generation"
|
||||
>
|
||||
<div class="encrypt-container__functional__variant__icon">
|
||||
<key-icon />
|
||||
</div>
|
||||
<div class="encrypt-container__functional__variant__text-container">
|
||||
<h4 class="encrypt-container__functional__variant__text-container__title">Generate passphrase</h4>
|
||||
<p class="encrypt-container__functional__variant__text-container__info">Automatically generate 12-word passphrase.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="encrypt-container__functional__variant__divider" />
|
||||
<div class="encrypt-container__functional__variant" aria-roledescription="manual" @click="selectedType = GenerationSteps.Manual">
|
||||
<input
|
||||
class="encrypt-container__functional__variant__radio"
|
||||
type="radio"
|
||||
name="radio"
|
||||
:checked="selectedType === GenerationSteps.Manual"
|
||||
>
|
||||
<div class="encrypt-container__functional__variant__icon">
|
||||
<fingerprint-icon />
|
||||
</div>
|
||||
<div class="encrypt-container__functional__variant__text-container">
|
||||
<h4 class="encrypt-container__functional__variant__text-container__title">Enter passphrase</h4>
|
||||
<p class="encrypt-container__functional__variant__text-container__info">You can also enter your own passphrase.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="encrypt-container__functional">
|
||||
<div class="encrypt-container__functional__header">
|
||||
<p class="encrypt-container__functional__header__title" aria-roledescription="title">
|
||||
{{ generationStep === GenerationSteps.Generation ? 'Generate a passphrase' : 'Enter a passphrase' }}
|
||||
</p>
|
||||
<p class="encrypt-container__functional__header__info">
|
||||
Please note that Storj does not know or store your encryption passphrase.
|
||||
If you lose it, you will not be able to recover your files.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="generationStep === GenerationSteps.Generation" class="encrypt-container__functional__generate">
|
||||
<p class="encrypt-container__functional__generate__value" aria-roledescription="mnemonic">{{ passphrase }}</p>
|
||||
<v-button
|
||||
class="encrypt-container__functional__generate__button"
|
||||
label="Download"
|
||||
width="143px"
|
||||
height="48px"
|
||||
:on-press="onDownloadClick"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="encrypt-container__functional__enter">
|
||||
<headerless-input
|
||||
label="Your Passphrase"
|
||||
placeholder="Enter a passphrase here..."
|
||||
:error="enterError"
|
||||
role-description="passphrase"
|
||||
is-password="true"
|
||||
:disabled="isLoading"
|
||||
@setData="setPassphrase"
|
||||
/>
|
||||
</div>
|
||||
<v-checkbox
|
||||
class="encrypt-container__functional__checkbox"
|
||||
label="I understand, and I have saved the passphrase."
|
||||
:is-checkbox-error="isCheckboxError"
|
||||
@setData="setSavingConfirmation"
|
||||
/>
|
||||
</div>
|
||||
<div class="encrypt-container__buttons">
|
||||
<v-button
|
||||
class="encrypt-container__buttons__back"
|
||||
label="Back"
|
||||
height="48px"
|
||||
width="45%"
|
||||
:is-white="true"
|
||||
:on-press="generationStep === GenerationSteps.TypeSelection ? onBackClick : navigateToTypeSelection"
|
||||
:is-disabled="isLoading"
|
||||
/>
|
||||
<v-button
|
||||
v-if="generationStep === GenerationSteps.TypeSelection"
|
||||
height="48px"
|
||||
width="45%"
|
||||
label="Continue"
|
||||
:on-press="selectPassphraseVariant"
|
||||
/>
|
||||
<v-button
|
||||
v-else
|
||||
label="Continue"
|
||||
height="48px"
|
||||
width="45%"
|
||||
:on-press="onNextButtonClick"
|
||||
:is-disabled="isLoading || !isSavingConfirmed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { generateMnemonic } from "bip39";
|
||||
|
||||
import { Download } from "@/utils/download";
|
||||
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import HeaderlessInput from "@/components/common/HeaderlessInput.vue";
|
||||
import VCheckbox from "@/components/common/VCheckbox.vue";
|
||||
|
||||
import BucketIcon from "@/../static/images/objects/bucketCreation.svg";
|
||||
import KeyIcon from "@/../static/images/objects/key.svg";
|
||||
import FingerprintIcon from "@/../static/images/objects/fingerprint.svg";
|
||||
|
||||
enum GenerationSteps {
|
||||
TypeSelection,
|
||||
Generation,
|
||||
Manual,
|
||||
}
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
BucketIcon,
|
||||
KeyIcon,
|
||||
FingerprintIcon,
|
||||
VButton,
|
||||
HeaderlessInput,
|
||||
VCheckbox,
|
||||
},
|
||||
})
|
||||
export default class GeneratePassphrase extends Vue {
|
||||
@Prop({ default: () => null })
|
||||
public readonly onNextClick: () => unknown;
|
||||
@Prop({ default: () => null })
|
||||
public readonly onBackClick: () => unknown;
|
||||
@Prop({ default: () => null })
|
||||
public readonly setParentPassphrase: (passphrase: string) => void;
|
||||
@Prop({ default: false })
|
||||
public readonly isLoading: boolean;
|
||||
|
||||
public readonly GenerationSteps = GenerationSteps;
|
||||
|
||||
public selectedType: GenerationSteps = GenerationSteps.Generation;
|
||||
public generationStep: GenerationSteps = GenerationSteps.TypeSelection;
|
||||
public enterError = '';
|
||||
public passphrase = '';
|
||||
public isSavingConfirmed = false;
|
||||
public isCheckboxError = false;
|
||||
|
||||
public setSavingConfirmation(value: boolean): void {
|
||||
this.isSavingConfirmed = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects passphrase setup variant.
|
||||
* If not manual, generates passphrase.
|
||||
*/
|
||||
public selectPassphraseVariant(): void {
|
||||
if (this.selectedType === GenerationSteps.Generation) {
|
||||
this.passphrase = generateMnemonic();
|
||||
this.setParentPassphrase(this.passphrase);
|
||||
}
|
||||
|
||||
this.generationStep = this.selectedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds on download button click logic.
|
||||
* Downloads encryption passphrase as a txt file.
|
||||
*/
|
||||
public onDownloadClick(): void {
|
||||
if (!this.passphrase) {
|
||||
this.enterError = 'Can\'t be empty!';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = 'StorjEncryptionPassphrase.txt';
|
||||
|
||||
Download.file(this.passphrase, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to passphrase option selection.
|
||||
*/
|
||||
public navigateToTypeSelection(): void {
|
||||
this.enterError = '';
|
||||
this.passphrase = '';
|
||||
this.isSavingConfirmed = false;
|
||||
this.generationStep = GenerationSteps.TypeSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets passphrase from child component.
|
||||
*/
|
||||
public setPassphrase(passphrase: string): void {
|
||||
if (this.enterError) this.enterError = '';
|
||||
|
||||
this.passphrase = passphrase;
|
||||
this.setParentPassphrase(this.passphrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds on next button click logic.
|
||||
*/
|
||||
public async onNextButtonClick(): Promise<void> {
|
||||
if (!this.passphrase) {
|
||||
this.enterError = 'Can\'t be empty!';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isSavingConfirmed) {
|
||||
this.isCheckboxError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.onNextClick();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.encrypt-container {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
padding: 60px 60px 50px;
|
||||
max-width: 500px;
|
||||
background: #fcfcfc;
|
||||
box-shadow: 0 0 32px rgb(0 0 0 / 4%);
|
||||
border-radius: 20px;
|
||||
margin: 30px auto 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__functional {
|
||||
padding: 20px 0;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 26px;
|
||||
line-height: 31px;
|
||||
color: #131621;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: #354049;
|
||||
}
|
||||
}
|
||||
|
||||
&__variant {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 20px 0;
|
||||
|
||||
&__radio {
|
||||
color: #0149ff;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: #e6edf7;
|
||||
margin: 0 8px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
max-width: 16px;
|
||||
max-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&__divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: #c8d3de;
|
||||
}
|
||||
}
|
||||
|
||||
&__generate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 22px;
|
||||
background: #eff0f7;
|
||||
border-radius: 10px;
|
||||
margin-top: 25px;
|
||||
|
||||
&__value {
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
color: #384b65;
|
||||
}
|
||||
|
||||
&__button {
|
||||
margin-left: 12px;
|
||||
min-width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
&__download {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
color: #0068dc;
|
||||
cursor: pointer;
|
||||
margin: 20px 25px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&__warning-title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
color: #1b2533;
|
||||
margin: 25px 25px 10px;
|
||||
}
|
||||
|
||||
&__warning-msg {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #1b2533;
|
||||
margin: 0 25px;
|
||||
}
|
||||
|
||||
&__checkbox {
|
||||
margin-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 30px;
|
||||
|
||||
&__back {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
204
web/satellite/src/components/objects/BucketCreationNameStep.vue
Normal file
204
web/satellite/src/components/objects/BucketCreationNameStep.vue
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="bucket-name-creation">
|
||||
<v-loader v-if="isLoading" width="100px" height="100px" />
|
||||
<template v-else>
|
||||
<bucket-icon />
|
||||
<div class="bucket-name-creation__functional">
|
||||
<div class="bucket-name-creation__functional__header">
|
||||
<p class="bucket-name-creation__functional__header__title" aria-roledescription="title">
|
||||
Create a bucket
|
||||
</p>
|
||||
<p class="bucket-name-creation__functional__header__info">
|
||||
Buckets are used to store your files. It’s recommended that every bucket should have it’s own encryption passphrase.
|
||||
</p>
|
||||
</div>
|
||||
<headered-input
|
||||
label="Bucket Name"
|
||||
placeholder="Enter a passphrase here..."
|
||||
:error="nameError"
|
||||
role-description="name"
|
||||
:init-value="name"
|
||||
additional-label="Lowercase alphanumeric characters only (no spaces)"
|
||||
@setData="onChangeName"
|
||||
/>
|
||||
</div>
|
||||
<div class="bucket-name-creation__buttons">
|
||||
<v-button
|
||||
label="Cancel"
|
||||
height="48px"
|
||||
width="45%"
|
||||
:on-press="onCancelClick"
|
||||
:is-white="true"
|
||||
/>
|
||||
<v-button
|
||||
label="Continue"
|
||||
height="48px"
|
||||
width="45%"
|
||||
:on-press="onNextButtonClick"
|
||||
:is-disabled="!name || nameError !== ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { RouteConfig } from "@/router";
|
||||
import { Validator } from "@/utils/validation";
|
||||
import { LocalData } from "@/utils/localData";
|
||||
import { BUCKET_ACTIONS } from "@/store/modules/buckets";
|
||||
|
||||
import HeaderedInput from '@/components/common/HeaderedInput.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import VLoader from "@/components/common/VLoader.vue";
|
||||
|
||||
import BucketIcon from "@/../static/images/objects/bucketCreation.svg";
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
HeaderedInput,
|
||||
VButton,
|
||||
BucketIcon,
|
||||
VLoader,
|
||||
},
|
||||
})
|
||||
export default class BucketCreationNameStep extends Vue {
|
||||
public name = '';
|
||||
public nameError = '';
|
||||
public isLoading = true;
|
||||
|
||||
public async mounted(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES)
|
||||
this.name = this.allBucketNames.length > 0 ? '' : 'demo-bucket';
|
||||
} catch (e) {
|
||||
await this.$notify.error(e.message)
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bucket name from input.
|
||||
*/
|
||||
public onChangeName(value: string): void {
|
||||
if (this.nameError) this.nameError = '';
|
||||
this.name = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bucket name from input.
|
||||
*/
|
||||
public onNextButtonClick(): void {
|
||||
if (!this.isBucketNameValid(this.name)) return;
|
||||
|
||||
if (this.allBucketNames.includes(this.name)) {
|
||||
this.$notify.error("Bucket with this name already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('setName', this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to buckets list view.
|
||||
*/
|
||||
public onCancelClick(): void {
|
||||
LocalData.setDemoBucketCreatedStatus();
|
||||
this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns validation status of a bucket name.
|
||||
*/
|
||||
private isBucketNameValid(name: string): boolean {
|
||||
switch (true) {
|
||||
case name.length < 3 || name.length > 63:
|
||||
this.nameError = 'Name must be not less than 3 and not more than 63 characters length';
|
||||
|
||||
return false;
|
||||
case !Validator.bucketName(name):
|
||||
this.nameError = 'Name must contain only lowercase latin characters, numbers, a hyphen or a period';
|
||||
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private get allBucketNames(): string[] {
|
||||
return this.$store.state.bucketUsageModule.allBucketNames;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bucket-name-creation {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
padding: 60px 65px 50px;
|
||||
max-width: 500px;
|
||||
background: #fcfcfc;
|
||||
box-shadow: 0 0 32px rgb(0 0 0 / 4%);
|
||||
border-radius: 20px;
|
||||
margin: 30px auto 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__functional {
|
||||
padding: 20px 0;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 26px;
|
||||
line-height: 31px;
|
||||
color: #131621;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: #354049;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 30px;
|
||||
|
||||
&__back {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .label-container__main__label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::v-deep .label-container__main {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
134
web/satellite/src/components/objects/BucketCreationProgress.vue
Normal file
134
web/satellite/src/components/objects/BucketCreationProgress.vue
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="bucket-creation-progress">
|
||||
<div class="bucket-creation-progress__item active">
|
||||
<div class="bucket-creation-progress__item__outer-circle">
|
||||
<div class="bucket-creation-progress__item__inner-circle">
|
||||
<span v-if="creationStep === BucketCreationSteps.Name">1</span>
|
||||
<check-icon v-else />
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="bucket-creation-progress__item__label" :class="{ passed: creationStep >= BucketCreationSteps.Passphrase }">Bucket</h4>
|
||||
<div class="bucket-creation-progress__item__divider" />
|
||||
</div>
|
||||
<div class="bucket-creation-progress__item" :class="{ active: creationStep >= BucketCreationSteps.Passphrase }">
|
||||
<div class="bucket-creation-progress__item__outer-circle">
|
||||
<div class="bucket-creation-progress__item__inner-circle">
|
||||
<check-icon v-if="creationStep === BucketCreationSteps.Upload" />
|
||||
<span v-else>2</span>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="bucket-creation-progress__item__label" :class="{ passed: creationStep >= BucketCreationSteps.Passphrase }">Encryption</h4>
|
||||
<div class="bucket-creation-progress__item__divider" />
|
||||
</div>
|
||||
<div class="bucket-creation-progress__item" :class="{ active: creationStep > BucketCreationSteps.Passphrase }">
|
||||
<div class="bucket-creation-progress__item__outer-circle">
|
||||
<div class="bucket-creation-progress__item__inner-circle">
|
||||
<span>3</span>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="bucket-creation-progress__item__label">Upload</h4>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { BucketCreationSteps } from "@/components/objects/BucketCreation.vue";
|
||||
|
||||
import CheckIcon from "@/../static/images/objects/check.svg";
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
CheckIcon,
|
||||
}
|
||||
})
|
||||
export default class BucketCreationProgress extends Vue {
|
||||
@Prop({ default: 0 })
|
||||
public readonly creationStep: BucketCreationSteps;
|
||||
|
||||
public readonly BucketCreationSteps = BucketCreationSteps;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bucket-creation-progress {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
&__outer-circle,
|
||||
&__inner-circle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&__outer-circle {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
background: rgb(20 20 42 / 20%);
|
||||
}
|
||||
|
||||
&__inner-circle {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
background: #b1b1b1;
|
||||
|
||||
span {
|
||||
font-family: 'font_semiBold', sans-serif;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
color: rgb(20 20 42 / 20%);
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
position: absolute;
|
||||
width: 130px;
|
||||
height: 1px;
|
||||
top: 16px;
|
||||
left: 100%;
|
||||
background: #ebeef1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
|
||||
.bucket-creation-progress__item__outer-circle {
|
||||
background: #d7e8ff;
|
||||
}
|
||||
|
||||
.bucket-creation-progress__item__inner-circle {
|
||||
background: #0149ff;
|
||||
}
|
||||
|
||||
.bucket-creation-progress__item__label {
|
||||
color: #0149ff;
|
||||
|
||||
&.passed {
|
||||
color: #14142a !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -5,7 +5,7 @@
|
||||
<div class="buckets-view">
|
||||
<div class="buckets-view__title-area">
|
||||
<h1 class="buckets-view__title-area__title" aria-roledescription="title">Buckets</h1>
|
||||
<div class="buckets-view__title-area__button" :class="{ disabled: isLoading }" @click="showCreateBucketPopup">
|
||||
<div class="buckets-view__title-area__button" :class="{ disabled: isLoading }" @click="onNewBucketButtonClick">
|
||||
<BucketIcon />
|
||||
<p class="buckets-view__title-area__button__label">New Bucket</p>
|
||||
</div>
|
||||
@ -62,20 +62,20 @@
|
||||
import { Bucket } from 'aws-sdk/clients/s3';
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
|
||||
import { MetaUtils } from '@/utils/meta';
|
||||
import { Validator } from '@/utils/validation';
|
||||
import { LocalData } from "@/utils/localData";
|
||||
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
import BucketItem from '@/components/objects/BucketItem.vue';
|
||||
import ObjectsPopup from '@/components/objects/ObjectsPopup.vue';
|
||||
|
||||
import BucketIcon from '@/../static/images/objects/bucket.svg';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||
import { DEMO_BUCKET_NAME, OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
|
||||
import { MetaUtils } from '@/utils/meta';
|
||||
import { Validator } from '@/utils/validation';
|
||||
import { LocalData } from "@/utils/localData";
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
@ -104,23 +104,15 @@ export default class BucketsView extends Vue {
|
||||
* Setup gateway credentials.
|
||||
*/
|
||||
public async mounted(): Promise<void> {
|
||||
if (!this.$store.state.objectsModule.passphrase && !this.isNewObjectsFlow) {
|
||||
await this.$router.push(RouteConfig.Buckets.with(RouteConfig.EncryptData).path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.setBucketsView();
|
||||
}
|
||||
|
||||
@Watch('selectedProjectID')
|
||||
public async handleProjectChange(): Promise<void> {
|
||||
if (this.isNewObjectsFlow) {
|
||||
this.isLoading = true;
|
||||
this.isLoading = true;
|
||||
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.CLEAR);
|
||||
await this.setBucketsView();
|
||||
}
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.CLEAR);
|
||||
await this.setBucketsView();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,7 +139,14 @@ export default class BucketsView extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wasDemoBucketCreated) await this.createDemoBucket();
|
||||
if (!this.bucketsList.length && !wasDemoBucketCreated) {
|
||||
if (this.isNewObjectsFlow) {
|
||||
await this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.createDemoBucket();
|
||||
}
|
||||
} catch (error) {
|
||||
await this.$notify.error(`Failed to setup Buckets view. ${error.message}`);
|
||||
} finally {
|
||||
@ -183,14 +182,10 @@ export default class BucketsView extends Vue {
|
||||
}
|
||||
|
||||
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
|
||||
let passphrase = '';
|
||||
if (!this.isNewObjectsFlow) {
|
||||
passphrase = this.passphrase;
|
||||
}
|
||||
this.worker.postMessage({
|
||||
'type': 'GenerateAccess',
|
||||
'apiKey': this.grantWithPermissions,
|
||||
'passphrase': passphrase,
|
||||
'passphrase': '',
|
||||
'projectID': this.$store.getters.selectedProject.id,
|
||||
'satelliteNodeURL': satelliteNodeURL,
|
||||
});
|
||||
@ -224,6 +219,12 @@ export default class BucketsView extends Vue {
|
||||
};
|
||||
}
|
||||
|
||||
public onNewBucketButtonClick(): void {
|
||||
this.isNewObjectsFlow
|
||||
? this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path)
|
||||
: this.showCreateBucketPopup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds create bucket click logic.
|
||||
*/
|
||||
@ -239,14 +240,9 @@ export default class BucketsView extends Vue {
|
||||
await this.setAccess();
|
||||
}
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.CREATE_BUCKET, this.createBucketName);
|
||||
if (this.isNewObjectsFlow) {
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
|
||||
this.createBucketName = '';
|
||||
this.isRequestProcessing = false;
|
||||
this.hideCreateBucketPopup();
|
||||
|
||||
return;
|
||||
}
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
|
||||
this.createBucketName = '';
|
||||
this.hideCreateBucketPopup();
|
||||
} catch (error) {
|
||||
const BUCKET_ALREADY_EXISTS_ERROR = 'BucketAlreadyExists';
|
||||
|
||||
@ -255,17 +251,9 @@ export default class BucketsView extends Vue {
|
||||
} else {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.isRequestProcessing = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const bucket = this.createBucketName;
|
||||
this.createBucketName = '';
|
||||
this.isRequestProcessing = false;
|
||||
|
||||
this.openBucket(bucket);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -278,25 +266,14 @@ export default class BucketsView extends Vue {
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.CREATE_DEMO_BUCKET);
|
||||
if (this.isNewObjectsFlow) {
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
|
||||
|
||||
LocalData.setDemoBucketCreatedStatus();
|
||||
this.isRequestProcessing = false;
|
||||
|
||||
return;
|
||||
}
|
||||
LocalData.setDemoBucketCreatedStatus();
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
} finally {
|
||||
this.isRequestProcessing = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LocalData.setDemoBucketCreatedStatus();
|
||||
this.isRequestProcessing = false;
|
||||
|
||||
this.openBucket(DEMO_BUCKET_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -317,17 +294,27 @@ export default class BucketsView extends Vue {
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
|
||||
this.isRequestProcessing = false;
|
||||
|
||||
return;
|
||||
} finally {
|
||||
this.isRequestProcessing = false;
|
||||
}
|
||||
|
||||
this.isRequestProcessing = false;
|
||||
this.deleteBucketName = '';
|
||||
this.hideDeleteBucketPopup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes temporary created access grant.
|
||||
*/
|
||||
public async removeTemporaryAccessGrant(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE_BY_NAME_AND_PROJECT_ID, this.FILE_BROWSER_AG_NAME);
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.CLEAR);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens utils dropdown.
|
||||
*/
|
||||
@ -389,31 +376,12 @@ export default class BucketsView extends Vue {
|
||||
this.createBucketName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes temporary created access grant.
|
||||
*/
|
||||
public async removeTemporaryAccessGrant(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE_BY_NAME_AND_PROJECT_ID, this.FILE_BROWSER_AG_NAME);
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.CLEAR);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds on bucket click. Proceeds to file browser.
|
||||
*/
|
||||
public openBucket(bucketName: string): void {
|
||||
this.$store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, bucketName);
|
||||
|
||||
if (this.isNewObjectsFlow) {
|
||||
this.$router.push(RouteConfig.Buckets.with(RouteConfig.EncryptData).path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.$router.push(RouteConfig.Buckets.with(RouteConfig.UploadFile).path);
|
||||
this.$router.push(RouteConfig.Buckets.with(RouteConfig.EncryptData).path);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -423,13 +391,6 @@ export default class BucketsView extends Vue {
|
||||
return this.$store.state.objectsModule.bucketsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns passphrase from store.
|
||||
*/
|
||||
private get passphrase(): string {
|
||||
return this.$store.state.objectsModule.passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns objects flow status from store.
|
||||
*/
|
||||
|
@ -98,13 +98,11 @@ export default class EncryptData extends Vue {
|
||||
* Sets local worker if new flow is used.
|
||||
*/
|
||||
public mounted(): void {
|
||||
if (this.isNewObjectsFlow) {
|
||||
if (!this.apiKey) {
|
||||
this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path)
|
||||
}
|
||||
|
||||
this.setWorker();
|
||||
if (!this.apiKey) {
|
||||
this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path)
|
||||
}
|
||||
|
||||
this.setWorker();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,27 +140,19 @@ export default class EncryptData extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
const keyToBeStored = await result.toString('hex');
|
||||
const keyToBeStored = result.toString('hex');
|
||||
|
||||
await LocalData.setUserIDPassSalt(this.$store.getters.user.id, keyToBeStored, SALT);
|
||||
await this.$store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, this.passphrase);
|
||||
|
||||
if (this.isNewObjectsFlow) {
|
||||
try {
|
||||
await this.setAccess();
|
||||
await this.$router.push(RouteConfig.UploadFile.path);
|
||||
} catch (e) {
|
||||
await this.$notify.error(e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.setAccess();
|
||||
await this.$router.push(RouteConfig.UploadFile.path);
|
||||
} catch (e) {
|
||||
await this.$notify.error(e.message);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
await this.$router.push(RouteConfig.BucketsManagement.path);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -242,13 +232,6 @@ export default class EncryptData extends Vue {
|
||||
private get bucket(): string {
|
||||
return this.$store.state.objectsModule.fileComponentBucketName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns objects flow status from store.
|
||||
*/
|
||||
private get isNewObjectsFlow(): string {
|
||||
return this.$store.state.appStateModule.isNewObjectsFlow;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -22,8 +22,8 @@ export default class ObjectsArea extends Vue {
|
||||
* Redirects if flow is disabled.
|
||||
*/
|
||||
public async mounted(): Promise<void> {
|
||||
const value = MetaUtils.getMetaContent('file-browser-flow-disabled');
|
||||
if (value === "true") {
|
||||
const isFileBrowserFlowDisabled = MetaUtils.getMetaContent('file-browser-flow-disabled');
|
||||
if (isFileBrowserFlowDisabled === "true") {
|
||||
await this.$router.push(RouteConfig.ProjectDashboard.path);
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ import RegistrationSuccess from "@/components/common/RegistrationSuccess.vue";
|
||||
import SuccessScreen from "@/components/onboardingTour/steps/cliFlow/SuccessScreen.vue";
|
||||
import AGName from "@/components/onboardingTour/steps/cliFlow/AGName.vue";
|
||||
import AGPermissions from "@/components/onboardingTour/steps/cliFlow/AGPermissions.vue";
|
||||
import BucketCreation from "@/components/objects/BucketCreation.vue";
|
||||
|
||||
import { NavigationLink } from '@/types/navigation';
|
||||
import { MetaUtils } from "@/utils/meta";
|
||||
@ -121,6 +122,7 @@ export abstract class RouteConfig {
|
||||
public static BucketsManagement = new NavigationLink('management', 'Buckets Management');
|
||||
public static UploadFile = new NavigationLink('upload/', 'Objects Upload');
|
||||
public static UploadFileChildren = new NavigationLink('*', 'Objects Upload Children');
|
||||
public static BucketCreation = new NavigationLink('creation', 'Bucket Creation')
|
||||
}
|
||||
|
||||
const isNewProjectDashboard = MetaUtils.getMetaContent('new-project-dashboard') === 'true';
|
||||
@ -419,6 +421,11 @@ export const router = new Router({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: RouteConfig.BucketCreation.path,
|
||||
name: RouteConfig.BucketCreation.name,
|
||||
component: BucketCreation,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -95,7 +95,6 @@ export default store;
|
||||
store and the router. Many of the tests require router, however, this implementation
|
||||
relies on store state for the routing behavior.
|
||||
*/
|
||||
|
||||
router.beforeEach(async (to, _, next) => {
|
||||
if (!to.path.includes(RouteConfig.UploadFile.path) && !store.state.appStateModule.appState.isUploadCancelPopupVisible) {
|
||||
const areUploadsInProgress: boolean = await store.dispatch(OBJECTS_ACTIONS.CHECK_ONGOING_UPLOADS, to.path);
|
||||
@ -127,13 +126,7 @@ router.beforeEach(async (to, _, next) => {
|
||||
}
|
||||
|
||||
if (navigateToDefaultSubTab(to.matched, RouteConfig.Buckets)) {
|
||||
if (store.state.appStateModule.isNewObjectsFlow) {
|
||||
next(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
next(RouteConfig.Buckets.with(RouteConfig.EncryptData).path);
|
||||
next(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
17
web/satellite/static/images/objects/bucketCreation.svg
Normal file
17
web/satellite/static/images/objects/bucketCreation.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<svg width="267" height="80" viewBox="0 0 267 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M192 38.8574H206V52.8574H192V38.8574Z" fill="#00E567"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92 0H102V10H92V0Z" fill="#FF458B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M209.143 18.2861H215.143V24.2861H209.143V18.2861Z" fill="#FF458B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M46.8574 18.5713H52.8574V24.5713H46.8574V18.5713Z" fill="#00E567"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M261 10H267V16H261V10Z" fill="#0149FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 51H6V57H0V51Z" fill="#0149FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M239.857 33.1426H245.857V39.1426H239.857V33.1426Z" fill="#FFC600"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M63.1426 47.8574H81.1426V65.8574H63.1426V47.8574Z" fill="#FFC600"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M164.285 8H178.285V22H164.285V8Z" fill="#0149FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M161.578 24.585H104L112.637 73.151H112.655C113.114 76.9613 121.952 80.0004 132.789 80.0004C143.626 80.0004 152.464 76.9613 152.923 73.151H152.941L161.578 24.585Z" fill="#0149FF"/>
|
||||
<ellipse cx="132.789" cy="24.2736" rx="28.789" ry="10.2736" fill="#00127F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M134 34.5382C133.598 34.5442 133.195 34.5472 132.789 34.5472C126.524 34.5472 120.726 33.833 116 32.6203V24.4287H134V34.5382Z" fill="#00E567"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M127.143 3.85742H141.143V17.8574H127.143V3.85742Z" fill="#FFC600"/>
|
||||
<circle cx="165.713" cy="34.2857" r="18.2857" fill="#D9E0EE"/>
|
||||
<path d="M165.842 25C169.495 25 172.463 27.9314 172.522 31.5699L172.523 31.6804L172.523 33.1021C172.849 33.3342 173.113 33.6345 173.306 33.9955L173.347 34.0757C173.563 34.5047 173.68 34.9704 173.685 36.1098V39.3616C173.685 40.6268 173.553 41.0855 173.306 41.5481C173.058 42.0106 172.695 42.3736 172.233 42.6209L172.153 42.6626C171.724 42.8785 171.258 42.9952 170.118 42.9999L161.638 43C160.373 43 159.914 42.8683 159.452 42.6209C158.989 42.3736 158.626 42.0106 158.379 41.5481L158.351 41.4936C158.126 41.0572 158.005 40.596 158 39.4337V36.1819C158 34.9168 158.132 34.458 158.379 33.9955C158.572 33.6345 158.836 33.3342 159.162 33.1021L159.162 31.6804C159.162 27.9909 162.153 25 165.842 25ZM170.046 34.1867L161.573 34.1869L161.406 34.1882C160.684 34.1973 160.46 34.2472 160.227 34.3716C160.051 34.4658 159.922 34.5943 159.828 34.7704L159.802 34.8216C159.696 35.039 159.652 35.2848 159.644 35.9555L159.643 36.1819L159.643 39.427L159.645 39.5938C159.654 40.3165 159.704 40.5405 159.828 40.7731C159.922 40.9493 160.051 41.0777 160.227 41.172L160.278 41.1981C160.495 41.3035 160.741 41.3481 161.412 41.3557L161.638 41.3568L170.163 41.3566C170.974 41.3524 171.214 41.3023 171.458 41.172C171.634 41.0777 171.762 40.9493 171.857 40.7731L171.883 40.722C171.988 40.5046 172.033 40.2587 172.04 39.588L172.041 39.3616L172.041 36.065C172.037 35.2542 171.987 35.0141 171.857 34.7704C171.762 34.5943 171.634 34.4658 171.458 34.3716L171.407 34.3455C171.189 34.2401 170.943 34.1954 170.273 34.1879L170.046 34.1867ZM165.992 35.9793C166.734 35.9793 167.336 36.5812 167.336 37.3237C167.336 37.7567 167.131 38.1419 166.813 38.3878L166.813 38.8921C166.813 39.3459 166.445 39.7137 165.992 39.7137C165.538 39.7137 165.17 39.3459 165.17 38.8921L165.17 38.3879C164.852 38.1421 164.647 37.7568 164.647 37.3237C164.647 36.5812 165.249 35.9793 165.992 35.9793ZM165.842 26.6432C163.088 26.6432 160.85 28.8535 160.806 31.5971L160.805 31.6804L160.805 32.5697C161.021 32.5537 161.271 32.5449 161.566 32.5437L170.046 32.5436C170.373 32.5436 170.646 32.5524 170.88 32.5697L170.88 31.6804C170.88 28.8984 168.624 26.6432 165.842 26.6432Z" fill="#00127F"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
3
web/satellite/static/images/objects/check.svg
Normal file
3
web/satellite/static/images/objects/check.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="13" height="10" viewBox="0 0 13 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2621 1.28859C12.6919 1.71795 12.6922 2.41443 12.2628 2.84423L5.66212 9.45156C5.45586 9.65802 5.17602 9.77406 4.88419 9.77413C4.59235 9.7742 4.31245 9.6583 4.10609 9.45195L1.08878 6.43464C0.659207 6.00506 0.659207 5.30858 1.08878 4.879C1.51836 4.44943 2.21484 4.44943 2.64442 4.879L4.88352 7.11811L10.7064 1.28937C11.1358 0.859577 11.8323 0.859229 12.2621 1.28859Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 540 B |
3
web/satellite/static/images/objects/fingerprint.svg
Normal file
3
web/satellite/static/images/objects/fingerprint.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.0174 8.74399C8.37302 8.74399 8.66294 9.02524 8.67688 9.37744L8.6774 9.40399V15.0398C8.6774 15.4043 8.38191 15.6998 8.0174 15.6998C7.66178 15.6998 7.37185 15.4186 7.35792 15.0663L7.3574 15.0398V9.40399C7.3574 9.03948 7.65289 8.74399 8.0174 8.74399ZM8.0174 6.21168C9.7314 6.21168 11.1241 7.58726 11.1518 9.29466L11.1522 9.3465V13.3062C11.1522 13.6707 10.8567 13.9662 10.4922 13.9662C10.1366 13.9662 9.84667 13.6849 9.83274 13.3327L9.83221 13.3062V9.3465C9.83221 8.3442 9.01969 7.53168 8.0174 7.53168C7.02779 7.53168 6.22319 8.32376 6.20298 9.30853L6.20259 9.3465V13.3062C6.20259 13.6707 5.90709 13.9662 5.54259 13.9662C5.18697 13.9662 4.89704 13.6849 4.88311 13.3327L4.88259 13.3062V9.3465C4.88259 7.61519 6.28609 6.21168 8.0174 6.21168ZM8.0174 3.73969C11.0082 3.73969 13.4384 6.13999 13.4867 9.1193L13.4875 9.20976V12.4165C13.4875 12.781 13.192 13.0765 12.8275 13.0765C12.4718 13.0765 12.1819 12.7952 12.168 12.443L12.1675 12.4165V9.20976C12.1675 6.91774 10.3094 5.05969 8.0174 5.05969C5.7483 5.05969 3.90454 6.88076 3.86789 9.14113L3.86734 9.20976V12.4165C3.86734 12.781 3.57184 13.0765 3.20734 13.0765C2.85172 13.0765 2.56179 12.7952 2.54786 12.443L2.54734 12.4165V9.20976C2.54734 6.18872 4.99637 3.73969 8.0174 3.73969ZM8.0174 1.2998C11.1986 1.2998 13.9498 3.05599 15.1723 5.8803C15.3171 6.21482 15.1633 6.60337 14.8287 6.74817C14.4942 6.89296 14.1057 6.73915 13.9609 6.40464C12.9502 4.06966 10.6789 2.6198 8.0174 2.6198C5.35667 2.6198 3.08401 4.06917 2.06496 6.40627C1.91927 6.74039 1.5303 6.89315 1.19618 6.74746C0.86205 6.60177 0.709292 6.2128 0.854983 5.87867C2.08643 3.05446 4.83779 1.2998 8.0174 1.2998Z" fill="#56606D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
3
web/satellite/static/images/objects/key.svg
Normal file
3
web/satellite/static/images/objects/key.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.302 15.0119L1.31054 15.1989C1.04555 15.2154 0.817303 15.014 0.800741 14.749C0.799493 14.7291 0.799493 14.709 0.800741 14.6891L0.987706 11.6976C0.995013 11.5807 1.04475 11.4705 1.12757 11.3877L4.96813 7.547L4.95363 7.49862C4.43544 5.7291 4.88296 3.78823 6.18759 2.42043L6.23044 2.37603L6.27485 2.33108C8.31655 0.289379 11.6268 0.289379 13.6685 2.33108C15.7102 4.37278 15.7102 7.68304 13.6685 9.72474C12.3113 11.0819 10.3515 11.5653 8.55862 11.0625L8.50084 11.0459L8.4525 11.0315L4.61195 14.872C4.52912 14.9548 4.4189 15.0046 4.302 15.0119ZM2.1797 13.82L3.90208 13.7123L8.11875 9.49554L8.52311 9.65669C9.96195 10.2301 11.6194 9.90415 12.7337 8.78991C14.2591 7.2645 14.2591 4.79132 12.7337 3.26591C11.2083 1.7405 8.73509 1.7405 7.20968 3.26591C6.10813 4.36746 5.77699 5.99996 6.32366 7.42725L6.34286 7.47638L6.50399 7.88072L2.28738 12.0976L2.1797 13.82ZM9.58727 6.41233C10.1036 6.92862 10.9406 6.92862 11.4569 6.41233C11.9732 5.89603 11.9732 5.05896 11.4569 4.54266C10.9406 4.02637 10.1036 4.02637 9.58727 4.54266C9.07097 5.05896 9.07097 5.89603 9.58727 6.41233Z" fill="#003DC1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Loading…
Reference in New Issue
Block a user