feat: Quota enforcement UI and various improvements
- Add quota limit warnings to Resources, Services, and OwnerScheduler pages - Add quotaUtils.ts for checking quota limits - Update BusinessLayout with quota context - Improve email receiver logging - Update serializers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
116
frontend/src/utils/quotaUtils.ts
Normal file
116
frontend/src/utils/quotaUtils.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Quota Utilities
|
||||
*
|
||||
* Helpers for identifying resources/services that are over quota and will be
|
||||
* auto-archived when the grace period expires.
|
||||
*/
|
||||
|
||||
import { Resource, Service, QuotaOverage } from '../types';
|
||||
|
||||
/**
|
||||
* Get the IDs of resources that are over quota and will be auto-archived.
|
||||
* These are the oldest resources (by created_at) that exceed the limit.
|
||||
*
|
||||
* @param resources - All resources
|
||||
* @param quotaOverages - Active quota overages
|
||||
* @returns Set of resource IDs that are over quota
|
||||
*/
|
||||
export function getOverQuotaResourceIds(
|
||||
resources: Resource[],
|
||||
quotaOverages?: QuotaOverage[]
|
||||
): Set<string> {
|
||||
const overQuotaIds = new Set<string>();
|
||||
|
||||
if (!quotaOverages || quotaOverages.length === 0) {
|
||||
return overQuotaIds;
|
||||
}
|
||||
|
||||
// Find MAX_RESOURCES overage
|
||||
const resourceOverage = quotaOverages.find(o => o.quota_type === 'MAX_RESOURCES');
|
||||
if (!resourceOverage) {
|
||||
return overQuotaIds;
|
||||
}
|
||||
|
||||
// Filter out already-archived resources and sort by created_at (oldest first)
|
||||
const activeResources = resources
|
||||
.filter(r => !r.is_archived_by_quota)
|
||||
.sort((a, b) => {
|
||||
const aDate = a.created_at ? new Date(a.created_at).getTime() : 0;
|
||||
const bDate = b.created_at ? new Date(b.created_at).getTime() : 0;
|
||||
return aDate - bDate;
|
||||
});
|
||||
|
||||
// The first N resources (where N = overage_amount) are over quota
|
||||
const overageCount = resourceOverage.overage_amount;
|
||||
for (let i = 0; i < Math.min(overageCount, activeResources.length); i++) {
|
||||
overQuotaIds.add(activeResources[i].id);
|
||||
}
|
||||
|
||||
return overQuotaIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IDs of services that are over quota and will be auto-archived.
|
||||
* These are the oldest services (by created_at) that exceed the limit.
|
||||
*
|
||||
* @param services - All services
|
||||
* @param quotaOverages - Active quota overages
|
||||
* @returns Set of service IDs that are over quota
|
||||
*/
|
||||
export function getOverQuotaServiceIds(
|
||||
services: Service[],
|
||||
quotaOverages?: QuotaOverage[]
|
||||
): Set<string> {
|
||||
const overQuotaIds = new Set<string>();
|
||||
|
||||
if (!quotaOverages || quotaOverages.length === 0) {
|
||||
return overQuotaIds;
|
||||
}
|
||||
|
||||
// Find MAX_SERVICES overage
|
||||
const serviceOverage = quotaOverages.find(o => o.quota_type === 'MAX_SERVICES');
|
||||
if (!serviceOverage) {
|
||||
return overQuotaIds;
|
||||
}
|
||||
|
||||
// Filter out already-archived services and sort by created_at (oldest first)
|
||||
const activeServices = services
|
||||
.filter(s => !s.is_archived_by_quota)
|
||||
.sort((a, b) => {
|
||||
const aDate = a.created_at ? new Date(a.created_at).getTime() : 0;
|
||||
const bDate = b.created_at ? new Date(b.created_at).getTime() : 0;
|
||||
return aDate - bDate;
|
||||
});
|
||||
|
||||
// The first N services (where N = overage_amount) are over quota
|
||||
const overageCount = serviceOverage.overage_amount;
|
||||
for (let i = 0; i < Math.min(overageCount, activeServices.length); i++) {
|
||||
overQuotaIds.add(activeServices[i].id);
|
||||
}
|
||||
|
||||
return overQuotaIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific resource is over quota (will be archived)
|
||||
*/
|
||||
export function isResourceOverQuota(
|
||||
resourceId: string,
|
||||
resources: Resource[],
|
||||
quotaOverages?: QuotaOverage[]
|
||||
): boolean {
|
||||
const overQuotaIds = getOverQuotaResourceIds(resources, quotaOverages);
|
||||
return overQuotaIds.has(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific service is over quota (will be archived)
|
||||
*/
|
||||
export function isServiceOverQuota(
|
||||
serviceId: string,
|
||||
services: Service[],
|
||||
quotaOverages?: QuotaOverage[]
|
||||
): boolean {
|
||||
const overQuotaIds = getOverQuotaServiceIds(services, quotaOverages);
|
||||
return overQuotaIds.has(serviceId);
|
||||
}
|
||||
Reference in New Issue
Block a user