web/multinode: storage api, service and store module created

Change-Id: Ieb3dbcd9c967388315f5203598ff56a848477476
This commit is contained in:
NickolaiYurchenko 2021-06-18 17:48:14 +03:00 committed by Nikolay Yurchenko
parent 55754df110
commit 8686267e06
7 changed files with 276 additions and 30 deletions

View File

@ -11,7 +11,7 @@ export class BandwidthClient extends APIClient {
private readonly ROOT_PATH: string = '/api/v0/bandwidth';
/**
* Returns bandwidth information for selected node and satellite in any.
* Returns bandwidth information for selected node and satellite if any.
*
* @throws {@link BadRequestError}
* This exception is thrown if the input is not a valid.
@ -26,7 +26,7 @@ export class BandwidthClient extends APIClient {
let path = `${this.ROOT_PATH}`;
if (satelliteId) {
path += `/satellite/${satelliteId}`;
path += `/satellites/${satelliteId}`;
}
if (nodeId) {

View File

@ -0,0 +1,85 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
import { APIClient } from '@/api/index';
import { DiskSpace, Stamp } from '@/storage';
/**
* Client for storage controller of MND api.
*/
export class StorageClient extends APIClient {
private readonly ROOT_PATH: string = '/api/v0/storage';
/**
* Returns storage usage information for selected node and satellite if any.
*
* @throws {@link BadRequestError}
* This exception is thrown if the input is not a valid.
*
* @throws {@link UnauthorizedError}
* Thrown if the auth cookie is missing or invalid.
*
* @throws {@link InternalError}
* Thrown if something goes wrong on server side.
*/
public async usage(satelliteId: string | null, nodeId: string | null): Promise<Stamp[]> {
let path = `${this.ROOT_PATH}/usage`;
if (satelliteId) {
path += `/satellites/${satelliteId}`;
}
if (nodeId) {
path += `/${nodeId}`;
}
const response = await this.http.get(path);
if (!response.ok) {
await this.handleError(response);
}
const usage = await response.json() || [];
return usage.map(stamp => {
return new Stamp(stamp.atRestTotal, new Date(stamp.intervalStart));
});
}
/**
* Returns disk space information for selected node if selected.
*
* @throws {@link BadRequestError}
* This exception is thrown if the input is not a valid.
*
* @throws {@link UnauthorizedError}
* Thrown if the auth cookie is missing or invalid.
*
* @throws {@link InternalError}
* Thrown if something goes wrong on server side.
*/
public async diskSpace(nodeId: string | null): Promise<DiskSpace> {
let path = `${this.ROOT_PATH}/disk-space`;
if (nodeId) {
path += `/${nodeId}`;
}
const response = await this.http.get(path);
if (!response.ok) {
await this.handleError(response);
}
const diskSpace = await response.json();
return new DiskSpace(
diskSpace.allocated,
diskSpace.usedPieces,
diskSpace.usedTrash,
diskSpace.free,
diskSpace.available,
diskSpace.overused,
);
}
}

View File

@ -8,14 +8,17 @@ import { BandwidthClient } from '@/api/bandwidth';
import { NodesClient } from '@/api/nodes';
import { Operators as OperatorsClient } from '@/api/operators';
import { PayoutsClient } from '@/api/payouts';
import { StorageClient } from '@/api/storage';
import { BandwidthModule, BandwidthState } from '@/app/store/bandwidth';
import { NodesModule, NodesState } from '@/app/store/nodes';
import { OperatorsModule, OperatorsState } from '@/app/store/operators';
import { PayoutsModule, PayoutsState } from '@/app/store/payouts';
import { StorageModule, StorageState } from '@/app/store/storage';
import { Bandwidth } from '@/bandwidth/service';
import { Nodes } from '@/nodes/service';
import { Operators } from '@/operators';
import { Payouts } from '@/payouts/service';
import { StorageService } from '@/storage/service';
Vue.use(Vuex);
@ -27,6 +30,7 @@ export class RootState {
payouts: PayoutsState;
operators: OperatorsState;
bandwidth: BandwidthState;
storage: StorageState;
}
/**
@ -42,6 +46,7 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
payouts: PayoutsModule,
operators: OperatorsModule,
bandwidth: BandwidthModule,
storage: StorageModule,
) {
this.strict = true;
this.state = {
@ -49,12 +54,14 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
payouts: payouts.state,
bandwidth: bandwidth.state,
operators: operators.state,
storage: storage.state,
};
this.modules = {
nodes,
payouts,
bandwidth,
operators,
storage,
};
}
}
@ -68,14 +75,17 @@ const bandwidthClient = new BandwidthClient();
const bandwidthService = new Bandwidth(bandwidthClient);
const operatorsClient: OperatorsClient = new OperatorsClient();
const operatorsService: Operators = new Operators(operatorsClient);
const storageClient: StorageClient = new StorageClient();
const storageService: StorageService = new StorageService(storageClient);
// Modules
const nodesModule: NodesModule = new NodesModule(nodesService);
const payoutsModule: PayoutsModule = new PayoutsModule(payoutsService);
const bandwidthModule: BandwidthModule = new BandwidthModule(bandwidthService);
const operatorsModule: OperatorsModule = new OperatorsModule(operatorsService);
const storageModule: StorageModule = new StorageModule(storageService);
// Store
export const store: Store<RootState> = new Vuex.Store<RootState>(
new MultinodeStoreOptions(nodesModule, payoutsModule, operatorsModule, bandwidthModule),
new MultinodeStoreOptions(nodesModule, payoutsModule, operatorsModule, bandwidthModule, storageModule),
);

View File

@ -0,0 +1,87 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
import { ActionContext, ActionTree, GetterTree, Module, MutationTree } from 'vuex';
import { RootState } from '@/app/store/index';
import { DiskSpace, Stamp } from '@/storage';
import { StorageService } from '@/storage/service';
/**
* StorageState is a representation of by day and total storage usage.
*/
export class StorageState {
public usage: Stamp[] = [];
public diskSpace: DiskSpace = new DiskSpace();
}
/**
* StorageModule is a part of a global store that encapsulates all storage related logic.
*/
export class StorageModule implements Module<StorageState, RootState> {
public readonly namespaced: boolean;
public readonly state: StorageState;
public readonly getters?: GetterTree<StorageState, RootState>;
public readonly actions: ActionTree<StorageState, RootState>;
public readonly mutations: MutationTree<StorageState>;
private readonly storage: StorageService;
public constructor(storage: StorageService) {
this.storage = storage;
this.namespaced = true;
this.state = new StorageState();
this.mutations = {
setUsage: this.setUsage,
setDiskSpace: this.setDiskSpace,
};
this.actions = {
usage: this.usage.bind(this),
diskSpace: this.diskSpace.bind(this),
};
}
/**
* setUsage mutation will set storage usage.
* @param state - state of the module.
* @param usage
*/
public setUsage(state: StorageState, usage: Stamp[]): void {
state.usage = usage;
}
/**
* setDiskSpace mutation will set storage totals.
* @param state - state of the module.
* @param diskSpace
*/
public setDiskSpace(state: StorageState, diskSpace: DiskSpace): void {
state.diskSpace = diskSpace;
}
/**
* usage action loads storage usage information.
* @param ctx - context of the Vuex action.
*/
public async usage(ctx: ActionContext<StorageState, RootState>): Promise<void> {
const selectedSatelliteId = ctx.rootState.nodes.selectedSatellite ? ctx.rootState.nodes.selectedSatellite.id : null;
const selectedNodeId = ctx.rootState.nodes.selectedNode ? ctx.rootState.nodes.selectedNode.id : null;
const usage = await this.storage.usage(selectedSatelliteId, selectedNodeId);
ctx.commit('setUsage', usage);
}
/**
* diskSpace action loads total storage usage information.
* @param ctx - context of the Vuex action.
*/
public async diskSpace(ctx: ActionContext<StorageState, RootState>): Promise<void> {
const selectedNodeId = ctx.rootState.nodes.selectedNode ? ctx.rootState.nodes.selectedNode.id : null;
const diskSpace = await this.storage.diskSpace(selectedNodeId);
ctx.commit('setDiskSpace', diskSpace);
}
}

View File

@ -3,36 +3,10 @@
import { BandwidthRollup } from '@/bandwidth';
import { SizeBreakpoints } from '@/private/memory/size';
import { Stamp } from '@/storage';
const shortMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];
// TODO: move to diskspace package
/**
* Stamp is storage usage stamp for satellite at some point in time
*/
export class Stamp {
public atRestTotal: number;
public intervalStart: Date;
public constructor(atRestTotal: number = 0, intervalStart: Date = new Date()) {
this.atRestTotal = atRestTotal;
this.intervalStart = intervalStart;
}
/**
* Creates new empty instance of stamp with defined date
* @param date - holds specific date of the month
* @returns Stamp - new empty instance of stamp with defined date
*/
public static emptyWithDate(date: number): Stamp {
const now = new Date();
now.setUTCDate(date);
now.setUTCHours(0, 0, 0, 0);
return new Stamp(0, now);
}
}
/**
* Used to display correct and convenient data on chart.
*/

View File

@ -0,0 +1,42 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
/**
* Stamp is storage usage stamp for satellite at some point in time
*/
export class Stamp {
public atRestTotal: number;
public intervalStart: Date;
public constructor(atRestTotal: number = 0, intervalStart: Date = new Date()) {
this.atRestTotal = atRestTotal;
this.intervalStart = intervalStart;
}
/**
* Creates new empty instance of stamp with defined date
* @param date - holds specific date of the month
* @returns Stamp - new empty instance of stamp with defined date
*/
public static emptyWithDate(date: number): Stamp {
const now = new Date();
now.setUTCDate(date);
now.setUTCHours(0, 0, 0, 0);
return new Stamp(0, now);
}
}
/**
* DiskSpace is total storage usage for node if any selected
*/
export class DiskSpace {
public constructor(
public allocated: number = 0,
public usedPieces: number = 0,
public usedTrash: number = 0,
public free: number = 0,
public available: number = 0,
public overused: number = 0,
) {}
}

View File

@ -0,0 +1,48 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
import { StorageClient } from '@/api/storage';
import { DiskSpace, Stamp } from '@/storage';
/**
* Exposes all bandwidth related logic
*/
export class StorageService {
private readonly storage: StorageClient;
public constructor(bandwidth: StorageClient) {
this.storage = bandwidth;
}
/**
* Returns storage usage for selected satellite and node if any.
*
* @throws {@link BadRequestError}
* This exception is thrown if the input is not a valid.
*
* @throws {@link UnauthorizedError}
* Thrown if the auth cookie is missing or invalid.
*
* @throws {@link InternalError}
* Thrown if something goes wrong on server side.
*/
public async usage(satelliteId: string | null, nodeId: string | null): Promise<Stamp[]> {
return await this.storage.usage(satelliteId, nodeId);
}
/**
* Returns total storage usage for selected node if any.
*
* @throws {@link BadRequestError}
* This exception is thrown if the input is not a valid.
*
* @throws {@link UnauthorizedError}
* Thrown if the auth cookie is missing or invalid.
*
* @throws {@link InternalError}
* Thrown if something goes wrong on server side.
*/
public async diskSpace(nodeId: string | null): Promise<DiskSpace> {
return await this.storage.diskSpace(nodeId);
}
}