web/satellite: pinia package added

added to replace vuex in future
buckets, projects and users modules pinia analogues created
have no pretty and straight-forward ways to work with option api + ts files so it is better to use with composition api components

Change-Id: Ia8acc491c0e76e01bf6d533747d186257680e5c9
This commit is contained in:
NickolaiYurchenko 2022-11-02 17:25:44 +02:00 committed by Storj Robot
parent c07f016f57
commit 3c8f68fed9
6 changed files with 528 additions and 2 deletions

View File

@ -23,6 +23,7 @@
"graphql-tag": "2.12.6",
"load-script": "1.0.0",
"pbkdf2": "3.1.2",
"pinia": "^2.0.23",
"pretty-bytes": "5.6.0",
"qrcode": "1.5.0",
"stream-browserify": "3.0.0",
@ -4036,6 +4037,11 @@
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
"dev": true
},
"node_modules/@vue/devtools-api": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"node_modules/@vue/eslint-config-typescript": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz",
@ -14220,6 +14226,56 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pinia": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==",
"dependencies": {
"@vue/devtools-api": "^6.4.4",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pirates": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@ -17570,7 +17626,7 @@
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
"dev": true,
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -22135,6 +22191,11 @@
}
}
},
"@vue/devtools-api": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"@vue/eslint-config-typescript": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz",
@ -29788,6 +29849,23 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
"pinia": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==",
"requires": {
"@vue/devtools-api": "^6.4.4",
"vue-demi": "*"
},
"dependencies": {
"vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"requires": {}
}
}
},
"pirates": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@ -32225,7 +32303,7 @@
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
"dev": true
"devOptional": true
},
"unbox-primitive": {
"version": "1.0.2",

View File

@ -28,6 +28,7 @@
"graphql-tag": "2.12.6",
"load-script": "1.0.0",
"pbkdf2": "3.1.2",
"pinia": "2.0.23",
"pretty-bytes": "5.6.0",
"qrcode": "1.5.0",
"stream-browserify": "3.0.0",

View File

@ -4,6 +4,7 @@
import Vue from 'vue';
import VueClipboard from 'vue-clipboard2';
import VueSanitize from 'vue-sanitize';
import { createPinia, PiniaVuePlugin } from 'pinia';
import App from './App.vue';
import { router } from './router';
@ -23,6 +24,8 @@ Vue.config.productionTip = false;
Vue.use(new NotificatorPlugin(store));
Vue.use(VueClipboard);
Vue.use(VueSanitize);
Vue.use(PiniaVuePlugin);
const pinia = createPinia();
/**
* Click outside handlers.
@ -87,5 +90,6 @@ Vue.filter('bytesToBase10String', (amountInBytes: number): string => {
new Vue({
router,
store,
pinia,
render: (h) => h(App),
}).$mount('#app');

View File

@ -0,0 +1,62 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
import { defineStore } from 'pinia';
import { reactive } from 'vue';
import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
import { BucketsApiGql } from '@/api/buckets';
import { useProjectsStore } from '@/store/modules/projectsStore';
const BUCKETS_PAGE_LIMIT = 7;
const FIRST_PAGE = 1;
export class BucketsState {
public allBucketNames: string[] = [];
public cursor: BucketCursor = { limit: BUCKETS_PAGE_LIMIT, search: '', page: FIRST_PAGE };
public page: BucketPage = { buckets: new Array<Bucket>(), currentPage: 1, pageCount: 1, offset: 0, limit: BUCKETS_PAGE_LIMIT, search: '', totalCount: 0 };
}
export const useBucketsStore = defineStore('buckets', () => {
const bucketsState = reactive<BucketsState>({
allBucketNames: [],
cursor: { limit: BUCKETS_PAGE_LIMIT, search: '', page: FIRST_PAGE },
page: { buckets: new Array<Bucket>(), currentPage: 1, pageCount: 1, offset: 0, limit: BUCKETS_PAGE_LIMIT, search: '', totalCount: 0 },
});
const api: BucketsApi = new BucketsApiGql();
function setBucketsSearch(search: string): void {
bucketsState.cursor.search = search;
}
function clearBucketsState(): void {
bucketsState.allBucketNames = [];
bucketsState.cursor = new BucketCursor('', BUCKETS_PAGE_LIMIT, FIRST_PAGE);
bucketsState.page = new BucketPage([], '', BUCKETS_PAGE_LIMIT, 0, 1, 1, 0);
}
async function fetchBuckets(page: number): Promise<void> {
const { projectsStore } = useProjectsStore();
const projectID = projectsStore.selectedProject.id;
const before = new Date();
bucketsState.cursor.page = page;
bucketsState.page = await api.get(projectID, before, bucketsState.cursor);
}
async function fetchAllBucketsNames(): Promise<void> {
const { projectsStore } = useProjectsStore();
const projectID = projectsStore.selectedProject.id;
bucketsState.allBucketNames = await api.getAllBucketNames(projectID);
}
return {
bucketsState,
setBucketsSearch,
clearBucketsState,
fetchBuckets,
fetchAllBucketsNames,
};
});

View File

@ -0,0 +1,293 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { defineStore } from 'pinia';
import { computed, reactive } from 'vue';
import {
DataStamp,
Project,
ProjectFields,
ProjectLimits,
ProjectsApi,
ProjectsCursor,
ProjectsPage,
ProjectsStorageBandwidthDaily,
ProjectUsageDateRange,
} from '@/types/projects';
import { ProjectsApiGql } from '@/api/projects';
import { useUsersStore } from '@/store/modules/usersStore';
const defaultSelectedProject = new Project('', '', '', '', '', true, 0);
export class ProjectsState {
public projects: Project[] = [];
public selectedProject: Project = defaultSelectedProject;
public currentLimits: ProjectLimits = new ProjectLimits();
public totalLimits: ProjectLimits = new ProjectLimits();
public cursor: ProjectsCursor = new ProjectsCursor();
public page: ProjectsPage = new ProjectsPage();
public allocatedBandwidthChartData: DataStamp[] = [];
public settledBandwidthChartData: DataStamp[] = [];
public storageChartData: DataStamp[] = [];
public chartDataSince: Date = new Date();
public chartDataBefore: Date = new Date();
}
const PROJECT_PAGE_LIMIT = 7;
export const useProjectsStore = defineStore('projects', () => {
const projectsStore = reactive<ProjectsState>({
projects: [],
selectedProject: defaultSelectedProject,
currentLimits: new ProjectLimits(),
totalLimits: new ProjectLimits(),
cursor: new ProjectsCursor(),
page: new ProjectsPage(),
allocatedBandwidthChartData: [],
settledBandwidthChartData: [],
storageChartData: [],
chartDataSince: new Date(),
chartDataBefore: new Date(),
});
const api: ProjectsApi = new ProjectsApiGql();
async function fetchProjects(): Promise<Project[]> {
const projects = await api.get();
setProjects(projects);
return projects;
}
function setProjects(projects: Project[]): void {
projectsStore.projects = projects;
if (!projectsStore.selectedProject.id) {
return;
}
const projectsCount = projectsStore.projects.length;
for (let i = 0; i < projectsCount; i++) {
const project = projectsStore.projects[i];
if (project.id !== projectsStore.selectedProject.id) {
continue;
}
projectsStore.selectedProject = project;
return;
}
projectsStore.selectedProject = defaultSelectedProject;
}
async function fetchOwnedProjects(pageNumber: number): Promise<void> {
projectsStore.cursor.page = pageNumber;
projectsStore.cursor.limit = PROJECT_PAGE_LIMIT;
projectsStore.page = await api.getOwnedProjects(projectsStore.cursor);
}
async function fetchDailyProjectData(payload: ProjectUsageDateRange): Promise<void> {
const usage: ProjectsStorageBandwidthDaily = await api.getDailyUsage(projectsStore.selectedProject.id, payload.since, payload.before);
projectsStore.allocatedBandwidthChartData = usage.allocatedBandwidth;
projectsStore.settledBandwidthChartData = usage.settledBandwidth;
projectsStore.storageChartData = usage.storage;
projectsStore.chartDataSince = payload.since;
projectsStore.chartDataBefore = payload.before;
}
async function createProject(createProjectFields: ProjectFields): Promise<string> {
const createdProject = await api.create(createProjectFields);
projectsStore.projects.push(createdProject);
return createdProject.id;
}
async function createDefaultProject(): Promise<void> {
const UNTITLED_PROJECT_NAME = 'My First Project';
const UNTITLED_PROJECT_DESCRIPTION = '___';
const { usersState } = useUsersStore();
const project = new ProjectFields(
UNTITLED_PROJECT_NAME,
UNTITLED_PROJECT_DESCRIPTION,
usersState.user.id,
);
const createdProjectId = await createProject(project);
selectProject(createdProjectId);
}
function selectProject(projectID: string): void {
const selected = projectsStore.projects.find((project: Project) => project.id === projectID);
if (!selected) {
return;
}
projectsStore.selectedProject = selected;
}
async function updateProjectName(fieldsToUpdate: ProjectFields): Promise<void> {
const project = new ProjectFields(
fieldsToUpdate.name,
projectsStore.selectedProject.description,
projectsStore.selectedProject.id,
);
const limit = new ProjectLimits(
projectsStore.currentLimits.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
projectsStore.currentLimits.storageLimit,
projectsStore.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
projectsStore.selectedProject.name = fieldsToUpdate.name;
}
async function updateProjectDescription(fieldsToUpdate: ProjectFields): Promise<void> {
const project = new ProjectFields(
projectsStore.selectedProject.name,
fieldsToUpdate.description,
projectsStore.selectedProject.id,
);
const limit = new ProjectLimits(
projectsStore.currentLimits.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
projectsStore.currentLimits.storageLimit,
projectsStore.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
projectsStore.selectedProject.description = fieldsToUpdate.description;
}
async function updateProjectStorageLimit(limitsToUpdate: ProjectLimits): Promise<void> {
const project = new ProjectFields(
projectsStore.selectedProject.name,
projectsStore.selectedProject.description,
projectsStore.selectedProject.id,
);
const limit = new ProjectLimits(
projectsStore.currentLimits.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
limitsToUpdate.storageLimit,
projectsStore.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
projectsStore.currentLimits.storageLimit = limitsToUpdate.storageLimit;
}
async function updateProjectBandwidthLimit(limitsToUpdate: ProjectLimits): Promise<void> {
const project = new ProjectFields(
projectsStore.selectedProject.name,
projectsStore.selectedProject.description,
projectsStore.selectedProject.id,
);
const limit = new ProjectLimits(
limitsToUpdate.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
projectsStore.currentLimits.storageLimit,
projectsStore.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
projectsStore.currentLimits.bandwidthLimit = limitsToUpdate.bandwidthLimit;
}
async function deleteProject(projectID: string): Promise<void> {
await api.delete(projectID);
projectsStore.projects = projectsStore.projects.filter(project => project.id !== projectID);
if (projectsStore.selectedProject.id === projectID) {
projectsStore.selectedProject = new Project();
}
}
async function fetchProjectLimits(projectID: string): Promise<void> {
projectsStore.currentLimits = await api.getLimits(projectID);
}
async function fetchTotalLimits(): Promise<void> {
projectsStore.totalLimits = await api.getTotalLimits();
}
async function getProjectSalt(projectID: string): Promise<string> {
return await api.getSalt(projectID);
}
function clearProjectState(): void {
projectsStore.projects = [];
projectsStore.selectedProject = defaultSelectedProject;
projectsStore.currentLimits = new ProjectLimits();
projectsStore.totalLimits = new ProjectLimits();
projectsStore.storageChartData = [];
projectsStore.allocatedBandwidthChartData = [];
projectsStore.settledBandwidthChartData = [];
projectsStore.chartDataSince = new Date();
projectsStore.chartDataBefore = new Date();
}
const projects = computed(() => {
return projectsStore.projects.map((project: Project) => {
if (project.id === projectsStore.selectedProject.id) {
project.isSelected = true;
}
return project;
});
});
const projectsWithoutSelected = computed(() => {
return projectsStore.projects.filter((project: Project) => {
return project.id !== projectsStore.selectedProject.id;
});
});
const projectsCount = computed(() => {
let projectsCount = 0;
const { usersState } = useUsersStore();
projectsStore.projects.forEach((project: Project) => {
if (project.ownerId === usersState.user.id) {
projectsCount++;
}
});
return projectsCount;
});
return {
projectsStore,
fetchProjects,
fetchOwnedProjects,
fetchDailyProjectData,
createProject,
createDefaultProject,
selectProject,
updateProjectName,
updateProjectDescription,
updateProjectStorageLimit,
updateProjectBandwidthLimit,
deleteProject,
fetchProjectLimits,
fetchTotalLimits,
getProjectSalt,
clearProjectState,
projects,
projectsWithoutSelected,
projectsCount,
};
});

View File

@ -0,0 +1,88 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
import { defineStore } from 'pinia';
import { computed, reactive } from 'vue';
import { DisableMFARequest, UpdatedUser, User, UsersApi } from '@/types/users';
import { MetaUtils } from '@/utils/meta';
import { AuthHttpApi } from '@/api/auth';
export class UsersState {
public user: User = new User();
public userMFASecret = '';
public userMFARecoveryCodes: string[] = [];
}
export const useUsersStore = defineStore('users', () => {
const usersState = reactive<UsersState>({
user: new User(),
userMFASecret: '',
userMFARecoveryCodes: [],
});
const userName = computed(() => {
return usersState.user.getFullName();
});
const api: UsersApi = new AuthHttpApi();
async function updateUserInfo(userInfo: UpdatedUser): Promise<void> {
await api.update(userInfo);
usersState.user.fullName = userInfo.fullName;
usersState.user.shortName = userInfo.shortName;
}
async function fetchUserInfo(): Promise<void> {
const user = await api.get();
usersState.user = user;
if (user.projectLimit === 0) {
const limitFromConfig = MetaUtils.getMetaContent('default-project-limit');
usersState.user.projectLimit = parseInt(limitFromConfig);
return;
}
usersState.user.projectLimit = user.projectLimit;
}
async function disableUserMFA(request: DisableMFARequest): Promise<void> {
await api.disableUserMFA(request.passcode, request.recoveryCode);
}
async function enableUserMFA(passcode: string): Promise<void> {
await api.enableUserMFA(passcode);
}
async function generateUserMFASecret(): Promise<void> {
usersState.userMFASecret = await api.generateUserMFASecret();
}
async function generateUserMFARecoveryCodes(): Promise<void> {
const codes = await api.generateUserMFARecoveryCodes();
usersState.userMFARecoveryCodes = codes;
usersState.user.mfaRecoveryCodeCount = codes.length;
}
function clearUserInfo() {
usersState.user = new User();
usersState.user.projectLimit = 1;
}
return {
usersState,
userName,
updateUserInfo,
fetchUserInfo,
disableUserMFA,
enableUserMFA,
generateUserMFASecret,
generateUserMFARecoveryCodes,
clearUserInfo,
};
});