satellite/console: add notifications for free account limits
Add notifications for free account limits for segment usage and update to follow the figma designs. Issue: https://github.com/storj/storj/issues/5482 Change-Id: I8a2fe38d609d53e09bf5074484cedc343223bffd
This commit is contained in:
parent
d48f745668
commit
155b927c5e
@ -78,6 +78,14 @@ func (c *ProjectLimitCache) GetProjectLimits(ctx context.Context, projectID uuid
|
|||||||
defaultUsage := c.defaultMaxUsage.Int64()
|
defaultUsage := c.defaultMaxUsage.Int64()
|
||||||
projectLimits.Usage = &defaultUsage
|
projectLimits.Usage = &defaultUsage
|
||||||
}
|
}
|
||||||
|
if projectLimits.Segments == nil {
|
||||||
|
defaultSegments := c.defaultMaxSegments
|
||||||
|
projectLimits.Segments = &defaultSegments
|
||||||
|
}
|
||||||
|
if projectLimits.Segments == nil {
|
||||||
|
defaultSegments := c.defaultMaxSegments
|
||||||
|
projectLimits.Segments = &defaultSegments
|
||||||
|
}
|
||||||
|
|
||||||
return projectLimits, nil
|
return projectLimits, nil
|
||||||
}
|
}
|
||||||
|
@ -218,6 +218,18 @@ func (usage *Service) GetProjectBandwidthTotals(ctx context.Context, projectID u
|
|||||||
return total, ErrProjectUsage.Wrap(err)
|
return total, ErrProjectUsage.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProjectSegmentTotals returns total amount of allocated segments used for past 30 days.
|
||||||
|
func (usage *Service) GetProjectSegmentTotals(ctx context.Context, projectID uuid.UUID) (total int64, err error) {
|
||||||
|
defer mon.Task()(&ctx, projectID)(&err)
|
||||||
|
|
||||||
|
total, err = usage.liveAccounting.GetProjectSegmentUsage(ctx, projectID)
|
||||||
|
if ErrKeyNotFound.Has(err) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return total, ErrProjectUsage.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
// GetProjectBandwidth returns project allocated bandwidth for the specified year, month and day.
|
// GetProjectBandwidth returns project allocated bandwidth for the specified year, month and day.
|
||||||
func (usage *Service) GetProjectBandwidth(ctx context.Context, projectID uuid.UUID, year int, month time.Month, day int) (_ int64, err error) {
|
func (usage *Service) GetProjectBandwidth(ctx context.Context, projectID uuid.UUID, year int, month time.Month, day int) (_ int64, err error) {
|
||||||
defer mon.Task()(&ctx, projectID)(&err)
|
defer mon.Task()(&ctx, projectID)(&err)
|
||||||
@ -243,6 +255,17 @@ func (usage *Service) GetProjectBandwidthLimit(ctx context.Context, projectID uu
|
|||||||
return usage.projectLimitCache.GetProjectBandwidthLimit(ctx, projectID)
|
return usage.projectLimitCache.GetProjectBandwidthLimit(ctx, projectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProjectSegmentLimit returns current project segment limit.
|
||||||
|
func (usage *Service) GetProjectSegmentLimit(ctx context.Context, projectID uuid.UUID) (_ memory.Size, err error) {
|
||||||
|
defer mon.Task()(&ctx, projectID)(&err)
|
||||||
|
limits, err := usage.projectLimitCache.GetProjectLimits(ctx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrProjectUsage.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return memory.Size(*limits.Usage), nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateProjectLimits sets new value for project's bandwidth and storage limit.
|
// UpdateProjectLimits sets new value for project's bandwidth and storage limit.
|
||||||
// TODO remove because it's not used.
|
// TODO remove because it's not used.
|
||||||
func (usage *Service) UpdateProjectLimits(ctx context.Context, projectID uuid.UUID, limit memory.Size) (err error) {
|
func (usage *Service) UpdateProjectLimits(ctx context.Context, projectID uuid.UUID, limit memory.Size) (err error) {
|
||||||
|
@ -11,6 +11,10 @@ type ProjectUsageLimits struct {
|
|||||||
BandwidthUsed int64 `json:"bandwidthUsed"`
|
BandwidthUsed int64 `json:"bandwidthUsed"`
|
||||||
ObjectCount int64 `json:"objectCount"`
|
ObjectCount int64 `json:"objectCount"`
|
||||||
SegmentCount int64 `json:"segmentCount"`
|
SegmentCount int64 `json:"segmentCount"`
|
||||||
|
RateLimit int64 `json:"rateLimit"`
|
||||||
|
SegmentLimit int64 `json:"segmentLimit"`
|
||||||
|
RateUsed int64 `json:"rateUsed"`
|
||||||
|
SegmentUsed int64 `json:"segmentUsed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsageLimits represents storage, bandwidth, and segment limits imposed on an entity.
|
// UsageLimits represents storage, bandwidth, and segment limits imposed on an entity.
|
||||||
|
@ -2642,6 +2642,8 @@ func (s *Service) GetProjectUsageLimits(ctx context.Context, projectID uuid.UUID
|
|||||||
BandwidthUsed: prUsageLimits.BandwidthUsed,
|
BandwidthUsed: prUsageLimits.BandwidthUsed,
|
||||||
ObjectCount: prObjectsSegments.ObjectCount,
|
ObjectCount: prObjectsSegments.ObjectCount,
|
||||||
SegmentCount: prObjectsSegments.SegmentCount,
|
SegmentCount: prObjectsSegments.SegmentCount,
|
||||||
|
SegmentLimit: prUsageLimits.SegmentLimit,
|
||||||
|
SegmentUsed: prUsageLimits.SegmentUsed,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2695,6 +2697,10 @@ func (s *Service) getProjectUsageLimits(ctx context.Context, projectID uuid.UUID
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
segmentLimit, err := s.projectUsage.GetProjectSegmentLimit(ctx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
storageUsed, err := s.projectUsage.GetProjectStorageTotals(ctx, projectID)
|
storageUsed, err := s.projectUsage.GetProjectStorageTotals(ctx, projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2704,12 +2710,18 @@ func (s *Service) getProjectUsageLimits(ctx context.Context, projectID uuid.UUID
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
segmentUsed, err := s.projectUsage.GetProjectSegmentTotals(ctx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &ProjectUsageLimits{
|
return &ProjectUsageLimits{
|
||||||
StorageLimit: storageLimit.Int64(),
|
StorageLimit: storageLimit.Int64(),
|
||||||
BandwidthLimit: bandwidthLimit.Int64(),
|
BandwidthLimit: bandwidthLimit.Int64(),
|
||||||
StorageUsed: storageUsed,
|
StorageUsed: storageUsed,
|
||||||
BandwidthUsed: bandwidthUsed,
|
BandwidthUsed: bandwidthUsed,
|
||||||
|
SegmentLimit: segmentLimit.Int64(),
|
||||||
|
SegmentUsed: segmentUsed,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,9 +153,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
|||||||
limits.bandwidthLimit,
|
limits.bandwidthLimit,
|
||||||
limits.bandwidthUsed,
|
limits.bandwidthUsed,
|
||||||
limits.storageLimit,
|
limits.storageLimit,
|
||||||
limits.storageUsed,
|
limits.storageUsed,
|
||||||
limits.objectCount,
|
limits.objectCount,
|
||||||
limits.segmentCount,
|
limits.segmentCount,
|
||||||
|
limits.segmentLimit,
|
||||||
|
limits.segmentUsed,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="modal">
|
<div class="modal">
|
||||||
<Icon class="modal__icon" :class="{ warning: severity === 'warning', critical: severity === 'critical' }" />
|
<Icon class="modal__icon" :class="{ warning: severity === 'warning', critical: severity === 'critical' }" />
|
||||||
<h1 class="modal__title">{{ title }}</h1>
|
<h1 class="modal__title">{{ title }}</h1>
|
||||||
<p class="modal__info">To get more storage and bandwidth, upgrade to a Pro Account. You will still get 150GB free storage and bandwidth per month, and only pay what you use beyond that.</p>
|
<p class="modal__info">To get more {{ limitType }} limit, upgrade to a Pro Account. You will still get 150GB free storage and bandwidth per month, and only pay what you use beyond that.</p>
|
||||||
<div class="modal__buttons">
|
<div class="modal__buttons">
|
||||||
<VButton
|
<VButton
|
||||||
label="Cancel"
|
label="Cancel"
|
||||||
@ -25,7 +25,7 @@
|
|||||||
font-size="13px"
|
font-size="13px"
|
||||||
class="modal__buttons__button upgrade"
|
class="modal__buttons__button upgrade"
|
||||||
:on-press="onUpgrade"
|
:on-press="onUpgrade"
|
||||||
:is-green-white="true"
|
:is-white-blue="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -54,6 +54,8 @@ export default class LimitWarningModal extends Vue {
|
|||||||
private readonly severity: 'warning' | 'critical';
|
private readonly severity: 'warning' | 'critical';
|
||||||
@Prop({ default: '' })
|
@Prop({ default: '' })
|
||||||
private readonly title: string;
|
private readonly title: string;
|
||||||
|
@Prop({ default: '' })
|
||||||
|
private readonly limitType: string;
|
||||||
@Prop({ default: () => {} })
|
@Prop({ default: () => {} })
|
||||||
private readonly onUpgrade: () => Promise<void>;
|
private readonly onUpgrade: () => Promise<void>;
|
||||||
@Prop({ default: () => {} })
|
@Prop({ default: () => {} })
|
||||||
|
@ -153,6 +153,8 @@ export class ProjectLimits {
|
|||||||
public storageUsed: number = 0,
|
public storageUsed: number = 0,
|
||||||
public objectCount: number = 0,
|
public objectCount: number = 0,
|
||||||
public segmentCount: number = 0,
|
public segmentCount: number = 0,
|
||||||
|
public segmentLimit: number = 0,
|
||||||
|
public segmentUsed: number = 0,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +38,24 @@
|
|||||||
</v-banner>
|
</v-banner>
|
||||||
|
|
||||||
<v-banner
|
<v-banner
|
||||||
v-if="limitState.isShown && !isLoading && dashboardContent"
|
v-if="limitState.hundredIsShown && !isLoading && dashboardContent"
|
||||||
:severity="limitState.severity"
|
severity="critical"
|
||||||
:on-click="() => setIsLimitModalShown(true)"
|
:on-click="() => setIsHundredLimitModalShown(true)"
|
||||||
:dashboard-ref="dashboardContent"
|
:dashboard-ref="dashboardContent"
|
||||||
>
|
>
|
||||||
<template #text>
|
<template #text>
|
||||||
<p class="medium">{{ limitState.label }}</p>
|
<p class="medium">{{ limitState.hundredLabel }}</p>
|
||||||
|
<p class="link" @click.stop.self="togglePMModal">Upgrade now</p>
|
||||||
|
</template>
|
||||||
|
</v-banner>
|
||||||
|
<v-banner
|
||||||
|
v-if="limitState.eightyIsShown && !isLoading && dashboardContent"
|
||||||
|
severity="warning"
|
||||||
|
:on-click="() => setIsEightyLimitModalShown(true)"
|
||||||
|
:dashboard-ref="dashboardContent"
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
<p class="medium">{{ limitState.eightyLabel }}</p>
|
||||||
<p class="link" @click.stop.self="togglePMModal">Upgrade now</p>
|
<p class="link" @click.stop.self="togglePMModal">Upgrade now</p>
|
||||||
</template>
|
</template>
|
||||||
</v-banner>
|
</v-banner>
|
||||||
@ -66,10 +77,19 @@
|
|||||||
:initial-seconds="inactivityModalTime / 1000"
|
:initial-seconds="inactivityModalTime / 1000"
|
||||||
/>
|
/>
|
||||||
<limit-warning-modal
|
<limit-warning-modal
|
||||||
v-if="isLimitModalShown && !isLoading"
|
v-if="isHundredLimitModalShown && !isLoading"
|
||||||
:severity="limitState.severity"
|
severity="critical"
|
||||||
:on-close="() => setIsLimitModalShown(false)"
|
:on-close="() => setIsHundredLimitModalShown(false)"
|
||||||
:title="limitState.modalTitle"
|
:title="limitState.hundredModalTitle"
|
||||||
|
:limit-type="limitState.hundredModalLimitType"
|
||||||
|
:on-upgrade="togglePMModal"
|
||||||
|
/>
|
||||||
|
<limit-warning-modal
|
||||||
|
v-if="isEightyLimitModalShown && !isLoading"
|
||||||
|
severity="warning"
|
||||||
|
:on-close="() => setIsEightyLimitModalShown(false)"
|
||||||
|
:title="limitState.eightyModalTitle"
|
||||||
|
:limit-type="limitState.eightyModalLimitType"
|
||||||
:on-upgrade="togglePMModal"
|
:on-upgrade="togglePMModal"
|
||||||
/>
|
/>
|
||||||
<AllModals />
|
<AllModals />
|
||||||
@ -147,7 +167,8 @@ const debugTimerId = ref<ReturnType<typeof setTimeout> | null>();
|
|||||||
const inactivityModalShown = ref<boolean>(false);
|
const inactivityModalShown = ref<boolean>(false);
|
||||||
const isSessionActive = ref<boolean>(false);
|
const isSessionActive = ref<boolean>(false);
|
||||||
const isSessionRefreshing = ref<boolean>(false);
|
const isSessionRefreshing = ref<boolean>(false);
|
||||||
const isLimitModalShown = ref<boolean>(false);
|
const isHundredLimitModalShown = ref<boolean>(false);
|
||||||
|
const isEightyLimitModalShown = ref<boolean>(false);
|
||||||
const debugTimerText = ref<string>('');
|
const debugTimerText = ref<string>('');
|
||||||
|
|
||||||
const dashboardContent = ref<HTMLElement | null>(null);
|
const dashboardContent = ref<HTMLElement | null>(null);
|
||||||
@ -162,41 +183,61 @@ const isAccountFrozen = computed((): boolean => {
|
|||||||
/**
|
/**
|
||||||
* Returns all needed information for limit banner and modal when bandwidth or storage close to limits.
|
* Returns all needed information for limit banner and modal when bandwidth or storage close to limits.
|
||||||
*/
|
*/
|
||||||
const limitState = computed((): { isShown: boolean, severity?: 'info' | 'warning' | 'critical', label?: string, modalTitle?: string } => {
|
const limitState = computed((): { eightyIsShown: boolean, hundredIsShown: boolean, eightyLabel?: string, eightyModalTitle?: string, eightyModalLimitType?: string, hundredLabel?: string, hundredModalTitle?: string, hundredModalLimitType?: string } => {
|
||||||
if (store.state.usersModule.user.paidTier || isAccountFrozen.value) return { isShown: false };
|
if (store.state.usersModule.user.paidTier || isAccountFrozen.value) return { eightyIsShown: false, hundredIsShown: false };
|
||||||
|
|
||||||
const EIGHTY_PERCENT = 80;
|
const result:
|
||||||
const HUNDRED_PERCENT = 100;
|
{
|
||||||
|
eightyIsShown: boolean,
|
||||||
|
hundredIsShown: boolean,
|
||||||
|
eightyLabel?: string,
|
||||||
|
eightyModalTitle?: string,
|
||||||
|
eightyModalLimitType?: string,
|
||||||
|
hundredLabel?: string,
|
||||||
|
hundredModalTitle?: string,
|
||||||
|
hundredModalLimitType?: string
|
||||||
|
|
||||||
|
} = { eightyIsShown: false, hundredIsShown: false, eightyLabel: '', hundredLabel: '' };
|
||||||
|
|
||||||
const result: { isShown: boolean, severity?: 'info' | 'warning' | 'critical', label?: string, modalTitle?: string } = { isShown: false, label: '' };
|
|
||||||
const { currentLimits } = store.state.projectsModule;
|
const { currentLimits } = store.state.projectsModule;
|
||||||
|
|
||||||
|
const limitTypeArr = [
|
||||||
|
{ name: 'bandwidth', usedPercent: Math.round(currentLimits.bandwidthUsed * 100 / currentLimits.bandwidthLimit) },
|
||||||
|
{ name: 'storage', usedPercent: Math.round(currentLimits.storageUsed * 100 / currentLimits.storageLimit) },
|
||||||
|
{ name: 'segment', usedPercent: Math.round(currentLimits.segmentUsed * 100 / currentLimits.segmentLimit) },
|
||||||
|
];
|
||||||
|
|
||||||
const bandwidthUsedPercent = Math.round(currentLimits.bandwidthUsed * HUNDRED_PERCENT / currentLimits.bandwidthLimit);
|
const hundredPercent = [] as string[];
|
||||||
const storageUsedPercent = Math.round(currentLimits.storageUsed * HUNDRED_PERCENT / currentLimits.storageLimit);
|
const eightyPercent = [] as string[];
|
||||||
|
|
||||||
const isLimitHigh = bandwidthUsedPercent >= EIGHTY_PERCENT || storageUsedPercent >= EIGHTY_PERCENT;
|
limitTypeArr.forEach((limitType) => {
|
||||||
const isLimitCritical = bandwidthUsedPercent === HUNDRED_PERCENT || storageUsedPercent === HUNDRED_PERCENT;
|
if (limitType.usedPercent >= 80) {
|
||||||
|
if (limitType.usedPercent >= 100) {
|
||||||
|
hundredPercent.push(limitType.name);
|
||||||
|
} else {
|
||||||
|
eightyPercent.push(limitType.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (isLimitHigh) {
|
if (eightyPercent.length !== 0) {
|
||||||
result.isShown = true;
|
result.eightyIsShown = true;
|
||||||
result.severity = isLimitCritical ? 'critical' : 'warning';
|
|
||||||
|
|
||||||
if (bandwidthUsedPercent > storageUsedPercent) {
|
const eightyPercentString = eightyPercent.join(' and ');
|
||||||
result.label = bandwidthUsedPercent === HUNDRED_PERCENT ?
|
|
||||||
'URGENT: You’ve reached the bandwidth limit for your project. Avoid any service interruptions.'
|
result.eightyLabel = `You've used 80% of your ${eightyPercentString} limit. Avoid interrupting your usage by upgrading your account.`;
|
||||||
: `You’ve used ${bandwidthUsedPercent}% of your bandwidth limit. Avoid interrupting your usage by upgrading account.`;
|
result.eightyModalTitle = `80% ${eightyPercentString} limit used`;
|
||||||
result.modalTitle = `You’ve used ${bandwidthUsedPercent}% of your free account bandwidth`;
|
result.eightyModalLimitType = eightyPercentString;
|
||||||
} else if (bandwidthUsedPercent < storageUsedPercent) {
|
}
|
||||||
result.label = storageUsedPercent === HUNDRED_PERCENT ?
|
|
||||||
'URGENT: You’ve reached the storage limit for your project. Avoid any service interruptions.'
|
if (hundredPercent.length !== 0) {
|
||||||
: `You’ve used ${storageUsedPercent}% of your storage limit. Avoid interrupting your usage by upgrading account.`;
|
result.hundredIsShown = true;
|
||||||
result.modalTitle = `You’ve used ${storageUsedPercent}% of your free account storage`;
|
|
||||||
} else {
|
const hundredPercentString = hundredPercent.join(' and ');
|
||||||
result.label = storageUsedPercent === HUNDRED_PERCENT && bandwidthUsedPercent === HUNDRED_PERCENT ?
|
|
||||||
'URGENT: You’ve reached the storage and bandwidth limits for your project. Avoid any service interruptions.'
|
result.hundredLabel = `URGENT: You’ve reached the ${hundredPercentString} limit for your project. Upgrade to avoid any service interruptions.`;
|
||||||
: `You’ve used ${storageUsedPercent}% of your storage and ${bandwidthUsedPercent}% of bandwidth limit. Avoid interrupting your usage by upgrading account.`;
|
result.hundredModalTitle = `URGENT: You’ve reached the ${hundredPercentString} limit for your project.`;
|
||||||
result.modalTitle = `You’ve used ${storageUsedPercent}% storage and ${bandwidthUsedPercent}% of your free account bandwidth`;
|
result.hundredModalLimitType = hundredPercentString;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -469,8 +510,12 @@ async function handleInactive(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setIsLimitModalShown(value: boolean): void {
|
function setIsEightyLimitModalShown(value: boolean): void {
|
||||||
isLimitModalShown.value = value;
|
isEightyLimitModalShown.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIsHundredLimitModalShown(value: boolean): void {
|
||||||
|
isHundredLimitModalShown.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -496,7 +541,8 @@ async function generateNewMFARecoveryCodes(): Promise<void> {
|
|||||||
* Opens add payment method modal.
|
* Opens add payment method modal.
|
||||||
*/
|
*/
|
||||||
function togglePMModal(): void {
|
function togglePMModal(): void {
|
||||||
isLimitModalShown.value = false;
|
isHundredLimitModalShown.value = false;
|
||||||
|
isEightyLimitModalShown.value = false;
|
||||||
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.addPaymentMethod);
|
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.addPaymentMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user