diff --git a/satellite/admin/back-office/ui/src/components/AccountProjectsTableComponent.vue b/satellite/admin/back-office/ui/src/components/AccountProjectsTableComponent.vue index 3c4abf8deb41..b73e83a3370a 100644 --- a/satellite/admin/back-office/ui/src/components/AccountProjectsTableComponent.vue +++ b/satellite/admin/back-office/ui/src/components/AccountProjectsTableComponent.vue @@ -28,8 +28,9 @@ \ No newline at end of file + +const percentage = computed((): string => { + if (props.onlyLimit || !props.used) { + return '0'; + } + + const p = props.used/props.limit * 100; + return Math.round(p).toString(); +}); + +const available = computed((): number => { + if (props.onlyLimit || !props.used) { + return 0; + } + + return props.limit - props.used; +}); + +/** +* Returns a stringify val considering if val is expressed in bytes and it that case it returns the +* value in the best human readable memory size unit rounding down to 0 when its expressed in bytes +* and truncating the decimals when its expressed in other units. +*/ +function format(val: number): string { + if (!props.isBytes) { + return val.toString(); + } + + const valmem = new Size(val, 2); + switch (valmem.label) { + case Dimensions.Bytes: + return '0'; + default: + return `${valmem.formattedBytes.replace(/\.0+$/, '')}${valmem.label}`; + } +} + diff --git a/satellite/admin/back-office/ui/src/store/app.ts b/satellite/admin/back-office/ui/src/store/app.ts index 9fd5dcc126d5..a856875ca129 100644 --- a/satellite/admin/back-office/ui/src/store/app.ts +++ b/satellite/admin/back-office/ui/src/store/app.ts @@ -4,11 +4,12 @@ import { reactive } from 'vue'; import { defineStore } from 'pinia'; -import { PlacementInfo, PlacementManagementHttpApiV1, UserAccount, UserManagementHttpApiV1 } from '@/api/client.gen'; +import { PlacementInfo, PlacementManagementHttpApiV1, Project, ProjectManagementHttpApiV1, UserAccount, UserManagementHttpApiV1 } from '@/api/client.gen'; class AppState { public placements: PlacementInfo[]; public userAccount: UserAccount | null = null; + public selectedProject: Project | null = null; } export const useAppStore = defineStore('app', () => { @@ -16,6 +17,7 @@ export const useAppStore = defineStore('app', () => { const userApi = new UserManagementHttpApiV1(); const placementApi = new PlacementManagementHttpApiV1(); + const projectApi = new ProjectManagementHttpApiV1(); async function getUserByEmail(email: string): Promise { state.userAccount = await userApi.getUserByEmail(email); @@ -29,10 +31,28 @@ export const useAppStore = defineStore('app', () => { state.placements = await placementApi.getPlacements(); } + function getPlacementText(code: number): string { + for (const placement of state.placements) { + if (placement.id === code) { + if (placement.location) { + return placement.location; + } + break; + } + } + return `Unknown (${code})`; + } + + async function selectProject(id: string): Promise { + state.selectedProject = await projectApi.getProject(id); + } + return { state, getUserByEmail, clearUser, getPlacements, + getPlacementText, + selectProject, }; }); diff --git a/satellite/admin/back-office/ui/src/utils/bytesSize.ts b/satellite/admin/back-office/ui/src/utils/bytesSize.ts new file mode 100644 index 000000000000..441743f963fc --- /dev/null +++ b/satellite/admin/back-office/ui/src/utils/bytesSize.ts @@ -0,0 +1,94 @@ +// Copyright (C) 2019 Storj Labs, Inc. +// See LICENSE for copying information. + +export enum Memory { + Bytes = 1e0, + KB = 1e3, + MB = 1e6, + GB = 1e9, + TB = 1e12, + PB = 1e15, + EB = 1e18, + + KiB = 2 ** 10, + MiB = 2 ** 20, + GiB = 2 ** 30, + TiB = 2 ** 40, + PiB = 2 ** 50, + EiB = 2 ** 60, +} + +export enum Dimensions { + Bytes = 'B', + KB = 'KB', + MB = 'MB', + GB = 'GB', + TB = 'TB', + PB = 'PB', +} + +export class Size { + private readonly precision: number; + public readonly bytes: number; + public readonly formattedBytes: string; + public readonly label: Dimensions; + + public constructor(bytes: number, precision = 0) { + const _bytes = Math.ceil(bytes); + this.bytes = bytes; + this.precision = precision; + + switch (true) { + case _bytes === 0: + this.formattedBytes = (bytes / Memory.Bytes).toFixed(this.precision); + this.label = Dimensions.Bytes; + break; + case _bytes < Memory.MB: + this.formattedBytes = (bytes / Memory.KB).toFixed(this.precision); + this.label = Dimensions.KB; + break; + case _bytes < Memory.GB: + this.formattedBytes = (bytes / Memory.MB).toFixed(this.precision); + this.label = Dimensions.MB; + break; + case _bytes < Memory.TB: + this.formattedBytes = (bytes / Memory.GB).toFixed(this.precision); + this.label = Dimensions.GB; + break; + case _bytes < Memory.PB: + this.formattedBytes = (bytes / Memory.TB).toFixed(this.precision); + this.label = Dimensions.TB; + break; + default: + this.formattedBytes = (bytes / Memory.PB).toFixed(this.precision); + this.label = Dimensions.PB; + } + } + + /** + * Base10String converts size to a string using base-10 prefixes. + * @param size in bytes + */ + public static toBase10String(size: number): string { + const decimals = 2; + + const _size = Math.abs(size); + + switch (true) { + case _size >= Memory.EB * 2 / 3: + return `${parseFloat((size / Memory.EB).toFixed(decimals))}EB`; + case _size >= Memory.PB * 2 / 3: + return `${parseFloat((size / Memory.PB).toFixed(decimals))}PB`; + case _size >= Memory.TB * 2 / 3: + return `${parseFloat((size / Memory.TB).toFixed(decimals))}TB`; + case _size >= Memory.GB * 2 / 3: + return `${parseFloat((size / Memory.GB).toFixed(decimals))}GB`; + case _size >= Memory.MB * 2 / 3: + return `${parseFloat((size / Memory.MB).toFixed(decimals))}MB`; + case _size >= Memory.KB * 2 / 3: + return `${parseFloat((size / Memory.KB).toFixed(decimals))}KB`; + default: + return `${size}B`; + } + } +} diff --git a/satellite/admin/back-office/ui/src/views/AccountDetails.vue b/satellite/admin/back-office/ui/src/views/AccountDetails.vue index a68aa6dea2a6..7c003e6675ea 100644 --- a/satellite/admin/back-office/ui/src/views/AccountDetails.vue +++ b/satellite/admin/back-office/ui/src/views/AccountDetails.vue @@ -210,21 +210,6 @@ const userAccount = computed(() => appStore.state.userAccount as Us */ const createdAt = computed(() => new Date(userAccount.value.createdAt)); -/** - * Returns the string representation of the user's default placement. - */ -const placementText = computed(() => { - for (const placement of appStore.state.placements) { - if (placement.id === userAccount.value.defaultPlacement) { - if (placement.location) { - return placement.location; - } - break; - } - } - return `Unknown (${userAccount.value.defaultPlacement})`; -}); - type Usage = { storage: number | null; download: number; @@ -258,17 +243,24 @@ const totalUsage = computed(() => { return total; }); +const placementText = computed(() => { + return appStore.getPlacementText(userAccount.value.defaultPlacement); +}); + /** * Returns whether an error occurred retrieving usage data from the Redis live accounting cache. */ const usageCacheError = computed(() => { return !!userAccount.value.projects?.some(project => project.storageUsed === null || - project.bandwidthUsed === null || project.segmentUsed === null, ); }); onBeforeMount(() => !userAccount.value && router.push('/accounts')); -onUnmounted(appStore.clearUser); +onUnmounted(() => { + if (appStore.state.selectedProject === null) { + appStore.clearUser; + } +}); diff --git a/satellite/admin/back-office/ui/src/views/ProjectDetails.vue b/satellite/admin/back-office/ui/src/views/ProjectDetails.vue index e438b84f61bd..c39dd4fd7f69 100644 --- a/satellite/admin/back-office/ui/src/views/ProjectDetails.vue +++ b/satellite/admin/back-office/ui/src/views/ProjectDetails.vue @@ -19,7 +19,7 @@ /> - itacker@gmail.com + {{ project.owner.email }} Project @@ -33,7 +33,7 @@ /> - 56F82SR21Q284 + {{ project.id }} This project ID @@ -51,15 +51,28 @@ + + + +
+ + An error occurred when retrieving project usage data. + Please retry after a few minutes and report the issue if it persists. +
+
+
+
+ - + - Pro + {{ userAccount.paidTier ? 'Pro' : 'Free' }} + @@ -73,7 +86,7 @@ - Company + {{ project.userAgent }} Set Value Attribution @@ -87,7 +100,7 @@ - Global + {{ placementText }} @@ -103,44 +116,39 @@ + @@ -169,7 +177,9 @@ \ No newline at end of file + +const appStore = useAppStore(); + +const userAccount = computed(() => appStore.state.userAccount as UserAccount); +const project = computed(() => appStore.state.selectedProject as Project); + +const placementText = computed(() => { + return appStore.getPlacementText(project.value.defaultPlacement); +}); + +/** + * Returns whether an error occurred retrieving usage data from the Redis live accounting cache. + */ +const usageCacheError = computed(() => { + return project.value.storageUsed === null || project.value.segmentUsed === null; +}); +