Convert portal licensing store to TS
This commit is contained in:
parent
a286685900
commit
78a7ae9ec1
|
@ -1,279 +0,0 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { API } from "@/api"
|
||||
import { auth, admin } from "@/stores/portal"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import { StripeStatus } from "@/components/portal/licensing/constants"
|
||||
import { PlanModel } from "@budibase/types"
|
||||
|
||||
const UNLIMITED = -1
|
||||
|
||||
export const createLicensingStore = () => {
|
||||
const DEFAULT = {
|
||||
// navigation
|
||||
goToUpgradePage: () => {},
|
||||
goToPricingPage: () => {},
|
||||
// the top level license
|
||||
license: undefined,
|
||||
isFreePlan: true,
|
||||
isEnterprisePlan: true,
|
||||
isBusinessPlan: true,
|
||||
// features
|
||||
groupsEnabled: false,
|
||||
backupsEnabled: false,
|
||||
brandingEnabled: false,
|
||||
scimEnabled: false,
|
||||
environmentVariablesEnabled: false,
|
||||
budibaseAIEnabled: false,
|
||||
customAIConfigsEnabled: false,
|
||||
auditLogsEnabled: false,
|
||||
// the currently used quotas from the db
|
||||
quotaUsage: undefined,
|
||||
// derived quota metrics for percentages used
|
||||
usageMetrics: undefined,
|
||||
// quota reset
|
||||
quotaResetDaysRemaining: undefined,
|
||||
quotaResetDate: undefined,
|
||||
// failed payments
|
||||
accountPastDue: undefined,
|
||||
pastDueEndDate: undefined,
|
||||
pastDueDaysRemaining: undefined,
|
||||
accountDowngraded: undefined,
|
||||
// user limits
|
||||
userCount: undefined,
|
||||
userLimit: undefined,
|
||||
userLimitReached: false,
|
||||
errUserLimit: false,
|
||||
}
|
||||
|
||||
const oneDayInMilliseconds = 86400000
|
||||
|
||||
const store = writable(DEFAULT)
|
||||
|
||||
function usersLimitReached(userCount, userLimit) {
|
||||
if (userLimit === UNLIMITED) {
|
||||
return false
|
||||
}
|
||||
return userCount >= userLimit
|
||||
}
|
||||
|
||||
function usersLimitExceeded(userCount, userLimit) {
|
||||
if (userLimit === UNLIMITED) {
|
||||
return false
|
||||
}
|
||||
return userCount > userLimit
|
||||
}
|
||||
|
||||
async function isCloud() {
|
||||
let adminStore = get(admin)
|
||||
if (!adminStore.loaded) {
|
||||
await admin.init()
|
||||
adminStore = get(admin)
|
||||
}
|
||||
return adminStore.cloud
|
||||
}
|
||||
|
||||
const actions = {
|
||||
init: async () => {
|
||||
actions.setNavigation()
|
||||
actions.setLicense()
|
||||
await actions.setQuotaUsage()
|
||||
},
|
||||
setNavigation: () => {
|
||||
const adminStore = get(admin)
|
||||
const authStore = get(auth)
|
||||
|
||||
const upgradeUrl = authStore?.user?.accountPortalAccess
|
||||
? `${adminStore.accountPortalUrl}/portal/upgrade`
|
||||
: "/builder/portal/account/upgrade"
|
||||
|
||||
const goToUpgradePage = () => {
|
||||
window.location.href = upgradeUrl
|
||||
}
|
||||
const goToPricingPage = () => {
|
||||
window.open("https://budibase.com/pricing/", "_blank")
|
||||
}
|
||||
store.update(state => {
|
||||
return {
|
||||
...state,
|
||||
goToUpgradePage,
|
||||
goToPricingPage,
|
||||
}
|
||||
})
|
||||
},
|
||||
setLicense: () => {
|
||||
const license = get(auth).user.license
|
||||
const planType = license?.plan.type
|
||||
const isEnterprisePlan = planType === Constants.PlanType.ENTERPRISE
|
||||
const isFreePlan = planType === Constants.PlanType.FREE
|
||||
const isBusinessPlan = planType === Constants.PlanType.BUSINESS
|
||||
const isEnterpriseTrial =
|
||||
planType === Constants.PlanType.ENTERPRISE_BASIC_TRIAL
|
||||
const groupsEnabled = license.features.includes(
|
||||
Constants.Features.USER_GROUPS
|
||||
)
|
||||
const backupsEnabled = license.features.includes(
|
||||
Constants.Features.APP_BACKUPS
|
||||
)
|
||||
const scimEnabled = license.features.includes(Constants.Features.SCIM)
|
||||
const environmentVariablesEnabled = license.features.includes(
|
||||
Constants.Features.ENVIRONMENT_VARIABLES
|
||||
)
|
||||
const enforceableSSO = license.features.includes(
|
||||
Constants.Features.ENFORCEABLE_SSO
|
||||
)
|
||||
const brandingEnabled = license.features.includes(
|
||||
Constants.Features.BRANDING
|
||||
)
|
||||
const auditLogsEnabled = license.features.includes(
|
||||
Constants.Features.AUDIT_LOGS
|
||||
)
|
||||
const syncAutomationsEnabled = license.features.includes(
|
||||
Constants.Features.SYNC_AUTOMATIONS
|
||||
)
|
||||
const triggerAutomationRunEnabled = license.features.includes(
|
||||
Constants.Features.TRIGGER_AUTOMATION_RUN
|
||||
)
|
||||
const perAppBuildersEnabled = license.features.includes(
|
||||
Constants.Features.APP_BUILDERS
|
||||
)
|
||||
const budibaseAIEnabled = license.features.includes(
|
||||
Constants.Features.BUDIBASE_AI
|
||||
)
|
||||
const customAIConfigsEnabled = license.features.includes(
|
||||
Constants.Features.AI_CUSTOM_CONFIGS
|
||||
)
|
||||
store.update(state => {
|
||||
return {
|
||||
...state,
|
||||
license,
|
||||
isEnterprisePlan,
|
||||
isFreePlan,
|
||||
isBusinessPlan,
|
||||
isEnterpriseTrial,
|
||||
groupsEnabled,
|
||||
backupsEnabled,
|
||||
brandingEnabled,
|
||||
budibaseAIEnabled,
|
||||
customAIConfigsEnabled,
|
||||
scimEnabled,
|
||||
environmentVariablesEnabled,
|
||||
auditLogsEnabled,
|
||||
enforceableSSO,
|
||||
syncAutomationsEnabled,
|
||||
triggerAutomationRunEnabled,
|
||||
perAppBuildersEnabled,
|
||||
}
|
||||
})
|
||||
},
|
||||
setQuotaUsage: async () => {
|
||||
const quotaUsage = await API.getQuotaUsage()
|
||||
store.update(state => {
|
||||
return {
|
||||
...state,
|
||||
quotaUsage,
|
||||
}
|
||||
})
|
||||
await actions.setUsageMetrics()
|
||||
},
|
||||
usersLimitReached: userCount => {
|
||||
return usersLimitReached(userCount, get(store).userLimit)
|
||||
},
|
||||
usersLimitExceeded(userCount) {
|
||||
return usersLimitExceeded(userCount, get(store).userLimit)
|
||||
},
|
||||
setUsageMetrics: async () => {
|
||||
const usage = get(store).quotaUsage
|
||||
const license = get(auth).user.license
|
||||
const now = new Date()
|
||||
|
||||
const getMetrics = (keys, license, quota) => {
|
||||
if (!license || !quota || !keys) {
|
||||
return {}
|
||||
}
|
||||
return keys.reduce((acc, key) => {
|
||||
const quotaLimit = license[key].value
|
||||
const quotaUsed = (quota[key] / quotaLimit) * 100
|
||||
acc[key] = quotaLimit > -1 ? Math.floor(quotaUsed) : -1
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
const monthlyMetrics = getMetrics(
|
||||
["queries", "automations"],
|
||||
license.quotas.usage.monthly,
|
||||
usage.monthly.current
|
||||
)
|
||||
const staticMetrics = getMetrics(
|
||||
["apps", "rows"],
|
||||
license.quotas.usage.static,
|
||||
usage.usageQuota
|
||||
)
|
||||
|
||||
const getDaysBetween = (dateStart, dateEnd) => {
|
||||
return dateEnd > dateStart
|
||||
? Math.round(
|
||||
(dateEnd.getTime() - dateStart.getTime()) / oneDayInMilliseconds
|
||||
)
|
||||
: 0
|
||||
}
|
||||
|
||||
const quotaResetDate = new Date(usage.quotaReset)
|
||||
const quotaResetDaysRemaining = getDaysBetween(now, quotaResetDate)
|
||||
|
||||
const accountDowngraded =
|
||||
license?.billing?.subscription?.downgradeAt &&
|
||||
license?.billing?.subscription?.downgradeAt <= now.getTime() &&
|
||||
license?.billing?.subscription?.status === StripeStatus.PAST_DUE &&
|
||||
license?.plan.type === Constants.PlanType.FREE
|
||||
|
||||
const pastDueAtMilliseconds = license?.billing?.subscription?.pastDueAt
|
||||
const downgradeAtMilliseconds =
|
||||
license?.billing?.subscription?.downgradeAt
|
||||
let pastDueDaysRemaining
|
||||
let pastDueEndDate
|
||||
|
||||
if (pastDueAtMilliseconds && downgradeAtMilliseconds) {
|
||||
pastDueEndDate = new Date(downgradeAtMilliseconds)
|
||||
pastDueDaysRemaining = getDaysBetween(
|
||||
new Date(pastDueAtMilliseconds),
|
||||
pastDueEndDate
|
||||
)
|
||||
}
|
||||
|
||||
const userQuota = license.quotas.usage.static.users
|
||||
const userLimit = userQuota?.value
|
||||
const userCount = usage.usageQuota.users
|
||||
const userLimitReached = usersLimitReached(userCount, userLimit)
|
||||
const userLimitExceeded = usersLimitExceeded(userCount, userLimit)
|
||||
const isCloudAccount = await isCloud()
|
||||
const errUserLimit =
|
||||
isCloudAccount &&
|
||||
license.plan.model === PlanModel.PER_USER &&
|
||||
userLimitExceeded
|
||||
|
||||
store.update(state => {
|
||||
return {
|
||||
...state,
|
||||
usageMetrics: { ...monthlyMetrics, ...staticMetrics },
|
||||
quotaResetDaysRemaining,
|
||||
quotaResetDate,
|
||||
accountDowngraded,
|
||||
accountPastDue: pastDueAtMilliseconds != null,
|
||||
pastDueEndDate,
|
||||
pastDueDaysRemaining,
|
||||
// user limits
|
||||
userCount,
|
||||
userLimit,
|
||||
userLimitReached,
|
||||
errUserLimit,
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
...actions,
|
||||
}
|
||||
}
|
||||
|
||||
export const licensing = createLicensingStore()
|
|
@ -0,0 +1,309 @@
|
|||
import { get } from "svelte/store"
|
||||
import { API } from "@/api"
|
||||
import { auth, admin } from "@/stores/portal"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import { StripeStatus } from "@/components/portal/licensing/constants"
|
||||
import {
|
||||
MonthlyQuotaName,
|
||||
PlanModel,
|
||||
QuotaUsage,
|
||||
StaticQuotaName,
|
||||
} from "@budibase/types"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
|
||||
const UNLIMITED = -1
|
||||
const ONE_DAY_MILLIS = 86400000
|
||||
|
||||
type MonthlyMetrics = { [key in MonthlyQuotaName]?: number }
|
||||
type StaticMetrics = { [key in StaticQuotaName]?: number }
|
||||
|
||||
interface LicensingState {
|
||||
goToUpgradePage: () => void
|
||||
goToPricingPage: () => void
|
||||
// the top level license
|
||||
license: any
|
||||
isFreePlan: boolean
|
||||
isEnterprisePlan: boolean
|
||||
isBusinessPlan: boolean
|
||||
// features
|
||||
groupsEnabled: boolean
|
||||
backupsEnabled: boolean
|
||||
brandingEnabled: boolean
|
||||
scimEnabled: boolean
|
||||
environmentVariablesEnabled: boolean
|
||||
budibaseAIEnabled: boolean
|
||||
customAIConfigsEnabled: boolean
|
||||
auditLogsEnabled: boolean
|
||||
// the currently used quotas from the db
|
||||
quotaUsage?: QuotaUsage
|
||||
// derived quota metrics for percentages used
|
||||
usageMetrics: any
|
||||
// quota reset
|
||||
quotaResetDaysRemaining: any
|
||||
quotaResetDate: any
|
||||
// failed payments
|
||||
accountPastDue: any
|
||||
pastDueEndDate: any
|
||||
pastDueDaysRemaining: any
|
||||
accountDowngraded: any
|
||||
// user limits
|
||||
userCount: any
|
||||
userLimit: any
|
||||
userLimitReached: boolean
|
||||
errUserLimit: boolean
|
||||
}
|
||||
|
||||
class LicensingStore extends BudiStore<LicensingState> {
|
||||
constructor() {
|
||||
super({
|
||||
// navigation
|
||||
goToUpgradePage: () => {},
|
||||
goToPricingPage: () => {},
|
||||
// the top level license
|
||||
license: undefined,
|
||||
isFreePlan: true,
|
||||
isEnterprisePlan: true,
|
||||
isBusinessPlan: true,
|
||||
// features
|
||||
groupsEnabled: false,
|
||||
backupsEnabled: false,
|
||||
brandingEnabled: false,
|
||||
scimEnabled: false,
|
||||
environmentVariablesEnabled: false,
|
||||
budibaseAIEnabled: false,
|
||||
customAIConfigsEnabled: false,
|
||||
auditLogsEnabled: false,
|
||||
// the currently used quotas from the db
|
||||
quotaUsage: undefined,
|
||||
// derived quota metrics for percentages used
|
||||
usageMetrics: undefined,
|
||||
// quota reset
|
||||
quotaResetDaysRemaining: undefined,
|
||||
quotaResetDate: undefined,
|
||||
// failed payments
|
||||
accountPastDue: undefined,
|
||||
pastDueEndDate: undefined,
|
||||
pastDueDaysRemaining: undefined,
|
||||
accountDowngraded: undefined,
|
||||
// user limits
|
||||
userCount: undefined,
|
||||
userLimit: undefined,
|
||||
userLimitReached: false,
|
||||
errUserLimit: false,
|
||||
})
|
||||
}
|
||||
|
||||
usersLimitReached(
|
||||
userCount: number,
|
||||
userLimit: number = get(this.store).userLimit
|
||||
) {
|
||||
if (userLimit === UNLIMITED) {
|
||||
return false
|
||||
}
|
||||
return userCount >= userLimit
|
||||
}
|
||||
|
||||
usersLimitExceeded(
|
||||
userCount: number,
|
||||
userLimit: number = get(this.store).userLimit
|
||||
) {
|
||||
if (userLimit === UNLIMITED) {
|
||||
return false
|
||||
}
|
||||
return userCount > userLimit
|
||||
}
|
||||
|
||||
async isCloud() {
|
||||
let adminStore = get(admin)
|
||||
if (!adminStore.loaded) {
|
||||
await admin.init()
|
||||
adminStore = get(admin)
|
||||
}
|
||||
return adminStore.cloud
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.setNavigation()
|
||||
this.setLicense()
|
||||
await this.setQuotaUsage()
|
||||
}
|
||||
|
||||
setNavigation() {
|
||||
const adminStore = get(admin)
|
||||
const authStore = get(auth)
|
||||
|
||||
const upgradeUrl = authStore?.user?.accountPortalAccess
|
||||
? `${adminStore.accountPortalUrl}/portal/upgrade`
|
||||
: "/builder/portal/account/upgrade"
|
||||
|
||||
const goToUpgradePage = () => {
|
||||
window.location.href = upgradeUrl
|
||||
}
|
||||
const goToPricingPage = () => {
|
||||
window.open("https://budibase.com/pricing/", "_blank")
|
||||
}
|
||||
this.update(state => {
|
||||
return {
|
||||
...state,
|
||||
goToUpgradePage,
|
||||
goToPricingPage,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setLicense() {
|
||||
const license = get(auth).user?.license
|
||||
const planType = license?.plan.type
|
||||
const features = license?.features || []
|
||||
const isEnterprisePlan = planType === Constants.PlanType.ENTERPRISE
|
||||
const isFreePlan = planType === Constants.PlanType.FREE
|
||||
const isBusinessPlan = planType === Constants.PlanType.BUSINESS
|
||||
const isEnterpriseTrial =
|
||||
planType === Constants.PlanType.ENTERPRISE_BASIC_TRIAL
|
||||
const groupsEnabled = features.includes(Constants.Features.USER_GROUPS)
|
||||
const backupsEnabled = features.includes(Constants.Features.APP_BACKUPS)
|
||||
const scimEnabled = features.includes(Constants.Features.SCIM)
|
||||
const environmentVariablesEnabled = features.includes(
|
||||
Constants.Features.ENVIRONMENT_VARIABLES
|
||||
)
|
||||
const enforceableSSO = features.includes(Constants.Features.ENFORCEABLE_SSO)
|
||||
const brandingEnabled = features.includes(Constants.Features.BRANDING)
|
||||
const auditLogsEnabled = features.includes(Constants.Features.AUDIT_LOGS)
|
||||
const syncAutomationsEnabled = features.includes(
|
||||
Constants.Features.SYNC_AUTOMATIONS
|
||||
)
|
||||
const triggerAutomationRunEnabled = features.includes(
|
||||
Constants.Features.TRIGGER_AUTOMATION_RUN
|
||||
)
|
||||
const perAppBuildersEnabled = features.includes(
|
||||
Constants.Features.APP_BUILDERS
|
||||
)
|
||||
const budibaseAIEnabled = features.includes(Constants.Features.BUDIBASE_AI)
|
||||
const customAIConfigsEnabled = features.includes(
|
||||
Constants.Features.AI_CUSTOM_CONFIGS
|
||||
)
|
||||
this.update(state => {
|
||||
return {
|
||||
...state,
|
||||
license,
|
||||
isEnterprisePlan,
|
||||
isFreePlan,
|
||||
isBusinessPlan,
|
||||
isEnterpriseTrial,
|
||||
groupsEnabled,
|
||||
backupsEnabled,
|
||||
brandingEnabled,
|
||||
budibaseAIEnabled,
|
||||
customAIConfigsEnabled,
|
||||
scimEnabled,
|
||||
environmentVariablesEnabled,
|
||||
auditLogsEnabled,
|
||||
enforceableSSO,
|
||||
syncAutomationsEnabled,
|
||||
triggerAutomationRunEnabled,
|
||||
perAppBuildersEnabled,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async setQuotaUsage() {
|
||||
const quotaUsage = await API.getQuotaUsage()
|
||||
this.update(state => {
|
||||
return {
|
||||
...state,
|
||||
quotaUsage,
|
||||
}
|
||||
})
|
||||
await this.setUsageMetrics()
|
||||
}
|
||||
|
||||
async setUsageMetrics() {
|
||||
const usage = get(this.store).quotaUsage
|
||||
const license = get(auth).user?.license
|
||||
const now = new Date()
|
||||
if (!license || !usage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Process monthly metrics
|
||||
const monthlyMetrics = [
|
||||
MonthlyQuotaName.QUERIES,
|
||||
MonthlyQuotaName.AUTOMATIONS,
|
||||
].reduce((acc: MonthlyMetrics, key) => {
|
||||
const limit = license.quotas.usage.monthly[key].value
|
||||
const used = (usage.monthly.current?.[key] || 0 / limit) * 100
|
||||
acc[key] = limit > -1 ? Math.floor(used) : -1
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
// Process static metrics
|
||||
const staticMetrics = [StaticQuotaName.APPS, StaticQuotaName.ROWS].reduce(
|
||||
(acc: StaticMetrics, key) => {
|
||||
const limit = license.quotas.usage.static[key].value
|
||||
const used = (usage.usageQuota[key] || 0 / limit) * 100
|
||||
acc[key] = limit > -1 ? Math.floor(used) : -1
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
const getDaysBetween = (dateStart: Date, dateEnd: Date) => {
|
||||
return dateEnd > dateStart
|
||||
? Math.round((dateEnd.getTime() - dateStart.getTime()) / ONE_DAY_MILLIS)
|
||||
: 0
|
||||
}
|
||||
|
||||
const quotaResetDate = new Date(usage.quotaReset)
|
||||
const quotaResetDaysRemaining = getDaysBetween(now, quotaResetDate)
|
||||
|
||||
const accountDowngraded =
|
||||
license.billing?.subscription?.downgradeAt &&
|
||||
license.billing?.subscription?.downgradeAt <= now.getTime() &&
|
||||
license.billing?.subscription?.status === StripeStatus.PAST_DUE &&
|
||||
license.plan.type === Constants.PlanType.FREE
|
||||
|
||||
const pastDueAtMilliseconds = license.billing?.subscription?.pastDueAt
|
||||
const downgradeAtMilliseconds = license.billing?.subscription?.downgradeAt
|
||||
let pastDueDaysRemaining: number
|
||||
let pastDueEndDate: Date
|
||||
|
||||
if (pastDueAtMilliseconds && downgradeAtMilliseconds) {
|
||||
pastDueEndDate = new Date(downgradeAtMilliseconds)
|
||||
pastDueDaysRemaining = getDaysBetween(
|
||||
new Date(pastDueAtMilliseconds),
|
||||
pastDueEndDate
|
||||
)
|
||||
}
|
||||
|
||||
const userQuota = license.quotas.usage.static.users
|
||||
const userLimit = userQuota.value
|
||||
const userCount = usage.usageQuota.users
|
||||
const userLimitReached = this.usersLimitReached(userCount, userLimit)
|
||||
const userLimitExceeded = this.usersLimitExceeded(userCount, userLimit)
|
||||
const isCloudAccount = await this.isCloud()
|
||||
const errUserLimit =
|
||||
isCloudAccount &&
|
||||
license.plan.model === PlanModel.PER_USER &&
|
||||
userLimitExceeded
|
||||
|
||||
this.update(state => {
|
||||
return {
|
||||
...state,
|
||||
usageMetrics: { ...monthlyMetrics, ...staticMetrics },
|
||||
quotaResetDaysRemaining,
|
||||
quotaResetDate,
|
||||
accountDowngraded,
|
||||
accountPastDue: pastDueAtMilliseconds != null,
|
||||
pastDueEndDate,
|
||||
pastDueDaysRemaining,
|
||||
// user limits
|
||||
userCount,
|
||||
userLimit,
|
||||
userLimitReached,
|
||||
errUserLimit,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const licensing = new LicensingStore()
|
|
@ -1,4 +1,5 @@
|
|||
import { DevInfo, User } from "../../../documents"
|
||||
import { License } from "../../../sdk"
|
||||
import { Account, DevInfo, User } from "../../../documents"
|
||||
|
||||
export interface GenerateAPIKeyRequest {
|
||||
userId?: string
|
||||
|
@ -9,4 +10,9 @@ export interface FetchAPIKeyResponse extends DevInfo {}
|
|||
|
||||
export interface GetGlobalSelfResponse extends User {
|
||||
flags?: Record<string, any>
|
||||
account?: Account
|
||||
license: License
|
||||
budibaseAccess: boolean
|
||||
accountPortalAccess: boolean
|
||||
csrfToken: boolean
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue