mirror of
https://github.com/PrivateCaptcha/PrivateCaptcha.git
synced 2026-02-09 07:19:08 -06:00
Show properties and orgs usage in Usage tab
This commit is contained in:
@@ -52,6 +52,8 @@ func (p *basePlan) PriceIDs() (string, string) { return p.priceIDMonthl
|
||||
func (p *basePlan) TrialDays() int { return 14 }
|
||||
func (p *basePlan) RequestsLimit() int64 { return p.requestsLimit }
|
||||
func (p *basePlan) APIRequestsPerSecond() float64 { return p.apiRequestsPerSecond }
|
||||
func (p *basePlan) PropertiesLimit() int { return 50 }
|
||||
func (p *basePlan) OrgsLimit() int { return 10 }
|
||||
|
||||
const (
|
||||
version1 = 1
|
||||
@@ -74,6 +76,8 @@ type Plan interface {
|
||||
CheckPropertiesLimit(count int) bool
|
||||
TrialDays() int
|
||||
RequestsLimit() int64
|
||||
PropertiesLimit() int
|
||||
OrgsLimit() int
|
||||
APIRequestsPerSecond() float64
|
||||
}
|
||||
|
||||
|
||||
@@ -870,6 +870,7 @@ func (impl *BusinessStoreImpl) CreateNewProperty(ctx context.Context, params *db
|
||||
impl.cacheProperty(ctx, property)
|
||||
// invalidate org properties in cache as we just created a new property
|
||||
_ = impl.cache.Delete(ctx, orgPropertiesCacheKey(params.OrgID.Int32))
|
||||
_ = impl.cache.Delete(ctx, userPropertiesCountCacheKey(property.CreatorID.Int32))
|
||||
|
||||
auditEvent := newCreatePropertyAuditLogEvent(property, org)
|
||||
|
||||
@@ -919,6 +920,7 @@ func (impl *BusinessStoreImpl) SoftDeleteProperty(ctx context.Context, prop *dbg
|
||||
_ = impl.cache.SetMissing(ctx, propertyByIDCacheKey(prop.ID))
|
||||
// invalidate org properties in cache as we just deleted a property
|
||||
_ = impl.cache.Delete(ctx, orgPropertiesCacheKey(org.ID))
|
||||
_ = impl.cache.Delete(ctx, userPropertiesCountCacheKey(user.ID))
|
||||
|
||||
auditEvent := newDeletePropertyAuditLogEvent(prop, org, user)
|
||||
|
||||
@@ -987,6 +989,7 @@ func (impl *BusinessStoreImpl) SoftDeleteOrganization(ctx context.Context, org *
|
||||
_ = impl.cache.SetMissing(ctx, orgCacheKey(org.ID))
|
||||
// invalidate user orgs in cache as we just deleted one
|
||||
_ = impl.cache.Delete(ctx, userOrgsCacheKey(user.ID))
|
||||
_ = impl.cache.Delete(ctx, userPropertiesCountCacheKey(user.ID))
|
||||
|
||||
auditEvent := newOrgAuditLogEvent(user.ID, org, common.AuditLogActionSoftDelete)
|
||||
|
||||
@@ -1688,6 +1691,11 @@ func (impl *BusinessStoreImpl) RetrieveUserPropertiesCount(ctx context.Context,
|
||||
return 0, ErrMaintenance
|
||||
}
|
||||
|
||||
cacheKey := userPropertiesCountCacheKey(userID)
|
||||
if count, err := FetchCachedOne[int64](ctx, impl.cache, cacheKey); err == nil {
|
||||
return *count, nil
|
||||
}
|
||||
|
||||
count, err := impl.querier.GetUserPropertiesCount(ctx, Int(userID))
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Failed to retrieve user properties count", "userID", userID, common.ErrAttr(err))
|
||||
@@ -1696,6 +1704,11 @@ func (impl *BusinessStoreImpl) RetrieveUserPropertiesCount(ctx context.Context,
|
||||
|
||||
slog.DebugContext(ctx, "Fetched user properties count", "userID", userID, "count", count)
|
||||
|
||||
const propertiesCountTTL = 5 * time.Minute
|
||||
c := new(int64)
|
||||
*c = count
|
||||
_ = impl.cache.SetWithTTL(ctx, cacheKey, c, propertiesCountTTL)
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,7 @@ const (
|
||||
userAuditLogsCacheKeyPrefix
|
||||
propertyAuditLogsCacheKeyPrefix
|
||||
orgAuditLogsCacheKeyPrefix
|
||||
userPropertiesCountCachePrefix
|
||||
// Add new fields _above_
|
||||
CACHE_KEY_PREFIXES_COUNT
|
||||
)
|
||||
@@ -238,6 +239,7 @@ func init() {
|
||||
cachePrefixToStrings[userAuditLogsCacheKeyPrefix] = "userAuditLogs/"
|
||||
cachePrefixToStrings[propertyAuditLogsCacheKeyPrefix] = "propAuditLogs/"
|
||||
cachePrefixToStrings[orgAuditLogsCacheKeyPrefix] = "orgAuditLogs/"
|
||||
cachePrefixToStrings[userPropertiesCountCachePrefix] = "userPropertiesCount/"
|
||||
|
||||
for i, v := range cachePrefixToStrings {
|
||||
if len(v) == 0 {
|
||||
@@ -338,3 +340,6 @@ func propertyAuditLogsCacheKey(propID int32) CacheKey {
|
||||
func orgAuditLogsCacheKey(orgID int32) CacheKey {
|
||||
return CacheKey{Prefix: orgAuditLogsCacheKeyPrefix, IntValue: orgID}
|
||||
}
|
||||
func userPropertiesCountCacheKey(userID int32) CacheKey {
|
||||
return Int32CacheKey(userPropertiesCountCachePrefix, userID)
|
||||
}
|
||||
|
||||
@@ -319,7 +319,11 @@ func TestRenderHTML(t *testing.T) {
|
||||
ActiveTabID: common.UsageEndpoint,
|
||||
Tabs: CreateTabViewModels(common.UsageEndpoint, server.SettingsTabs),
|
||||
},
|
||||
Limit: 12345,
|
||||
OrgsCount: 2,
|
||||
PropertiesCount: 10,
|
||||
IncludedOrgsCount: 10,
|
||||
IncludedPropertiesCount: 50,
|
||||
Limit: 12345,
|
||||
},
|
||||
selector: "",
|
||||
matches: []string{},
|
||||
|
||||
@@ -66,7 +66,11 @@ type SettingsCommonRenderContext struct {
|
||||
|
||||
type settingsUsageRenderContext struct {
|
||||
SettingsCommonRenderContext
|
||||
Limit int
|
||||
PropertiesCount int
|
||||
OrgsCount int
|
||||
IncludedPropertiesCount int
|
||||
IncludedOrgsCount int
|
||||
Limit int
|
||||
}
|
||||
|
||||
type settingsGeneralRenderContext struct {
|
||||
@@ -720,6 +724,14 @@ func (s *Server) createUsageSettingsModel(ctx context.Context, user *dbgen.User)
|
||||
SettingsCommonRenderContext: s.CreateSettingsCommonRenderContext(common.UsageEndpoint, user),
|
||||
}
|
||||
|
||||
if orgs, err := s.Store.Impl().RetrieveUserOrganizations(ctx, user); err == nil {
|
||||
renderCtx.OrgsCount = len(orgs)
|
||||
}
|
||||
|
||||
if count, err := s.Store.Impl().RetrieveUserPropertiesCount(ctx, user.ID); err == nil {
|
||||
renderCtx.PropertiesCount = int(count)
|
||||
}
|
||||
|
||||
if user.SubscriptionID.Valid {
|
||||
subscription, err := s.Store.Impl().RetrieveSubscription(ctx, user.SubscriptionID.Int32)
|
||||
if err != nil {
|
||||
@@ -729,6 +741,8 @@ func (s *Server) createUsageSettingsModel(ctx context.Context, user *dbgen.User)
|
||||
if plan, err := s.PlanService.FindPlan(subscription.ExternalProductID, subscription.ExternalPriceID, s.Stage,
|
||||
db.IsInternalSubscription(subscription.Source)); err == nil {
|
||||
renderCtx.Limit = int(plan.RequestsLimit())
|
||||
renderCtx.IncludedPropertiesCount = plan.PropertiesLimit()
|
||||
renderCtx.IncludedOrgsCount = plan.OrgsLimit()
|
||||
} else {
|
||||
slog.ErrorContext(ctx, "Failed to find billing plan for usage tab", "productID", subscription.ExternalProductID, "priceID", subscription.ExternalPriceID, common.ErrAttr(err))
|
||||
renderCtx.ErrorMessage = "Could not determine usage limits from your plan."
|
||||
|
||||
@@ -13,6 +13,23 @@
|
||||
<p class="text-base font-bold text-gray-900 lg:order-1">Monthly usage</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<dl class="grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-gray-500">Organizations</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-500">{{if .Params.IncludedOrgsCount}}<span class="text-gray-900">{{.Params.OrgsCount}}</span> / {{.Params.IncludedOrgsCount}}{{else}}<span class="text-xl">Calculating...</span>{{end}}</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-gray-500">Properties</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-500">{{if .Params.IncludedPropertiesCount}}<span class="text-gray-900">{{.Params.PropertiesCount}}</span> / {{.Params.IncludedPropertiesCount}}{{else}}<span class="text-xl">Calculating...</span>{{end}}</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-gray-500">Captcha requests</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900" id="totalRequests">Calculating...</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 min-h-96" id="usage-chart"></div>
|
||||
|
||||
<div id="usage-spinner" class="absolute inset-0 flex justify-center items-center z-10 hidden">
|
||||
|
||||
@@ -239,8 +239,19 @@
|
||||
const response = await this.fetchChartData(spinnerElement);
|
||||
if (response && response.data && response.data.length > 0) {
|
||||
this.setChartData(chartElement, response, this.monthlyTicks.bind(this));
|
||||
|
||||
const totalElement = document.getElementById("totalRequests");
|
||||
if (totalElement) {
|
||||
const sum = response.data.reduce((sum, item) => sum + item.y, 0);
|
||||
const formatter = new Intl.NumberFormat('en', {
|
||||
notation: 'compact',
|
||||
compactDisplay: 'short',
|
||||
});
|
||||
totalElement.innerHTML = formatter.format(sum);;
|
||||
}
|
||||
} else {
|
||||
this.drawNoData(chartElement, this.monthlyTicks.bind(this), 365);
|
||||
totalElement.innerHTML = "N/A";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user