web/satellite: introduce access grants pinia module

Added new access grants pinia module.

Change-Id: Icb04d6e77ecc19797e250279cbe07e333596d816
This commit is contained in:
Vitalii 2023-03-22 11:46:34 +02:00 committed by Storj Robot
parent 7c68b51c82
commit fd28a665c7
5 changed files with 332 additions and 121 deletions

View File

@ -14,7 +14,7 @@ import { ProjectsApiGql } from '@/api/projects';
import { notProjectRelatedRoutes, RouteConfig, router } from '@/router';
import { AccessGrantsState, makeAccessGrantsModule } from '@/store/modules/accessGrants';
import { appStateModule } from '@/store/modules/appState';
import { makeBucketsModule } from '@/store/modules/buckets';
import { BucketsState, makeBucketsModule } from '@/store/modules/buckets';
import { makeNotificationsModule, NotificationsState } from '@/store/modules/notifications';
import { makeObjectsModule, OBJECTS_ACTIONS, ObjectsState } from '@/store/modules/objects';
import { makePaymentsModule, PaymentsState } from '@/store/modules/payments';
@ -54,6 +54,7 @@ export interface ModulesState {
usersModule: User;
projectsModule: ProjectsState;
objectsModule: ObjectsState;
bucketUsageModule: BucketsState;
files: FilesState;
}

View File

@ -0,0 +1,225 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
import { defineStore } from 'pinia';
import { computed, reactive } from 'vue';
import {
AccessGrant,
AccessGrantCursor,
AccessGrantsOrderBy,
AccessGrantsPage,
DurationPermission,
EdgeCredentials,
} from '@/types/accessGrants';
import { SortDirection } from '@/types/common';
import { AccessGrantsApiGql } from '@/api/accessGrants';
import { useProjectsStore } from '@/store/modules/projectsStore';
class AccessGrantsState {
public cursor: AccessGrantCursor = new AccessGrantCursor();
public page: AccessGrantsPage = new AccessGrantsPage();
public selectedAccessGrantsIds: string[] = [];
public selectedBucketNames: string[] = [];
public permissionNotBefore: Date | null = null;
public permissionNotAfter: Date | null = null;
public isDownload = true;
public isUpload = true;
public isList = true;
public isDelete = true;
public edgeCredentials: EdgeCredentials = new EdgeCredentials();
public accessGrantsWebWorker: Worker | null = null;
public isAccessGrantsWebWorkerReady = false;
}
export const useAccessGrantsStore = defineStore('accessGrants', () => {
const api = new AccessGrantsApiGql();
const { selectedProject } = useProjectsStore();
const state = reactive<AccessGrantsState>(new AccessGrantsState());
async function startWorker(): Promise<void> {
const worker = new Worker(new URL('@/utils/accessGrant.worker.js', import.meta.url), { type: 'module' });
worker.postMessage({ 'type': 'Setup' });
const event: MessageEvent = await new Promise(resolve => worker.onmessage = resolve);
if (event.data.error) {
throw new Error(event.data.error);
}
if (event.data !== 'configured') {
throw new Error('Failed to configure access grants web worker');
}
worker.onerror = (error: ErrorEvent) => {
throw new Error(`Failed to configure access grants web worker. ${error.message}`);
};
state.accessGrantsWebWorker = worker;
state.isAccessGrantsWebWorkerReady = true;
}
function stopWorker(): void {
state.accessGrantsWebWorker?.terminate();
state.accessGrantsWebWorker = null;
state.isAccessGrantsWebWorkerReady = false;
}
async function fetchAccessGrants(pageNumber: number): Promise<AccessGrantsPage> {
state.cursor.page = pageNumber;
const accessGrantsPage: AccessGrantsPage = await api.get(selectedProject.id, state.cursor);
state.page = accessGrantsPage;
state.page.accessGrants = state.page.accessGrants.map(accessGrant => {
if (state.selectedAccessGrantsIds.includes(accessGrant.id)) {
accessGrant.isSelected = true;
}
return accessGrant;
});
return accessGrantsPage;
}
async function createAccessGrant(name: string): Promise<AccessGrant> {
return await api.create(selectedProject.id, name);
}
async function deleteAccessGrants(): Promise<void> {
await api.delete(state.selectedAccessGrantsIds);
}
async function deleteAccessGrantByNameAndProjectID(name: string): Promise<void> {
await api.deleteByNameAndProjectID(name, selectedProject.id);
}
async function getEdgeCredentials(accessGrant: string, optionalURL: string, isPublic: boolean): Promise<EdgeCredentials> {
const credentials: EdgeCredentials = await api.getGatewayCredentials(accessGrant, optionalURL, isPublic);
state.edgeCredentials = credentials;
return credentials;
}
function setAccessGrantsSearchQuery(query: string): void {
state.cursor.search = query;
}
function setAccessGrantsSortingBy(order: AccessGrantsOrderBy): void {
state.cursor.order = order;
}
function setAccessGrantsSortingDirection(direction: SortDirection): void {
state.cursor.orderDirection = direction;
}
function setAccessGrantsDurationPermission(permission: DurationPermission): void {
state.permissionNotBefore = permission.notBefore;
state.permissionNotAfter = permission.notAfter;
}
function toggleAccessGrantsSelection(accessGrant: AccessGrant): void {
if (!state.selectedAccessGrantsIds.includes(accessGrant.id)) {
state.page.accessGrants.forEach((grant: AccessGrant) => {
if (grant.id === accessGrant.id) {
grant.isSelected = true;
}
});
state.selectedAccessGrantsIds.push(accessGrant.id);
return;
}
state.page.accessGrants.forEach((grant: AccessGrant) => {
if (grant.id === accessGrant.id) {
grant.isSelected = false;
}
});
state.selectedAccessGrantsIds = state.selectedAccessGrantsIds.filter(accessGrantId => {
return accessGrant.id !== accessGrantId;
});
}
function toggleBucketSelection(bucketName: string): void {
if (!state.selectedBucketNames.includes(bucketName)) {
state.selectedBucketNames.push(bucketName);
return;
}
state.selectedBucketNames = state.selectedBucketNames.filter(name => {
return bucketName !== name;
});
}
function toggleIsDownloadPermission(): void {
state.isDownload = !state.isDownload;
}
function toggleIsUploadPermission(): void {
state.isUpload = !state.isUpload;
}
function toggleIsListPermission(): void {
state.isList = !state.isList;
}
function toggleIsDeletePermission(): void {
state.isDelete = !state.isDelete;
}
function clearAccessGrantsSelection(): void {
state.selectedBucketNames = [];
state.selectedAccessGrantsIds = [];
state.page.accessGrants = state.page.accessGrants.map((accessGrant: AccessGrant) => {
accessGrant.isSelected = false;
return accessGrant;
});
}
function clearAccessGrants(): void {
state.cursor = new AccessGrantCursor();
state.page = new AccessGrantsPage();
state.selectedAccessGrantsIds = [];
state.selectedBucketNames = [];
state.permissionNotBefore = null;
state.permissionNotAfter = null;
state.edgeCredentials = new EdgeCredentials();
state.isDownload = true;
state.isUpload = true;
state.isList = true;
state.isDelete = true;
state.accessGrantsWebWorker = null;
state.isAccessGrantsWebWorkerReady = false;
}
const selectedAccessGrants = computed((): AccessGrant[] => {
return state.page.accessGrants.filter((grant: AccessGrant) => grant.isSelected);
});
return {
accessGrantsState: state,
startWorker,
stopWorker,
fetchAccessGrants,
createAccessGrant,
deleteAccessGrants,
deleteAccessGrantByNameAndProjectID,
getEdgeCredentials,
setAccessGrantsSearchQuery,
setAccessGrantsSortingBy,
setAccessGrantsSortingDirection,
setAccessGrantsDurationPermission,
toggleAccessGrantsSelection,
toggleBucketSelection,
toggleIsDownloadPermission,
toggleIsUploadPermission,
toggleIsListPermission,
toggleIsDeletePermission,
clearAccessGrantsSelection,
clearAccessGrants,
selectedAccessGrants,
};
});

View File

@ -18,42 +18,38 @@ export class BucketsState {
}
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 state = reactive<BucketsState>(new BucketsState());
const api: BucketsApi = new BucketsApiGql();
function setBucketsSearch(search: string): void {
bucketsState.cursor.search = search;
state.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);
state.allBucketNames = [];
state.cursor = new BucketCursor('', BUCKETS_PAGE_LIMIT, FIRST_PAGE);
state.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 { projectsState } = useProjectsStore();
const projectID = projectsState.selectedProject.id;
const before = new Date();
bucketsState.cursor.page = page;
state.cursor.page = page;
bucketsState.page = await api.get(projectID, before, bucketsState.cursor);
state.page = await api.get(projectID, before, state.cursor);
}
async function fetchAllBucketsNames(): Promise<void> {
const { projectsStore } = useProjectsStore();
const projectID = projectsStore.selectedProject.id;
const { projectsState } = useProjectsStore();
const projectID = projectsState.selectedProject.id;
bucketsState.allBucketNames = await api.getAllBucketNames(projectID);
state.allBucketNames = await api.getAllBucketNames(projectID);
}
return {
bucketsState,
bucketsState: state,
setBucketsSearch,
clearBucketsState,
fetchBuckets,

View File

@ -1,4 +1,4 @@
// Copyright (C) 2019 Storj Labs, Inc.
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
import { defineStore } from 'pinia';
@ -37,19 +37,7 @@ export class ProjectsState {
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 state = reactive<ProjectsState>(new ProjectsState());
const api: ProjectsApi = new ProjectsApiGql();
@ -62,50 +50,50 @@ export const useProjectsStore = defineStore('projects', () => {
}
function setProjects(projects: Project[]): void {
projectsStore.projects = projects;
state.projects = projects;
if (!projectsStore.selectedProject.id) {
if (!state.selectedProject.id) {
return;
}
const projectsCount = projectsStore.projects.length;
const projectsCount = state.projects.length;
for (let i = 0; i < projectsCount; i++) {
const project = projectsStore.projects[i];
const project = state.projects[i];
if (project.id !== projectsStore.selectedProject.id) {
if (project.id !== state.selectedProject.id) {
continue;
}
projectsStore.selectedProject = project;
state.selectedProject = project;
return;
}
projectsStore.selectedProject = defaultSelectedProject;
state.selectedProject = defaultSelectedProject;
}
async function fetchOwnedProjects(pageNumber: number): Promise<void> {
projectsStore.cursor.page = pageNumber;
projectsStore.cursor.limit = PROJECT_PAGE_LIMIT;
state.cursor.page = pageNumber;
state.cursor.limit = PROJECT_PAGE_LIMIT;
projectsStore.page = await api.getOwnedProjects(projectsStore.cursor);
state.page = await api.getOwnedProjects(state.cursor);
}
async function fetchDailyProjectData(payload: ProjectUsageDateRange): Promise<void> {
const usage: ProjectsStorageBandwidthDaily = await api.getDailyUsage(projectsStore.selectedProject.id, payload.since, payload.before);
const usage: ProjectsStorageBandwidthDaily = await api.getDailyUsage(state.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;
state.allocatedBandwidthChartData = usage.allocatedBandwidth;
state.settledBandwidthChartData = usage.settledBandwidth;
state.storageChartData = usage.storage;
state.chartDataSince = payload.since;
state.chartDataBefore = payload.before;
}
async function createProject(createProjectFields: ProjectFields): Promise<string> {
const createdProject = await api.create(createProjectFields);
projectsStore.projects.push(createdProject);
state.projects.push(createdProject);
return createdProject.id;
}
@ -127,100 +115,100 @@ export const useProjectsStore = defineStore('projects', () => {
}
function selectProject(projectID: string): void {
const selected = projectsStore.projects.find((project: Project) => project.id === projectID);
const selected = state.projects.find((project: Project) => project.id === projectID);
if (!selected) {
return;
}
projectsStore.selectedProject = selected;
state.selectedProject = selected;
}
async function updateProjectName(fieldsToUpdate: ProjectFields): Promise<void> {
const project = new ProjectFields(
fieldsToUpdate.name,
projectsStore.selectedProject.description,
projectsStore.selectedProject.id,
state.selectedProject.description,
state.selectedProject.id,
);
const limit = new ProjectLimits(
projectsStore.currentLimits.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
projectsStore.currentLimits.storageLimit,
projectsStore.currentLimits.storageUsed,
state.currentLimits.bandwidthLimit,
state.currentLimits.bandwidthUsed,
state.currentLimits.storageLimit,
state.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
await api.update(state.selectedProject.id, project, limit);
projectsStore.selectedProject.name = fieldsToUpdate.name;
state.selectedProject.name = fieldsToUpdate.name;
}
async function updateProjectDescription(fieldsToUpdate: ProjectFields): Promise<void> {
const project = new ProjectFields(
projectsStore.selectedProject.name,
state.selectedProject.name,
fieldsToUpdate.description,
projectsStore.selectedProject.id,
state.selectedProject.id,
);
const limit = new ProjectLimits(
projectsStore.currentLimits.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
projectsStore.currentLimits.storageLimit,
projectsStore.currentLimits.storageUsed,
state.currentLimits.bandwidthLimit,
state.currentLimits.bandwidthUsed,
state.currentLimits.storageLimit,
state.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
await api.update(state.selectedProject.id, project, limit);
projectsStore.selectedProject.description = fieldsToUpdate.description;
state.selectedProject.description = fieldsToUpdate.description;
}
async function updateProjectStorageLimit(limitsToUpdate: ProjectLimits): Promise<void> {
const project = new ProjectFields(
projectsStore.selectedProject.name,
projectsStore.selectedProject.description,
projectsStore.selectedProject.id,
state.selectedProject.name,
state.selectedProject.description,
state.selectedProject.id,
);
const limit = new ProjectLimits(
projectsStore.currentLimits.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
state.currentLimits.bandwidthLimit,
state.currentLimits.bandwidthUsed,
limitsToUpdate.storageLimit,
projectsStore.currentLimits.storageUsed,
state.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
await api.update(state.selectedProject.id, project, limit);
projectsStore.currentLimits.storageLimit = limitsToUpdate.storageLimit;
state.currentLimits.storageLimit = limitsToUpdate.storageLimit;
}
async function updateProjectBandwidthLimit(limitsToUpdate: ProjectLimits): Promise<void> {
const project = new ProjectFields(
projectsStore.selectedProject.name,
projectsStore.selectedProject.description,
projectsStore.selectedProject.id,
state.selectedProject.name,
state.selectedProject.description,
state.selectedProject.id,
);
const limit = new ProjectLimits(
limitsToUpdate.bandwidthLimit,
projectsStore.currentLimits.bandwidthUsed,
projectsStore.currentLimits.storageLimit,
projectsStore.currentLimits.storageUsed,
state.currentLimits.bandwidthUsed,
state.currentLimits.storageLimit,
state.currentLimits.storageUsed,
);
await api.update(projectsStore.selectedProject.id, project, limit);
await api.update(state.selectedProject.id, project, limit);
projectsStore.currentLimits.bandwidthLimit = limitsToUpdate.bandwidthLimit;
state.currentLimits.bandwidthLimit = limitsToUpdate.bandwidthLimit;
}
async function deleteProject(projectID: string): Promise<void> {
await api.delete(projectID);
projectsStore.projects = projectsStore.projects.filter(project => project.id !== projectID);
state.projects = state.projects.filter(project => project.id !== projectID);
if (projectsStore.selectedProject.id === projectID) {
projectsStore.selectedProject = new Project();
if (state.selectedProject.id === projectID) {
state.selectedProject = new Project();
}
}
async function fetchProjectLimits(projectID: string): Promise<void> {
projectsStore.currentLimits = await api.getLimits(projectID);
state.currentLimits = await api.getLimits(projectID);
}
async function fetchTotalLimits(): Promise<void> {
projectsStore.totalLimits = await api.getTotalLimits();
state.totalLimits = await api.getTotalLimits();
}
async function getProjectSalt(projectID: string): Promise<string> {
@ -228,20 +216,20 @@ export const useProjectsStore = defineStore('projects', () => {
}
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();
state.projects = [];
state.selectedProject = defaultSelectedProject;
state.currentLimits = new ProjectLimits();
state.totalLimits = new ProjectLimits();
state.storageChartData = [];
state.allocatedBandwidthChartData = [];
state.settledBandwidthChartData = [];
state.chartDataSince = new Date();
state.chartDataBefore = new Date();
}
const projects = computed(() => {
return projectsStore.projects.map((project: Project) => {
if (project.id === projectsStore.selectedProject.id) {
return state.projects.map((project: Project) => {
if (project.id === state.selectedProject.id) {
project.isSelected = true;
}
@ -250,8 +238,8 @@ export const useProjectsStore = defineStore('projects', () => {
});
const projectsWithoutSelected = computed(() => {
return projectsStore.projects.filter((project: Project) => {
return project.id !== projectsStore.selectedProject.id;
return state.projects.filter((project: Project) => {
return project.id !== state.selectedProject.id;
});
});
@ -260,7 +248,7 @@ export const useProjectsStore = defineStore('projects', () => {
const { usersState } = useUsersStore();
projectsStore.projects.forEach((project: Project) => {
state.projects.forEach((project: Project) => {
if (project.ownerId === usersState.user.id) {
projectsCount++;
}
@ -269,8 +257,12 @@ export const useProjectsStore = defineStore('projects', () => {
return projectsCount;
});
const selectedProject = computed((): Project => {
return state.selectedProject;
});
return {
projectsStore,
projectsState: state,
fetchProjects,
fetchOwnedProjects,
fetchDailyProjectData,
@ -289,5 +281,6 @@ export const useProjectsStore = defineStore('projects', () => {
projects,
projectsWithoutSelected,
projectsCount,
selectedProject,
};
});

View File

@ -15,14 +15,10 @@ export class UsersState {
}
export const useUsersStore = defineStore('users', () => {
const usersState = reactive<UsersState>({
user: new User(),
userMFASecret: '',
userMFARecoveryCodes: [],
});
const state = reactive<UsersState>(new UsersState());
const userName = computed(() => {
return usersState.user.getFullName();
return state.user.getFullName();
});
const api: UsersApi = new AuthHttpApi();
@ -30,28 +26,28 @@ export const useUsersStore = defineStore('users', () => {
async function updateUserInfo(userInfo: UpdatedUser): Promise<void> {
await api.update(userInfo);
usersState.user.fullName = userInfo.fullName;
usersState.user.shortName = userInfo.shortName;
state.user.fullName = userInfo.fullName;
state.user.shortName = userInfo.shortName;
}
async function fetchUserInfo(): Promise<void> {
const user = await api.get();
usersState.user = user;
state.user = user;
if (user.projectLimit === 0) {
const limitFromConfig = MetaUtils.getMetaContent('default-project-limit');
usersState.user.projectLimit = parseInt(limitFromConfig);
state.user.projectLimit = parseInt(limitFromConfig);
return;
}
usersState.user.projectLimit = user.projectLimit;
state.user.projectLimit = user.projectLimit;
}
async function fetchFrozenStatus(): Promise<void> {
usersState.user.isFrozen = await api.getFrozenStatus();
state.user.isFrozen = await api.getFrozenStatus();
}
async function disableUserMFA(request: DisableMFARequest): Promise<void> {
@ -63,23 +59,23 @@ export const useUsersStore = defineStore('users', () => {
}
async function generateUserMFASecret(): Promise<void> {
usersState.userMFASecret = await api.generateUserMFASecret();
state.userMFASecret = await api.generateUserMFASecret();
}
async function generateUserMFARecoveryCodes(): Promise<void> {
const codes = await api.generateUserMFARecoveryCodes();
usersState.userMFARecoveryCodes = codes;
usersState.user.mfaRecoveryCodeCount = codes.length;
state.userMFARecoveryCodes = codes;
state.user.mfaRecoveryCodeCount = codes.length;
}
function clearUserInfo() {
usersState.user = new User();
usersState.user.projectLimit = 1;
state.user = new User();
state.user.projectLimit = 1;
}
return {
usersState,
usersState: state,
userName,
updateUserInfo,
fetchUserInfo,