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()
|
||||
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
|
||||
}
|
||||
|
@ -218,6 +218,18 @@ func (usage *Service) GetProjectBandwidthTotals(ctx context.Context, projectID u
|
||||
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.
|
||||
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)
|
||||
@ -243,6 +255,17 @@ func (usage *Service) GetProjectBandwidthLimit(ctx context.Context, projectID uu
|
||||
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.
|
||||
// TODO remove because it's not used.
|
||||
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"`
|
||||
ObjectCount int64 `json:"objectCount"`
|
||||
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.
|
||||
|
@ -2642,6 +2642,8 @@ func (s *Service) GetProjectUsageLimits(ctx context.Context, projectID uuid.UUID
|
||||
BandwidthUsed: prUsageLimits.BandwidthUsed,
|
||||
ObjectCount: prObjectsSegments.ObjectCount,
|
||||
SegmentCount: prObjectsSegments.SegmentCount,
|
||||
SegmentLimit: prUsageLimits.SegmentLimit,
|
||||
SegmentUsed: prUsageLimits.SegmentUsed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -2695,6 +2697,10 @@ func (s *Service) getProjectUsageLimits(ctx context.Context, projectID uuid.UUID
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
segmentLimit, err := s.projectUsage.GetProjectSegmentLimit(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storageUsed, err := s.projectUsage.GetProjectStorageTotals(ctx, projectID)
|
||||
if err != nil {
|
||||
@ -2704,12 +2710,18 @@ func (s *Service) getProjectUsageLimits(ctx context.Context, projectID uuid.UUID
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
segmentUsed, err := s.projectUsage.GetProjectSegmentTotals(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ProjectUsageLimits{
|
||||
StorageLimit: storageLimit.Int64(),
|
||||
BandwidthLimit: bandwidthLimit.Int64(),
|
||||
StorageUsed: storageUsed,
|
||||
BandwidthUsed: bandwidthUsed,
|
||||
SegmentLimit: segmentLimit.Int64(),
|
||||
SegmentUsed: segmentUsed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,8 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
||||
limits.storageUsed,
|
||||
limits.objectCount,
|
||||
limits.segmentCount,
|
||||
limits.segmentLimit,
|
||||
limits.segmentUsed,
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="modal">
|
||||
<Icon class="modal__icon" :class="{ warning: severity === 'warning', critical: severity === 'critical' }" />
|
||||
<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">
|
||||
<VButton
|
||||
label="Cancel"
|
||||
@ -25,7 +25,7 @@
|
||||
font-size="13px"
|
||||
class="modal__buttons__button upgrade"
|
||||
:on-press="onUpgrade"
|
||||
:is-green-white="true"
|
||||
:is-white-blue="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -54,6 +54,8 @@ export default class LimitWarningModal extends Vue {
|
||||
private readonly severity: 'warning' | 'critical';
|
||||
@Prop({ default: '' })
|
||||
private readonly title: string;
|
||||
@Prop({ default: '' })
|
||||
private readonly limitType: string;
|
||||
@Prop({ default: () => {} })
|
||||
private readonly onUpgrade: () => Promise<void>;
|
||||
@Prop({ default: () => {} })
|
||||
|
@ -153,6 +153,8 @@ export class ProjectLimits {
|
||||
public storageUsed: number = 0,
|
||||
public objectCount: number = 0,
|
||||
public segmentCount: number = 0,
|
||||
public segmentLimit: number = 0,
|
||||
public segmentUsed: number = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
|
@ -38,13 +38,24 @@
|
||||
</v-banner>
|
||||
|
||||
<v-banner
|
||||
v-if="limitState.isShown && !isLoading && dashboardContent"
|
||||
:severity="limitState.severity"
|
||||
:on-click="() => setIsLimitModalShown(true)"
|
||||
v-if="limitState.hundredIsShown && !isLoading && dashboardContent"
|
||||
severity="critical"
|
||||
:on-click="() => setIsHundredLimitModalShown(true)"
|
||||
:dashboard-ref="dashboardContent"
|
||||
>
|
||||
<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>
|
||||
</template>
|
||||
</v-banner>
|
||||
@ -66,10 +77,19 @@
|
||||
:initial-seconds="inactivityModalTime / 1000"
|
||||
/>
|
||||
<limit-warning-modal
|
||||
v-if="isLimitModalShown && !isLoading"
|
||||
:severity="limitState.severity"
|
||||
:on-close="() => setIsLimitModalShown(false)"
|
||||
:title="limitState.modalTitle"
|
||||
v-if="isHundredLimitModalShown && !isLoading"
|
||||
severity="critical"
|
||||
:on-close="() => setIsHundredLimitModalShown(false)"
|
||||
: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"
|
||||
/>
|
||||
<AllModals />
|
||||
@ -147,7 +167,8 @@ const debugTimerId = ref<ReturnType<typeof setTimeout> | null>();
|
||||
const inactivityModalShown = ref<boolean>(false);
|
||||
const isSessionActive = 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 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.
|
||||
*/
|
||||
const limitState = computed((): { isShown: boolean, severity?: 'info' | 'warning' | 'critical', label?: string, modalTitle?: string } => {
|
||||
if (store.state.usersModule.user.paidTier || isAccountFrozen.value) return { isShown: false };
|
||||
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 { eightyIsShown: false, hundredIsShown: false };
|
||||
|
||||
const EIGHTY_PERCENT = 80;
|
||||
const HUNDRED_PERCENT = 100;
|
||||
const result:
|
||||
{
|
||||
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 bandwidthUsedPercent = Math.round(currentLimits.bandwidthUsed * HUNDRED_PERCENT / currentLimits.bandwidthLimit);
|
||||
const storageUsedPercent = Math.round(currentLimits.storageUsed * HUNDRED_PERCENT / currentLimits.storageLimit);
|
||||
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 isLimitHigh = bandwidthUsedPercent >= EIGHTY_PERCENT || storageUsedPercent >= EIGHTY_PERCENT;
|
||||
const isLimitCritical = bandwidthUsedPercent === HUNDRED_PERCENT || storageUsedPercent === HUNDRED_PERCENT;
|
||||
const hundredPercent = [] as string[];
|
||||
const eightyPercent = [] as string[];
|
||||
|
||||
if (isLimitHigh) {
|
||||
result.isShown = true;
|
||||
result.severity = isLimitCritical ? 'critical' : 'warning';
|
||||
|
||||
if (bandwidthUsedPercent > storageUsedPercent) {
|
||||
result.label = bandwidthUsedPercent === HUNDRED_PERCENT ?
|
||||
'URGENT: You’ve reached the bandwidth limit for your project. Avoid any service interruptions.'
|
||||
: `You’ve used ${bandwidthUsedPercent}% of your bandwidth limit. Avoid interrupting your usage by upgrading account.`;
|
||||
result.modalTitle = `You’ve used ${bandwidthUsedPercent}% of your free account bandwidth`;
|
||||
} else if (bandwidthUsedPercent < storageUsedPercent) {
|
||||
result.label = storageUsedPercent === HUNDRED_PERCENT ?
|
||||
'URGENT: You’ve reached the storage limit for your project. Avoid any service interruptions.'
|
||||
: `You’ve used ${storageUsedPercent}% of your storage limit. Avoid interrupting your usage by upgrading account.`;
|
||||
result.modalTitle = `You’ve used ${storageUsedPercent}% of your free account storage`;
|
||||
} else {
|
||||
result.label = storageUsedPercent === HUNDRED_PERCENT && bandwidthUsedPercent === HUNDRED_PERCENT ?
|
||||
'URGENT: You’ve reached the storage and bandwidth limits for your project. Avoid any service interruptions.'
|
||||
: `You’ve used ${storageUsedPercent}% of your storage and ${bandwidthUsedPercent}% of bandwidth limit. Avoid interrupting your usage by upgrading account.`;
|
||||
result.modalTitle = `You’ve used ${storageUsedPercent}% storage and ${bandwidthUsedPercent}% of your free account bandwidth`;
|
||||
limitTypeArr.forEach((limitType) => {
|
||||
if (limitType.usedPercent >= 80) {
|
||||
if (limitType.usedPercent >= 100) {
|
||||
hundredPercent.push(limitType.name);
|
||||
} else {
|
||||
eightyPercent.push(limitType.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (eightyPercent.length !== 0) {
|
||||
result.eightyIsShown = true;
|
||||
|
||||
const eightyPercentString = eightyPercent.join(' and ');
|
||||
|
||||
result.eightyLabel = `You've used 80% of your ${eightyPercentString} limit. Avoid interrupting your usage by upgrading your account.`;
|
||||
result.eightyModalTitle = `80% ${eightyPercentString} limit used`;
|
||||
result.eightyModalLimitType = eightyPercentString;
|
||||
}
|
||||
|
||||
if (hundredPercent.length !== 0) {
|
||||
result.hundredIsShown = true;
|
||||
|
||||
const hundredPercentString = hundredPercent.join(' and ');
|
||||
|
||||
result.hundredLabel = `URGENT: You’ve reached the ${hundredPercentString} limit for your project. Upgrade to avoid any service interruptions.`;
|
||||
result.hundredModalTitle = `URGENT: You’ve reached the ${hundredPercentString} limit for your project.`;
|
||||
result.hundredModalLimitType = hundredPercentString;
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -469,8 +510,12 @@ async function handleInactive(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function setIsLimitModalShown(value: boolean): void {
|
||||
isLimitModalShown.value = value;
|
||||
function setIsEightyLimitModalShown(value: boolean): void {
|
||||
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.
|
||||
*/
|
||||
function togglePMModal(): void {
|
||||
isLimitModalShown.value = false;
|
||||
isHundredLimitModalShown.value = false;
|
||||
isEightyLimitModalShown.value = false;
|
||||
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.addPaymentMethod);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user