diff --git a/satellite/accounting/projectlimitcache.go b/satellite/accounting/projectlimitcache.go index 61aab5908..98a7e9cc5 100644 --- a/satellite/accounting/projectlimitcache.go +++ b/satellite/accounting/projectlimitcache.go @@ -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 } diff --git a/satellite/accounting/projectusage.go b/satellite/accounting/projectusage.go index 1a74913ee..ac2da5c8a 100644 --- a/satellite/accounting/projectusage.go +++ b/satellite/accounting/projectusage.go @@ -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) { diff --git a/satellite/console/projectusagelimits.go b/satellite/console/projectusagelimits.go index 0f89ab4ce..b404bde59 100644 --- a/satellite/console/projectusagelimits.go +++ b/satellite/console/projectusagelimits.go @@ -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. diff --git a/satellite/console/service.go b/satellite/console/service.go index f246cd0cb..2f0ffc5e8 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -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 } diff --git a/web/satellite/src/api/projects.ts b/web/satellite/src/api/projects.ts index 3a9a2f3bb..91e9e8102 100644 --- a/web/satellite/src/api/projects.ts +++ b/web/satellite/src/api/projects.ts @@ -153,9 +153,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi { limits.bandwidthLimit, limits.bandwidthUsed, limits.storageLimit, - limits.storageUsed, + limits.storageUsed, limits.objectCount, limits.segmentCount, + limits.segmentLimit, + limits.segmentUsed, ); } diff --git a/web/satellite/src/components/modals/LimitWarningModal.vue b/web/satellite/src/components/modals/LimitWarningModal.vue index 5ce87e5f8..78c284fa4 100644 --- a/web/satellite/src/components/modals/LimitWarningModal.vue +++ b/web/satellite/src/components/modals/LimitWarningModal.vue @@ -7,7 +7,7 @@ @@ -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; @Prop({ default: () => {} }) diff --git a/web/satellite/src/types/projects.ts b/web/satellite/src/types/projects.ts index 616f31d8c..36180bc05 100644 --- a/web/satellite/src/types/projects.ts +++ b/web/satellite/src/types/projects.ts @@ -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, ) {} } diff --git a/web/satellite/src/views/DashboardArea.vue b/web/satellite/src/views/DashboardArea.vue index b1df9b665..522bf6566 100644 --- a/web/satellite/src/views/DashboardArea.vue +++ b/web/satellite/src/views/DashboardArea.vue @@ -38,13 +38,24 @@ + + + @@ -66,10 +77,19 @@ :initial-seconds="inactivityModalTime / 1000" /> + @@ -147,7 +167,8 @@ const debugTimerId = ref | null>(); const inactivityModalShown = ref(false); const isSessionActive = ref(false); const isSessionRefreshing = ref(false); -const isLimitModalShown = ref(false); +const isHundredLimitModalShown = ref(false); +const isEightyLimitModalShown = ref(false); const debugTimerText = ref(''); const dashboardContent = ref(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 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 storageUsedPercent = Math.round(currentLimits.storageUsed * HUNDRED_PERCENT / currentLimits.storageLimit); + const hundredPercent = [] as string[]; + const eightyPercent = [] as string[]; - const isLimitHigh = bandwidthUsedPercent >= EIGHTY_PERCENT || storageUsedPercent >= EIGHTY_PERCENT; - const isLimitCritical = bandwidthUsedPercent === HUNDRED_PERCENT || storageUsedPercent === HUNDRED_PERCENT; + limitTypeArr.forEach((limitType) => { + if (limitType.usedPercent >= 80) { + if (limitType.usedPercent >= 100) { + hundredPercent.push(limitType.name); + } else { + eightyPercent.push(limitType.name); + } + } + }); - if (isLimitHigh) { - result.isShown = true; - result.severity = isLimitCritical ? 'critical' : 'warning'; + if (eightyPercent.length !== 0) { + result.eightyIsShown = true; - 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`; - } + 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 { } } -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 { * 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); }