From 8686267e064abd75d625117de77922924a8833ce Mon Sep 17 00:00:00 2001 From: NickolaiYurchenko Date: Fri, 18 Jun 2021 17:48:14 +0300 Subject: [PATCH] web/multinode: storage api, service and store module created Change-Id: Ieb3dbcd9c967388315f5203598ff56a848477476 --- web/multinode/src/api/bandwidth.ts | 4 +- web/multinode/src/api/storage.ts | 85 +++++++++++++++++++++++++ web/multinode/src/app/store/index.ts | 12 +++- web/multinode/src/app/store/storage.ts | 87 ++++++++++++++++++++++++++ web/multinode/src/app/utils/chart.ts | 28 +-------- web/multinode/src/storage/index.ts | 42 +++++++++++++ web/multinode/src/storage/service.ts | 48 ++++++++++++++ 7 files changed, 276 insertions(+), 30 deletions(-) create mode 100644 web/multinode/src/api/storage.ts create mode 100644 web/multinode/src/app/store/storage.ts create mode 100644 web/multinode/src/storage/index.ts create mode 100644 web/multinode/src/storage/service.ts diff --git a/web/multinode/src/api/bandwidth.ts b/web/multinode/src/api/bandwidth.ts index 7bf786fe8..0a708f473 100644 --- a/web/multinode/src/api/bandwidth.ts +++ b/web/multinode/src/api/bandwidth.ts @@ -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) { diff --git a/web/multinode/src/api/storage.ts b/web/multinode/src/api/storage.ts new file mode 100644 index 000000000..94970e178 --- /dev/null +++ b/web/multinode/src/api/storage.ts @@ -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 { + 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 { + 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, + ); + } +} diff --git a/web/multinode/src/app/store/index.ts b/web/multinode/src/app/store/index.ts index ef742cc96..36e0d16b5 100644 --- a/web/multinode/src/app/store/index.ts +++ b/web/multinode/src/app/store/index.ts @@ -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 { payouts: PayoutsModule, operators: OperatorsModule, bandwidth: BandwidthModule, + storage: StorageModule, ) { this.strict = true; this.state = { @@ -49,12 +54,14 @@ export class MultinodeStoreOptions implements StoreOptions { 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 = new Vuex.Store( - new MultinodeStoreOptions(nodesModule, payoutsModule, operatorsModule, bandwidthModule), + new MultinodeStoreOptions(nodesModule, payoutsModule, operatorsModule, bandwidthModule, storageModule), ); diff --git a/web/multinode/src/app/store/storage.ts b/web/multinode/src/app/store/storage.ts new file mode 100644 index 000000000..13968ce4f --- /dev/null +++ b/web/multinode/src/app/store/storage.ts @@ -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 { + public readonly namespaced: boolean; + public readonly state: StorageState; + public readonly getters?: GetterTree; + public readonly actions: ActionTree; + public readonly mutations: MutationTree; + + 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): Promise { + 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): Promise { + const selectedNodeId = ctx.rootState.nodes.selectedNode ? ctx.rootState.nodes.selectedNode.id : null; + + const diskSpace = await this.storage.diskSpace(selectedNodeId); + + ctx.commit('setDiskSpace', diskSpace); + } +} diff --git a/web/multinode/src/app/utils/chart.ts b/web/multinode/src/app/utils/chart.ts index 3631c95f6..ca074c266 100644 --- a/web/multinode/src/app/utils/chart.ts +++ b/web/multinode/src/app/utils/chart.ts @@ -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. */ diff --git a/web/multinode/src/storage/index.ts b/web/multinode/src/storage/index.ts new file mode 100644 index 000000000..dcf187efc --- /dev/null +++ b/web/multinode/src/storage/index.ts @@ -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, + ) {} +} diff --git a/web/multinode/src/storage/service.ts b/web/multinode/src/storage/service.ts new file mode 100644 index 000000000..d8b1917d9 --- /dev/null +++ b/web/multinode/src/storage/service.ts @@ -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 { + 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 { + return await this.storage.diskSpace(nodeId); + } +}