From 402c217800298aaa3d819f6bc2a9acc822663fbb Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Tue, 26 Apr 2022 11:28:31 +0100
Subject: [PATCH 01/27] BB logo on free plan
---
.../src/components/MadeInBudibase.svelte | 15 +++++++-------
.../client/src/components/app/Layout.svelte | 7 +++++++
packages/client/src/licensing/constants.js | 6 ++++++
packages/client/src/licensing/features.js | 5 +++++
packages/client/src/licensing/index.js | 7 +++++++
packages/client/src/licensing/utils.js | 20 +++++++++++++++++++
packages/server/src/api/controllers/auth.js | 11 ++++++++++
7 files changed, 63 insertions(+), 8 deletions(-)
create mode 100644 packages/client/src/licensing/constants.js
create mode 100644 packages/client/src/licensing/features.js
create mode 100644 packages/client/src/licensing/index.js
create mode 100644 packages/client/src/licensing/utils.js
diff --git a/packages/client/src/components/MadeInBudibase.svelte b/packages/client/src/components/MadeInBudibase.svelte
index 2e5d6336f1..59b95e0f44 100644
--- a/packages/client/src/components/MadeInBudibase.svelte
+++ b/packages/client/src/components/MadeInBudibase.svelte
@@ -1,15 +1,19 @@
-
+ import { Link } from "@budibase/bbui"
+
+
+
Made In Budibase
-
+
diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts
index 57a18e7df3..7183c083ef 100644
--- a/packages/server/src/api/controllers/application.ts
+++ b/packages/server/src/api/controllers/application.ts
@@ -443,7 +443,6 @@ const destroyApp = async (ctx: any) => {
const result = await db.destroy()
if (isUnpublish) {
- await quotas.removePublishedApp()
await events.app.unpublished(app)
} else {
await quotas.removeApp()
diff --git a/packages/server/src/api/controllers/deploy/index.ts b/packages/server/src/api/controllers/deploy/index.ts
index 7c1a093398..680f2d534e 100644
--- a/packages/server/src/api/controllers/deploy/index.ts
+++ b/packages/server/src/api/controllers/deploy/index.ts
@@ -188,12 +188,7 @@ const _deployApp = async function (ctx: any) {
console.log("Deploying app...")
- let app
- if (await isFirstDeploy()) {
- app = await quotas.addPublishedApp(() => deployApp(deployment))
- } else {
- app = await deployApp(deployment)
- }
+ let app = await deployApp(deployment)
await events.app.published(app)
ctx.body = deployment
diff --git a/packages/server/src/migrations/functions/backfill/global.ts b/packages/server/src/migrations/functions/backfill/global.ts
index 5dd812d6ea..7c8558c2df 100644
--- a/packages/server/src/migrations/functions/backfill/global.ts
+++ b/packages/server/src/migrations/functions/backfill/global.ts
@@ -35,12 +35,10 @@ const formatUsage = (usage: QuotaUsage) => {
let maxAutomations = 0
let maxQueries = 0
let rows = 0
- let developers = 0
if (usage) {
if (usage.usageQuota) {
rows = usage.usageQuota.rows
- developers = usage.usageQuota.developers
}
if (usage.monthly) {
@@ -59,7 +57,6 @@ const formatUsage = (usage: QuotaUsage) => {
maxAutomations,
maxQueries,
rows,
- developers,
}
}
diff --git a/packages/server/src/migrations/functions/developerQuota.ts b/packages/server/src/migrations/functions/developerQuota.ts
deleted file mode 100644
index b639e0bd83..0000000000
--- a/packages/server/src/migrations/functions/developerQuota.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-const { createUserBuildersView } = require("@budibase/backend-core/db")
-import * as syncDevelopers from "./usageQuotas/syncDevelopers"
-
-/**
- * Date:
- * March 2022
- *
- * Description:
- * Create the builder users view and sync the developer count
- */
-
-export const run = async (db: any) => {
- await createUserBuildersView(db)
- await syncDevelopers.run()
-}
diff --git a/packages/server/src/migrations/functions/publishedAppsQuota.ts b/packages/server/src/migrations/functions/publishedAppsQuota.ts
deleted file mode 100644
index 53bd8e65e5..0000000000
--- a/packages/server/src/migrations/functions/publishedAppsQuota.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as syncPublishedApps from "./usageQuotas/syncPublishedApps"
-
-/**
- * Date:
- * March 2022
- *
- * Description:
- * Sync the published apps count
- */
-
-export const run = async (db: any) => {
- await syncPublishedApps.run()
-}
diff --git a/packages/server/src/migrations/functions/usageQuotas/syncDevelopers.ts b/packages/server/src/migrations/functions/usageQuotas/syncDevelopers.ts
deleted file mode 100644
index c13a095b23..0000000000
--- a/packages/server/src/migrations/functions/usageQuotas/syncDevelopers.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { getTenantId } from "@budibase/backend-core/tenancy"
-import { utils } from "@budibase/backend-core"
-import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
-
-export const run = async () => {
- // get developer count
- const developerCount = await utils.getBuildersCount()
-
- // sync developer count
- const tenantId = getTenantId()
- console.log(
- `[Tenant: ${tenantId}] Syncing developer count: ${developerCount}`
- )
- await quotas.setUsage(
- developerCount,
- StaticQuotaName.DEVELOPERS,
- QuotaUsageType.STATIC
- )
-}
diff --git a/packages/server/src/migrations/functions/usageQuotas/syncPublishedApps.ts b/packages/server/src/migrations/functions/usageQuotas/syncPublishedApps.ts
deleted file mode 100644
index 550a3006b3..0000000000
--- a/packages/server/src/migrations/functions/usageQuotas/syncPublishedApps.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { getTenantId } from "@budibase/backend-core/tenancy"
-import { getAllApps } from "@budibase/backend-core/db"
-import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
-
-export const run = async () => {
- // get app count
- const opts: any = { dev: false }
- const prodApps = await getAllApps(opts)
- const prodAppCount = prodApps ? prodApps.length : 0
-
- // sync app count
- const tenantId = getTenantId()
- console.log(
- `[Tenant: ${tenantId}] Syncing published app count: ${prodAppCount}`
- )
- await quotas.setUsage(
- prodAppCount,
- StaticQuotaName.PUBLISHED_APPS,
- QuotaUsageType.STATIC
- )
-}
diff --git a/packages/server/src/migrations/index.ts b/packages/server/src/migrations/index.ts
index 494740d1d9..bb691f1133 100644
--- a/packages/server/src/migrations/index.ts
+++ b/packages/server/src/migrations/index.ts
@@ -6,8 +6,6 @@ import env from "../environment"
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
import * as quota1 from "./functions/quotas1"
import * as appUrls from "./functions/appUrls"
-import * as developerQuota from "./functions/developerQuota"
-import * as publishedAppsQuota from "./functions/publishedAppsQuota"
import * as backfill from "./functions/backfill"
/**
@@ -42,20 +40,6 @@ export const buildMigrations = () => {
})
break
}
- case MigrationName.DEVELOPER_QUOTA: {
- serverMigrations.push({
- ...definition,
- fn: developerQuota.run,
- })
- break
- }
- case MigrationName.PUBLISHED_APP_QUOTA: {
- serverMigrations.push({
- ...definition,
- fn: publishedAppsQuota.run,
- })
- break
- }
case MigrationName.EVENT_APP_BACKFILL: {
serverMigrations.push({
...definition,
diff --git a/packages/types/src/sdk/migrations.ts b/packages/types/src/sdk/migrations.ts
index bb32d2e045..3ad4daccaf 100644
--- a/packages/types/src/sdk/migrations.ts
+++ b/packages/types/src/sdk/migrations.ts
@@ -41,8 +41,6 @@ export enum MigrationName {
USER_EMAIL_VIEW_CASING = "user_email_view_casing",
QUOTAS_1 = "quotas_1",
APP_URLS = "app_urls",
- DEVELOPER_QUOTA = "developer_quota",
- PUBLISHED_APP_QUOTA = "published_apps_quota",
EVENT_APP_BACKFILL = "event_app_backfill",
EVENT_GLOBAL_BACKFILL = "event_global_backfill",
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts
index 8e9b3382e4..5355b82b72 100644
--- a/packages/worker/src/sdk/users/users.ts
+++ b/packages/worker/src/sdk/users/users.ts
@@ -1,5 +1,4 @@
import env from "../../environment"
-import { quotas } from "@budibase/pro"
import * as apps from "../../utilities/appService"
import * as eventHelpers from "./events"
import {
@@ -164,15 +163,7 @@ export const save = async (
return putOpts
}
// save the user to db
- let response
- const putUserFn = () => {
- return db.put(user)
- }
- if (eventHelpers.isAddingBuilder(user, dbUser)) {
- response = await quotas.addDeveloper(putUserFn)
- } else {
- response = await putUserFn()
- }
+ let response = await db.put(user)
user._rev = response.rev
await eventHelpers.handleSaveEvents(user, dbUser)
@@ -223,7 +214,6 @@ export const destroy = async (id: string, currentUser: any) => {
await deprovisioning.removeUserFromInfoDB(dbUser)
await db.remove(dbUser._id, dbUser._rev)
await eventHelpers.handleDeleteEvents(dbUser)
- await quotas.removeUser(dbUser)
await cache.user.invalidateUser(dbUser._id)
await sessions.invalidateSessions(dbUser._id)
// let server know to sync user
From d4d542e77399b1f4f23260cad3748377c9d62cad Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Tue, 30 Aug 2022 09:53:16 +0100
Subject: [PATCH 06/27] Usage page updates WIP
---
.../builder/src/components/usage/Usage.svelte | 52 +++-
.../src/components/usage/UsageDashCard.svelte | 108 +++++++
.../builder/src/components/usage/index.js | 2 +
packages/builder/src/constants/index.js | 7 +
.../builder/portal/settings/usage.svelte | 279 ++++++++++++++----
5 files changed, 381 insertions(+), 67 deletions(-)
create mode 100644 packages/builder/src/components/usage/UsageDashCard.svelte
create mode 100644 packages/builder/src/components/usage/index.js
diff --git a/packages/builder/src/components/usage/Usage.svelte b/packages/builder/src/components/usage/Usage.svelte
index cd9071785d..27383019e8 100644
--- a/packages/builder/src/components/usage/Usage.svelte
+++ b/packages/builder/src/components/usage/Usage.svelte
@@ -1,10 +1,13 @@
- {usage.name}
+
{#if unlimited}
- {usage.used}
+ {usage.used} / Unlimited
{:else}
{usage.used} / {usage.total}
{/if}
{#if unlimited}
- Unlimited
+
{:else}
-
+
+ {/if}
+ {#if showWarning}
+
+ To get more queries
upgrade your plan
+
{/if}
@@ -51,6 +82,13 @@
display: flex;
flex-direction: row;
justify-content: space-between;
- gap: var(--spacing-m);
+ margin-bottom: 12px;
+ }
+ .header-container {
+ display: flex;
+ }
+ .heading {
+ margin-top: 3px;
+ margin-left: 5px;
}
diff --git a/packages/builder/src/components/usage/UsageDashCard.svelte b/packages/builder/src/components/usage/UsageDashCard.svelte
new file mode 100644
index 0000000000..ff0ac7ca49
--- /dev/null
+++ b/packages/builder/src/components/usage/UsageDashCard.svelte
@@ -0,0 +1,108 @@
+
+
+
+
+
diff --git a/packages/builder/src/components/usage/index.js b/packages/builder/src/components/usage/index.js
new file mode 100644
index 0000000000..48c0e2ea2c
--- /dev/null
+++ b/packages/builder/src/components/usage/index.js
@@ -0,0 +1,2 @@
+export { default as Usage } from "./Usage.svelte"
+export { default as DashCard } from "./UsageDashCard.svelte"
diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.js
index 4e2ca37b9c..bcfbfffb81 100644
--- a/packages/builder/src/constants/index.js
+++ b/packages/builder/src/constants/index.js
@@ -57,3 +57,10 @@ export const DefaultAppTheme = {
navBackground: "var(--spectrum-global-color-gray-50)",
navTextColor: "var(--spectrum-global-color-gray-800)",
}
+
+export const PlanType = {
+ FREE: "free",
+ PRO: "pro",
+ BUSINESS: "business",
+ ENTERPRISE: "enterprise",
+}
diff --git a/packages/builder/src/pages/builder/portal/settings/usage.svelte b/packages/builder/src/pages/builder/portal/settings/usage.svelte
index 069c37b555..6ee7e45e25 100644
--- a/packages/builder/src/pages/builder/portal/settings/usage.svelte
+++ b/packages/builder/src/pages/builder/portal/settings/usage.svelte
@@ -5,20 +5,39 @@
Heading,
Layout,
notifications,
- Link,
+ Page,
+ Detail,
} from "@budibase/bbui"
import { onMount } from "svelte"
- import { admin, auth, licensing } from "stores/portal"
- import Usage from "components/usage/Usage.svelte"
+ import { admin, auth, licensing } from "../../../../stores/portal"
+ import { PlanType } from "../../../../constants"
+ import { DashCard, Usage } from "../../../../components/usage"
let staticUsage = []
let monthlyUsage = []
+ let price
+ let lastPayment
+ let cancelAt
+ let nextPayment
+ let balance
let loaded = false
+ let textRows = []
+ let daysRemainingInMonth
+
+ const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
+ const manageUrl = `${$admin.accountPortalUrl}/portal/billing`
+
+ const warnUsage = ["Queries", "Automations", "Rows"]
$: quotaUsage = $licensing.quotaUsage
$: license = $auth.user?.license
- const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
+ const numberFormatter = new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ })
const setMonthlyUsage = () => {
monthlyUsage = []
@@ -34,6 +53,7 @@
}
}
}
+ monthlyUsage = monthlyUsage.sort((a, b) => a.name.localeCompare(b.name))
}
const setStaticUsage = () => {
@@ -48,6 +68,52 @@
})
}
}
+ staticUsage = staticUsage.sort((a, b) => a.name.localeCompare(b.name))
+ }
+
+ const setNextPayment = () => {
+ const periodEnd = license?.billing.subscription?.currentPeriodEnd
+ const cancelAt = license?.billing.subscription?.cancelAt
+ if (periodEnd) {
+ if (cancelAt && periodEnd <= cancelAt) {
+ return
+ }
+ nextPayment = `Next payment: ${getLocaleDataString(periodEnd)}`
+ }
+ }
+
+ const setCancelAt = () => {
+ cancelAt = license?.billing.subscription?.cancelAt
+ }
+
+ const setLastPayment = () => {
+ const periodStart = license?.billing.subscription?.currentPeriodStart
+ if (periodStart) {
+ lastPayment = `Last payment: ${getLocaleDataString(periodStart)}`
+ }
+ }
+
+ const setBalance = () => {
+ const customerBalance = license?.billing.customer.balance
+ if (customerBalance) {
+ balance = `Balance: ${numberFormatter.format(
+ (customerBalance / 100) * -1
+ )}`
+ }
+ }
+
+ const getLocaleDataString = epoch => {
+ const date = new Date(epoch * 1000)
+ return date.toLocaleDateString("default", {
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ })
+ }
+
+ const setPrice = () => {
+ const planPrice = license.plan.price
+ price = `${numberFormatter.format(planPrice.amountMonthly / 100)} per month`
}
const capitalise = string => {
@@ -56,6 +122,69 @@
}
}
+ const planTitle = () => {
+ return capitalise(license?.plan.type)
+ }
+
+ const planSubtitle = () => {
+ return `${license?.plan.price.sessions} day passes`
+ }
+
+ const getDaysRemaining = timestamp => {
+ if (!timestamp) {
+ return
+ }
+ const now = new Date()
+ now.setHours(0)
+ now.setMinutes(0)
+
+ const thenDate = new Date(timestamp)
+ thenDate.setHours(0)
+ thenDate.setMinutes(0)
+
+ const difference = thenDate.getTime() - now
+ // return the difference in days
+ return (difference / (1000 * 3600 * 24)).toFixed(0)
+ }
+
+ const setTextRows = () => {
+ textRows = []
+
+ if (cancelAt) {
+ textRows.push("Subscription has been cancelled")
+ textRows.push(`${getDaysRemaining(cancelAt * 1000)} days remaining`)
+ } else {
+ if (price) {
+ textRows.push(price)
+ }
+ if (lastPayment) {
+ textRows.push(lastPayment)
+ }
+ if (nextPayment) {
+ textRows.push(nextPayment)
+ }
+ }
+ }
+
+ const setDaysRemainingInMonth = () => {
+ let now = new Date()
+ now = new Date(now.getFullYear(), now.getMonth(), now.getDate())
+
+ const firstNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
+ const difference = firstNextMonth.getTime() - now.getTime()
+
+ // return the difference in days
+ daysRemainingInMonth = (difference / (1000 * 3600 * 24)).toFixed(0)
+ }
+
+ const goToAccountPortal = () => {
+ if (license?.plan.type === PlanType.FREE) {
+ window.location.href = upgradeUrl
+ } else {
+ window.location.href = manageUrl
+ }
+ }
+
const init = async () => {
try {
await licensing.getQuotaUsage()
@@ -71,69 +200,99 @@
})
$: {
- if (license && quotaUsage) {
- setMonthlyUsage()
- setStaticUsage()
+ if (license) {
+ setPrice()
+ setBalance()
+ setLastPayment()
+ setNextPayment()
+ setCancelAt()
+ setTextRows()
+ setDaysRemainingInMonth()
+
+ if (quotaUsage) {
+ setMonthlyUsage()
+ setStaticUsage()
+ }
}
}
-{#if loaded}
-
- Usage
- Get information about your current usage within Budibase.
- {#if $admin.cloud}
- {#if $auth.user?.accountPortalAccess}
- To upgrade your plan and usage limits visit your Account.
- {:else}
- Contact your account holder to upgrade your usage limits.
- {/if}
- {/if}
-
-
-
-
-
-
-
- YOUR PLAN
- {capitalise(license?.plan.type)}
-
-
- USAGE
-
- {#each staticUsage as usage}
-
-
-
- {/each}
-
-
- {#if monthlyUsage.length}
-
- MONTHLY
-
- {#each monthlyUsage as usage}
-
-
-
- {/each}
-
+
+ {#if loaded}
+
+
+ Billing
+ Get information about your current usage and manage your plan
-
- {/if}
-
-{/if}
+
+
+
+
+
+
+ {#each staticUsage as usage}
+
+
+
+ {/each}
+
+
+
+ {#if monthlyUsage.length}
+
+
+ Monthly
+
+ Resets in {daysRemainingInMonth} days
+
+
+
+ {#each monthlyUsage as usage}
+
+
+
+ {/each}
+
+
+
+
+ {/if}
+
+
+
+ {/if}
+
From 82e8e23dc52f7d29bc0cb91b29bb431ab337c2aa Mon Sep 17 00:00:00 2001
From: Dean
Date: Wed, 31 Aug 2022 15:19:57 +0100
Subject: [PATCH 07/27] Updated bootstrapping flow to include the account
portal.
---
scripts/link-dependencies.sh | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/scripts/link-dependencies.sh b/scripts/link-dependencies.sh
index 052f8aed8e..d2c71f637b 100755
--- a/scripts/link-dependencies.sh
+++ b/scripts/link-dependencies.sh
@@ -20,6 +20,7 @@ cd -
if [ -d "../budibase-pro" ]; then
cd ../budibase-pro
+ echo "Bootstrapping budibase-pro"
yarn bootstrap
cd packages/pro
@@ -44,7 +45,11 @@ if [ -d "../budibase-pro" ]; then
fi
if [ -d "../account-portal" ]; then
- cd ../account-portal/packages/server
+ cd ../account-portal
+ echo "Bootstrapping account-portal"
+ yarn bootstrap
+
+ cd packages/server
echo "Linking backend-core to account-portal"
yarn link "@budibase/backend-core"
From 7afcaadc19a58ebef4be51c253a8a2f3132f8acf Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Thu, 1 Sep 2022 11:36:23 +0100
Subject: [PATCH 08/27] Billing and usage page updates to support different
kinds of users and plans
---
.../builder/src/components/usage/Usage.svelte | 11 +-
.../src/components/usage/UsageDashCard.svelte | 18 +-
.../builder/portal/settings/usage.svelte | 222 +++++++-----------
3 files changed, 100 insertions(+), 151 deletions(-)
diff --git a/packages/builder/src/components/usage/Usage.svelte b/packages/builder/src/components/usage/Usage.svelte
index 27383019e8..49c4205a4c 100644
--- a/packages/builder/src/components/usage/Usage.svelte
+++ b/packages/builder/src/components/usage/Usage.svelte
@@ -1,6 +1,6 @@
-
- {#if loaded}
-
-
- Billing
- Get information about your current usage and manage your plan
-
-
-
-
-
-
-
- {#each staticUsage as usage}
-
-
-
- {/each}
-
-
-
- {#if monthlyUsage.length}
-
-
- Monthly
-
- Resets in {daysRemainingInMonth} days
-
-
-
- {#each monthlyUsage as usage}
-
-
-
- {/each}
-
-
-
-
- {/if}
-
-
+{#if loaded}
+
+
+ Usage
+ Get information about your current usage within Budibase.
+ {#if accountPortalAccess}
+ To upgrade your plan and usage limits visit your Account
+ {:else}
+ To upgrade your plan and usage limits contact your account holder
+ {/if}
+
- {/if}
-
+
+
+
+
+
+
+ {#each staticUsage as usage}
+
+
+
+ {/each}
+
+
+
+ {#if monthlyUsage.length}
+
+
+ Monthly
+
+ Resets in {daysRemainingInMonth} days
+
+
+
+ {#each monthlyUsage as usage}
+
+
+
+ {/each}
+
+
+
+
+ {/if}
+
+
+
+{/if}
From 07a838ad729b6c257f28ec23eca3aafc3313af2b Mon Sep 17 00:00:00 2001
From: Dean
Date: Thu, 1 Sep 2022 15:30:04 +0100
Subject: [PATCH 09/27] Merge commit
---
.../pages/builder/portal/apps/index.svelte | 6 +-
.../builder/src/stores/portal/licensing.js | 63 ++++++++++++++++++-
2 files changed, 67 insertions(+), 2 deletions(-)
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte
index 13d23f6a51..3bb7050c43 100644
--- a/packages/builder/src/pages/builder/portal/apps/index.svelte
+++ b/packages/builder/src/pages/builder/portal/apps/index.svelte
@@ -20,7 +20,7 @@
import { store, automationStore } from "builderStore"
import { API } from "api"
import { onMount } from "svelte"
- import { apps, auth, admin, templates } from "stores/portal"
+ import { apps, auth, admin, templates, licensing } from "stores/portal"
import download from "downloadjs"
import { goto } from "@roxi/routify"
import AppRow from "components/start/AppRow.svelte"
@@ -243,6 +243,10 @@
notifications.error("Error loading apps and templates")
}
loaded = true
+
+ //Testing
+ await licensing.getQuotaUsage()
+ await licensing.getTestData()
})
diff --git a/packages/builder/src/stores/portal/licensing.js b/packages/builder/src/stores/portal/licensing.js
index 653dab52ed..40bfcff323 100644
--- a/packages/builder/src/stores/portal/licensing.js
+++ b/packages/builder/src/stores/portal/licensing.js
@@ -1,5 +1,6 @@
-import { writable } from "svelte/store"
+import { writable, get } from "svelte/store"
import { API } from "api"
+import { auth } from "stores/portal"
export const createLicensingStore = () => {
const DEFAULT = {
@@ -18,6 +19,66 @@ export const createLicensingStore = () => {
}
})
},
+ getTestData: async () => {
+ const tenantId = get(auth).tenantId
+ console.log("Tenant ", tenantId)
+
+ const license = get(auth).user.license
+ console.log("User LICENSE ", license)
+
+ // Pull out the usage.
+ const quota = get(store).quotaUsage
+ console.log("Quota usage", quota)
+
+ // Would only initialise the usage elements if the account element is present.
+ console.log("User account ", get(auth).user.account)
+
+ //separate into functions that pass in both the usage and quota
+ //easier to test
+
+ const getMonthlyMetrics = (license, quota) => {
+ return ["sessions", "queries", "automations"].reduce((acc, key) => {
+ const quotaLimit = license.quotas.usage.monthly[key].value
+ acc[key] =
+ quotaLimit > -1
+ ? (quota.monthly.current[key] / quotaLimit) * 100
+ : -1
+ return acc
+ }, {})
+ }
+
+ const getStaticMetrics = (license, quota) => {
+ return ["apps", "rows"].reduce((acc, key) => {
+ const quotaLimit = license.quotas.usage.monthly[key].value
+ acc[key] =
+ quotaLimit > -1 ? (quota.usageQuota[key] / quotaLimit) * 100 : -1
+ return acc
+ }, {})
+ }
+
+ const modQuotaStr = JSON.stringify(quota)
+ const cloneQuota = JSON.parse(modQuotaStr)
+ cloneQuota.monthly.current.sessions = 4
+
+ const monthlyMetrics = getMonthlyMetrics(license, cloneQuota)
+ const staticMetrics = getStaticMetrics(license, cloneQuota)
+
+ console.log("Monthly Usage Metrics ", monthlyMetrics)
+ console.log("Static Usage Metrics ", staticMetrics)
+
+ const flagged = Object.keys(monthlyMetrics).filter(key => {
+ return monthlyMetrics[key] >= 100
+ })
+
+ console.log(flagged)
+
+ // store.update(state => {
+ // return {
+ // ...state,
+ // metrics,
+ // }
+ // })
+ },
}
return {
From ba211b8490277918b8dec091951bcde054810770 Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Tue, 6 Sep 2022 12:25:57 +0100
Subject: [PATCH 10/27] Day pass middleware
---
.../src/middleware/authenticated.ts | 18 ++++++++++--
.../backend-core/src/security/sessions.ts | 28 ++++---------------
packages/backend-core/src/users.ts | 4 ++-
packages/backend-core/src/utils.js | 12 ++++++++
.../src/api/controllers/static/index.ts | 1 -
packages/server/src/api/index.js | 3 +-
packages/types/src/documents/global/user.ts | 1 +
packages/types/src/sdk/auth.ts | 22 +++++++++++++++
packages/worker/src/api/index.ts | 8 ++++--
9 files changed, 68 insertions(+), 29 deletions(-)
diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts
index 062070785d..54e41bff57 100644
--- a/packages/backend-core/src/middleware/authenticated.ts
+++ b/packages/backend-core/src/middleware/authenticated.ts
@@ -10,6 +10,7 @@ import { getGlobalDB, doInTenant } from "../tenancy"
import { decrypt } from "../security/encryption"
const identity = require("../context/identity")
const env = require("../environment")
+import { User } from "@budibase/types"
const ONE_MINUTE = env.SESSION_UPDATE_PERIOD || 60 * 1000
@@ -67,7 +68,11 @@ async function checkApiKey(apiKey: string, populateUser?: Function) {
*/
export = (
noAuthPatterns = [],
- opts: { publicAllowed: boolean; populateUser?: Function } = {
+ opts: {
+ publicAllowed: boolean
+ populateUser?: Function
+ checkDayPass?: (ctx: any, user: User, tenantId: string) => Promise
+ } = {
publicAllowed: false,
}
) => {
@@ -106,7 +111,16 @@ export = (
user = await getUser(userId, session.tenantId)
}
user.csrfToken = session.csrfToken
- if (session?.lastAccessedAt < timeMinusOneMinute()) {
+
+ // check day passes for the current user
+ if (opts.checkDayPass) {
+ await opts.checkDayPass(ctx, user, session.tenantId)
+ }
+
+ if (
+ !session.lastAccessedAt ||
+ session.lastAccessedAt < timeMinusOneMinute()
+ ) {
// make sure we denote that the session is still in use
await updateSessionTTL(session)
}
diff --git a/packages/backend-core/src/security/sessions.ts b/packages/backend-core/src/security/sessions.ts
index f621b99dc2..33230afc60 100644
--- a/packages/backend-core/src/security/sessions.ts
+++ b/packages/backend-core/src/security/sessions.ts
@@ -2,28 +2,12 @@ const redis = require("../redis/init")
const { v4: uuidv4 } = require("uuid")
const { logWarn } = require("../logging")
const env = require("../environment")
-
-interface CreateSession {
- sessionId: string
- tenantId: string
- csrfToken?: string
-}
-
-interface Session extends CreateSession {
- userId: string
- lastAccessedAt: string
- createdAt: string
- // make optional attributes required
- csrfToken: string
-}
-
-interface SessionKey {
- key: string
-}
-
-interface ScannedSession {
- value: Session
-}
+import {
+ Session,
+ ScannedSession,
+ SessionKey,
+ CreateSession,
+} from "@budibase/types"
// a week in seconds
const EXPIRY_SECONDS = 86400 * 7
diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts
index 5d6d45a582..0793eeb1d9 100644
--- a/packages/backend-core/src/users.ts
+++ b/packages/backend-core/src/users.ts
@@ -13,7 +13,9 @@ import { User } from "@budibase/types"
* all the users to find one with this email address.
* @param {string} email the email to lookup the user by.
*/
-export const getGlobalUserByEmail = async (email: String) => {
+export const getGlobalUserByEmail = async (
+ email: String
+): Promise => {
if (email == null) {
throw "Must supply an email address to view"
}
diff --git a/packages/backend-core/src/utils.js b/packages/backend-core/src/utils.js
index 0587267e9a..6b59c7cb72 100644
--- a/packages/backend-core/src/utils.js
+++ b/packages/backend-core/src/utils.js
@@ -42,6 +42,18 @@ async function resolveAppUrl(ctx) {
return app && app.appId ? app.appId : undefined
}
+exports.isServingApp = ctx => {
+ // dev app
+ if (ctx.path.startsWith(`/${APP_PREFIX}`)) {
+ return true
+ }
+ // prod app
+ if (ctx.path.startsWith(PROD_APP_PREFIX)) {
+ return true
+ }
+ return false
+}
+
/**
* Given a request tries to find the appId, which can be located in various places
* @param {object} ctx The main request body to look through.
diff --git a/packages/server/src/api/controllers/static/index.ts b/packages/server/src/api/controllers/static/index.ts
index 3b748a6591..a72b51fcf8 100644
--- a/packages/server/src/api/controllers/static/index.ts
+++ b/packages/server/src/api/controllers/static/index.ts
@@ -18,7 +18,6 @@ const { DocumentType } = require("../../../db/utils")
const { getAppDB, getAppId } = require("@budibase/backend-core/context")
const { setCookie, clearCookie } = require("@budibase/backend-core/utils")
const AWS = require("aws-sdk")
-
const fs = require("fs")
const {
downloadTarballDirect,
diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js
index d156ca2997..ffcea09985 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -11,7 +11,7 @@ const zlib = require("zlib")
const { mainRoutes, staticRoutes, publicRoutes } = require("./routes")
const pkg = require("../../package.json")
const env = require("../environment")
-const { middleware: pro } = require("@budibase/pro")
+const { middleware: pro, quotas } = require("@budibase/pro")
const { shutdown } = require("./routes/public")
const router = new Router()
@@ -44,6 +44,7 @@ router
.use(
buildAuthMiddleware(null, {
publicAllowed: true,
+ checkDayPass: quotas.checkDayPass,
})
)
// nothing in the server should allow query string tenants
diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts
index ac16194a21..6c93bac1ac 100644
--- a/packages/types/src/documents/global/user.ts
+++ b/packages/types/src/documents/global/user.ts
@@ -16,6 +16,7 @@ export interface User extends Document {
createdAt?: number // override the default createdAt behaviour - users sdk historically set this to Date.now()
userGroups?: string[]
forceResetPassword?: boolean
+ dayPassRecordedAt?: string
}
export interface UserRoles {
diff --git a/packages/types/src/sdk/auth.ts b/packages/types/src/sdk/auth.ts
index dd3c2124b5..6a040abf77 100644
--- a/packages/types/src/sdk/auth.ts
+++ b/packages/types/src/sdk/auth.ts
@@ -3,3 +3,25 @@ export interface AuthToken {
tenantId: string
sessionId: string
}
+
+export interface CreateSession {
+ sessionId: string
+ tenantId: string
+ csrfToken?: string
+}
+
+export interface Session extends CreateSession {
+ userId: string
+ lastAccessedAt: string
+ createdAt: string
+ // make optional attributes required
+ csrfToken: string
+}
+
+export interface SessionKey {
+ key: string
+}
+
+export interface ScannedSession {
+ value: Session
+}
diff --git a/packages/worker/src/api/index.ts b/packages/worker/src/api/index.ts
index 692eff685c..5d809ba5b8 100644
--- a/packages/worker/src/api/index.ts
+++ b/packages/worker/src/api/index.ts
@@ -2,7 +2,7 @@ import Router from "@koa/router"
const compress = require("koa-compress")
const zlib = require("zlib")
import { routes } from "./routes"
-import { middleware as pro } from "@budibase/pro"
+import { middleware as pro, quotas } from "@budibase/pro"
import { errors, auth, middleware } from "@budibase/backend-core"
import { APIError } from "@budibase/types"
@@ -92,7 +92,11 @@ router
})
)
.use("/health", ctx => (ctx.status = 200))
- .use(auth.buildAuthMiddleware(PUBLIC_ENDPOINTS))
+ .use(
+ auth.buildAuthMiddleware(PUBLIC_ENDPOINTS, {
+ checkDayPass: quotas.checkDayPass,
+ })
+ )
.use(auth.buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS))
.use(auth.buildCsrfMiddleware({ noCsrfPatterns: NO_CSRF_ENDPOINTS }))
.use(pro.licensing())
From 4f66dc0df391e51f9ca5d21ab9b5f2f4aa46f006 Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Tue, 6 Sep 2022 16:24:36 +0100
Subject: [PATCH 11/27] Move day pass middleware from authenticated to
licensing, sent activity to account portal
---
.../src/middleware/authenticated.ts | 5 --
packages/server/src/api/index.js | 6 +-
packages/types/src/sdk/koa.ts | 2 +
packages/types/src/sdk/licensing/billing.ts | 20 +++++
packages/types/src/sdk/licensing/feature.ts | 3 +
packages/types/src/sdk/licensing/index.ts | 4 +
packages/types/src/sdk/licensing/license.ts | 9 +-
packages/types/src/sdk/licensing/plan.ts | 25 ++++++
packages/types/src/sdk/licensing/quota.ts | 82 +++++++++++++++++++
.../worker/src/api/controllers/global/self.js | 1 +
packages/worker/src/api/index.ts | 9 +-
.../src/api/routes/global/tests/self.spec.ts | 8 +-
12 files changed, 156 insertions(+), 18 deletions(-)
create mode 100644 packages/types/src/sdk/licensing/billing.ts
create mode 100644 packages/types/src/sdk/licensing/feature.ts
create mode 100644 packages/types/src/sdk/licensing/plan.ts
create mode 100644 packages/types/src/sdk/licensing/quota.ts
diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts
index 54e41bff57..0e1b31b9b7 100644
--- a/packages/backend-core/src/middleware/authenticated.ts
+++ b/packages/backend-core/src/middleware/authenticated.ts
@@ -112,11 +112,6 @@ export = (
}
user.csrfToken = session.csrfToken
- // check day passes for the current user
- if (opts.checkDayPass) {
- await opts.checkDayPass(ctx, user, session.tenantId)
- }
-
if (
!session.lastAccessedAt ||
session.lastAccessedAt < timeMinusOneMinute()
diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js
index ffcea09985..4cd574f557 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -11,7 +11,7 @@ const zlib = require("zlib")
const { mainRoutes, staticRoutes, publicRoutes } = require("./routes")
const pkg = require("../../package.json")
const env = require("../environment")
-const { middleware: pro, quotas } = require("@budibase/pro")
+const { middleware: pro } = require("@budibase/pro")
const { shutdown } = require("./routes/public")
const router = new Router()
@@ -44,7 +44,6 @@ router
.use(
buildAuthMiddleware(null, {
publicAllowed: true,
- checkDayPass: quotas.checkDayPass,
})
)
// nothing in the server should allow query string tenants
@@ -55,9 +54,8 @@ router
noTenancyRequired: true,
})
)
- .use(currentApp)
.use(pro.licensing())
- .use(pro.activity())
+ .use(currentApp)
.use(auditLog)
// error handling middleware
diff --git a/packages/types/src/sdk/koa.ts b/packages/types/src/sdk/koa.ts
index 5ab04a3e6f..8d419d5cf1 100644
--- a/packages/types/src/sdk/koa.ts
+++ b/packages/types/src/sdk/koa.ts
@@ -1,8 +1,10 @@
import { Context } from "koa"
import { User } from "../documents"
+import { License } from "../sdk"
export interface ContextUser extends User {
globalId?: string
+ license: License
}
export interface BBContext extends Context {
diff --git a/packages/types/src/sdk/licensing/billing.ts b/packages/types/src/sdk/licensing/billing.ts
new file mode 100644
index 0000000000..6743b9cb17
--- /dev/null
+++ b/packages/types/src/sdk/licensing/billing.ts
@@ -0,0 +1,20 @@
+import { PriceDuration } from "./plan"
+
+export interface CustomerBilling {
+ balance: number | null | undefined
+ currency: string | null | undefined
+}
+
+export interface SubscriptionBilling {
+ amount: number
+ quantity: number
+ duration: PriceDuration
+ cancelAt: number | null | undefined
+ currentPeriodStart: number
+ currentPeriodEnd: number
+}
+
+export interface Billing {
+ customer: CustomerBilling
+ subscription?: SubscriptionBilling
+}
diff --git a/packages/types/src/sdk/licensing/feature.ts b/packages/types/src/sdk/licensing/feature.ts
new file mode 100644
index 0000000000..cbd1f4a50c
--- /dev/null
+++ b/packages/types/src/sdk/licensing/feature.ts
@@ -0,0 +1,3 @@
+export enum Feature {
+ USER_GROUPS = "userGroups",
+}
diff --git a/packages/types/src/sdk/licensing/index.ts b/packages/types/src/sdk/licensing/index.ts
index e6080073b7..8d15ba96fc 100644
--- a/packages/types/src/sdk/licensing/index.ts
+++ b/packages/types/src/sdk/licensing/index.ts
@@ -1 +1,5 @@
export * from "./license"
+export * from "./plan"
+export * from "./quota"
+export * from "./feature"
+export * from "./billing"
diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts
index ed2e0a0245..d19a3a617d 100644
--- a/packages/types/src/sdk/licensing/license.ts
+++ b/packages/types/src/sdk/licensing/license.ts
@@ -1 +1,8 @@
-export interface License {}
+import { AccountPlan, Quotas, Feature, Billing } from "."
+
+export interface License {
+ features: Feature[]
+ quotas: Quotas
+ plan: AccountPlan
+ billing?: Billing
+}
diff --git a/packages/types/src/sdk/licensing/plan.ts b/packages/types/src/sdk/licensing/plan.ts
new file mode 100644
index 0000000000..6b226887b4
--- /dev/null
+++ b/packages/types/src/sdk/licensing/plan.ts
@@ -0,0 +1,25 @@
+export interface AccountPlan {
+ type: PlanType
+ price?: Price
+}
+
+export enum PlanType {
+ FREE = "free",
+ PRO = "pro",
+ BUSINESS = "business",
+ ENTERPRISE = "enterprise",
+}
+
+export enum PriceDuration {
+ MONTHLY = "monthly",
+ YEARLY = "yearly",
+}
+
+export interface Price {
+ amount: number
+ amountMonthly: number
+ currency: string
+ duration: PriceDuration
+ priceId: string
+ dayPasses: number
+}
diff --git a/packages/types/src/sdk/licensing/quota.ts b/packages/types/src/sdk/licensing/quota.ts
new file mode 100644
index 0000000000..578a5d98d0
--- /dev/null
+++ b/packages/types/src/sdk/licensing/quota.ts
@@ -0,0 +1,82 @@
+import { PlanType } from "."
+
+export enum QuotaUsageType {
+ STATIC = "static",
+ MONTHLY = "monthly",
+}
+
+export enum QuotaType {
+ USAGE = "usage",
+ CONSTANT = "constant",
+}
+
+export enum StaticQuotaName {
+ ROWS = "rows",
+ APPS = "apps",
+}
+
+export enum MonthlyQuotaName {
+ QUERIES = "queries",
+ AUTOMATIONS = "automations",
+ DAY_PASSES = "dayPasses",
+}
+
+export enum ConstantQuotaName {
+ QUERY_TIMEOUT_SECONDS = "queryTimeoutSeconds",
+ AUTOMATION_LOG_RETENTION_DAYS = "automationLogRetentionDays",
+}
+
+export type QuotaName = StaticQuotaName | MonthlyQuotaName | ConstantQuotaName
+
+export const isStaticQuota = (
+ quotaType: QuotaType,
+ usageType: QuotaUsageType,
+ name: QuotaName
+): name is StaticQuotaName => {
+ return quotaType === QuotaType.USAGE && usageType === QuotaUsageType.STATIC
+}
+
+export const isMonthlyQuota = (
+ quotaType: QuotaType,
+ usageType: QuotaUsageType,
+ name: QuotaName
+): name is MonthlyQuotaName => {
+ return quotaType === QuotaType.USAGE && usageType === QuotaUsageType.MONTHLY
+}
+
+export const isConstantQuota = (
+ quotaType: QuotaType,
+ name: QuotaName
+): name is ConstantQuotaName => {
+ return quotaType === QuotaType.CONSTANT
+}
+
+export type PlanQuotas = {
+ [PlanType.FREE]: Quotas
+ [PlanType.PRO]: Quotas
+ [PlanType.BUSINESS]: Quotas
+ [PlanType.ENTERPRISE]: Quotas
+}
+
+export type Quotas = {
+ [QuotaType.USAGE]: {
+ [QuotaUsageType.MONTHLY]: {
+ [MonthlyQuotaName.QUERIES]: Quota
+ [MonthlyQuotaName.AUTOMATIONS]: Quota
+ [MonthlyQuotaName.DAY_PASSES]: Quota
+ }
+ [QuotaUsageType.STATIC]: {
+ [StaticQuotaName.ROWS]: Quota
+ [StaticQuotaName.APPS]: Quota
+ }
+ }
+ [QuotaType.CONSTANT]: {
+ [ConstantQuotaName.QUERY_TIMEOUT_SECONDS]: Quota
+ [ConstantQuotaName.AUTOMATION_LOG_RETENTION_DAYS]: Quota
+ }
+}
+
+export interface Quota {
+ name: string
+ value: number
+}
diff --git a/packages/worker/src/api/controllers/global/self.js b/packages/worker/src/api/controllers/global/self.js
index 28afa69fa0..3e073243dc 100644
--- a/packages/worker/src/api/controllers/global/self.js
+++ b/packages/worker/src/api/controllers/global/self.js
@@ -144,6 +144,7 @@ exports.updateSelf = async ctx => {
}
// remove the old password from the user before sending events
+ user._rev = response.rev
delete user.password
await events.user.updated(user)
if (passwordChange) {
diff --git a/packages/worker/src/api/index.ts b/packages/worker/src/api/index.ts
index 5d809ba5b8..21fbf1d993 100644
--- a/packages/worker/src/api/index.ts
+++ b/packages/worker/src/api/index.ts
@@ -2,7 +2,7 @@ import Router from "@koa/router"
const compress = require("koa-compress")
const zlib = require("zlib")
import { routes } from "./routes"
-import { middleware as pro, quotas } from "@budibase/pro"
+import { middleware as pro } from "@budibase/pro"
import { errors, auth, middleware } from "@budibase/backend-core"
import { APIError } from "@budibase/types"
@@ -92,15 +92,10 @@ router
})
)
.use("/health", ctx => (ctx.status = 200))
- .use(
- auth.buildAuthMiddleware(PUBLIC_ENDPOINTS, {
- checkDayPass: quotas.checkDayPass,
- })
- )
+ .use(auth.buildAuthMiddleware(PUBLIC_ENDPOINTS))
.use(auth.buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS))
.use(auth.buildCsrfMiddleware({ noCsrfPatterns: NO_CSRF_ENDPOINTS }))
.use(pro.licensing())
- .use(pro.activity())
// for now no public access is allowed to worker (bar health check)
.use((ctx, next) => {
if (ctx.publicEndpoint) {
diff --git a/packages/worker/src/api/routes/global/tests/self.spec.ts b/packages/worker/src/api/routes/global/tests/self.spec.ts
index b3c91a9306..5640bab3ce 100644
--- a/packages/worker/src/api/routes/global/tests/self.spec.ts
+++ b/packages/worker/src/api/routes/global/tests/self.spec.ts
@@ -1,5 +1,5 @@
jest.mock("nodemailer")
-import { TestConfiguration, API } from "../../../../tests"
+import { TestConfiguration, API, mocks } from "../../../../tests"
import { events } from "@budibase/backend-core"
describe("/api/global/self", () => {
@@ -26,6 +26,9 @@ describe("/api/global/self", () => {
delete user.password
const res = await api.self.updateSelf(user)
+ const dbUser = await config.getUser(user.email)
+ user._rev = dbUser._rev
+ user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString()
expect(res.body._id).toBe(user._id)
expect(events.user.updated).toBeCalledTimes(1)
expect(events.user.updated).toBeCalledWith(user)
@@ -39,6 +42,9 @@ describe("/api/global/self", () => {
user.password = "newPassword"
const res = await api.self.updateSelf(user)
+ const dbUser = await config.getUser(user.email)
+ user._rev = dbUser._rev
+ user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString()
delete user.password
expect(res.body._id).toBe(user._id)
expect(events.user.updated).toBeCalledTimes(1)
From 6e1a30bc60bdd42b21f5bac99a9e1027fef76610 Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Wed, 7 Sep 2022 12:08:10 +0100
Subject: [PATCH 12/27] Error handling, wildcard feature flags
---
.../backend-core/src/featureFlags/index.js | 24 ++++++++++++-------
packages/worker/src/sdk/users/events.ts | 1 -
2 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/packages/backend-core/src/featureFlags/index.js b/packages/backend-core/src/featureFlags/index.js
index 103ac4df59..b328839fda 100644
--- a/packages/backend-core/src/featureFlags/index.js
+++ b/packages/backend-core/src/featureFlags/index.js
@@ -31,20 +31,26 @@ const TENANT_FEATURE_FLAGS = getFeatureFlags()
exports.isEnabled = featureFlag => {
const tenantId = tenancy.getTenantId()
-
- return (
- TENANT_FEATURE_FLAGS &&
- TENANT_FEATURE_FLAGS[tenantId] &&
- TENANT_FEATURE_FLAGS[tenantId].includes(featureFlag)
- )
+ const flags = exports.getTenantFeatureFlags(tenantId)
+ return flags.includes(featureFlag)
}
exports.getTenantFeatureFlags = tenantId => {
- if (TENANT_FEATURE_FLAGS && TENANT_FEATURE_FLAGS[tenantId]) {
- return TENANT_FEATURE_FLAGS[tenantId]
+ const flags = []
+
+ if (TENANT_FEATURE_FLAGS) {
+ const globalFlags = TENANT_FEATURE_FLAGS["*"]
+ const tenantFlags = TENANT_FEATURE_FLAGS[tenantId]
+
+ if (globalFlags) {
+ flags.push(...globalFlags)
+ }
+ if (tenantFlags) {
+ flags.push(...tenantFlags)
+ }
}
- return []
+ return flags
}
exports.FeatureFlag = {
diff --git a/packages/worker/src/sdk/users/events.ts b/packages/worker/src/sdk/users/events.ts
index 92413df173..0094c6fd84 100644
--- a/packages/worker/src/sdk/users/events.ts
+++ b/packages/worker/src/sdk/users/events.ts
@@ -83,7 +83,6 @@ export const handleSaveEvents = async (
}
} else {
await events.user.created(user)
- await pro.createAccountUser(user)
}
if (isAddingBuilder(user, existingUser)) {
From ae2f199807c0faeef462e4fa1bc964118c9511c3 Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Sun, 11 Sep 2022 20:25:51 +0100
Subject: [PATCH 13/27] Update InlineAlert with additional spacing around
button
---
packages/bbui/src/InlineAlert/InlineAlert.svelte | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/bbui/src/InlineAlert/InlineAlert.svelte b/packages/bbui/src/InlineAlert/InlineAlert.svelte
index 25e14b7caf..b99399bf8b 100644
--- a/packages/bbui/src/InlineAlert/InlineAlert.svelte
+++ b/packages/bbui/src/InlineAlert/InlineAlert.svelte
@@ -39,13 +39,16 @@
{splitMsg}
{/each}
{#if onConfirm}
-
From 5523b638329a0515afb7d9f185dc82441fff4e44 Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Mon, 12 Sep 2022 10:43:26 +0100
Subject: [PATCH 15/27] Add buttonText to InlineAlert, export TooltipWrapper,
update Account type with license key activate time, convert error package to
TS
---
packages/backend-core/src/environment.ts | 2 +-
packages/backend-core/src/errors/base.js | 11 -----
packages/backend-core/src/errors/base.ts | 10 +++++
packages/backend-core/src/errors/generic.js | 11 -----
packages/backend-core/src/errors/generic.ts | 7 +++
packages/backend-core/src/errors/http.js | 12 ------
packages/backend-core/src/errors/http.ts | 15 +++++++
.../src/errors/{index.js => index.ts} | 17 +++++---
packages/backend-core/src/errors/licensing.js | 43 -------------------
packages/backend-core/src/errors/licensing.ts | 39 +++++++++++++++++
.../builder/portal/settings/upgrade.svelte | 12 ++++--
.../types/src/documents/account/account.ts | 1 +
12 files changed, 91 insertions(+), 89 deletions(-)
delete mode 100644 packages/backend-core/src/errors/base.js
create mode 100644 packages/backend-core/src/errors/base.ts
delete mode 100644 packages/backend-core/src/errors/generic.js
create mode 100644 packages/backend-core/src/errors/generic.ts
delete mode 100644 packages/backend-core/src/errors/http.js
create mode 100644 packages/backend-core/src/errors/http.ts
rename packages/backend-core/src/errors/{index.js => index.ts} (65%)
delete mode 100644 packages/backend-core/src/errors/licensing.js
create mode 100644 packages/backend-core/src/errors/licensing.ts
diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts
index 0348d921ab..0588ece793 100644
--- a/packages/backend-core/src/environment.ts
+++ b/packages/backend-core/src/environment.ts
@@ -36,7 +36,7 @@ const env = {
MULTI_TENANCY: process.env.MULTI_TENANCY,
ACCOUNT_PORTAL_URL:
process.env.ACCOUNT_PORTAL_URL || "https://account.budibase.app",
- ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY,
+ ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY || "",
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""),
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
diff --git a/packages/backend-core/src/errors/base.js b/packages/backend-core/src/errors/base.js
deleted file mode 100644
index 7cb0c0fc23..0000000000
--- a/packages/backend-core/src/errors/base.js
+++ /dev/null
@@ -1,11 +0,0 @@
-class BudibaseError extends Error {
- constructor(message, code, type) {
- super(message)
- this.code = code
- this.type = type
- }
-}
-
-module.exports = {
- BudibaseError,
-}
diff --git a/packages/backend-core/src/errors/base.ts b/packages/backend-core/src/errors/base.ts
new file mode 100644
index 0000000000..801dcf168d
--- /dev/null
+++ b/packages/backend-core/src/errors/base.ts
@@ -0,0 +1,10 @@
+export class BudibaseError extends Error {
+ code: string
+ type: string
+
+ constructor(message: string, code: string, type: string) {
+ super(message)
+ this.code = code
+ this.type = type
+ }
+}
diff --git a/packages/backend-core/src/errors/generic.js b/packages/backend-core/src/errors/generic.js
deleted file mode 100644
index 5c7661f035..0000000000
--- a/packages/backend-core/src/errors/generic.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const { BudibaseError } = require("./base")
-
-class GenericError extends BudibaseError {
- constructor(message, code, type) {
- super(message, code, type ? type : "generic")
- }
-}
-
-module.exports = {
- GenericError,
-}
diff --git a/packages/backend-core/src/errors/generic.ts b/packages/backend-core/src/errors/generic.ts
new file mode 100644
index 0000000000..71b3352438
--- /dev/null
+++ b/packages/backend-core/src/errors/generic.ts
@@ -0,0 +1,7 @@
+import { BudibaseError } from "./base"
+
+export class GenericError extends BudibaseError {
+ constructor(message: string, code: string, type: string) {
+ super(message, code, type ? type : "generic")
+ }
+}
diff --git a/packages/backend-core/src/errors/http.js b/packages/backend-core/src/errors/http.js
deleted file mode 100644
index 8e7cab4638..0000000000
--- a/packages/backend-core/src/errors/http.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const { GenericError } = require("./generic")
-
-class HTTPError extends GenericError {
- constructor(message, httpStatus, code = "http", type = "generic") {
- super(message, code, type)
- this.status = httpStatus
- }
-}
-
-module.exports = {
- HTTPError,
-}
diff --git a/packages/backend-core/src/errors/http.ts b/packages/backend-core/src/errors/http.ts
new file mode 100644
index 0000000000..182e009f58
--- /dev/null
+++ b/packages/backend-core/src/errors/http.ts
@@ -0,0 +1,15 @@
+import { GenericError } from "./generic"
+
+export class HTTPError extends GenericError {
+ status: number
+
+ constructor(
+ message: string,
+ httpStatus: number,
+ code = "http",
+ type = "generic"
+ ) {
+ super(message, code, type)
+ this.status = httpStatus
+ }
+}
diff --git a/packages/backend-core/src/errors/index.js b/packages/backend-core/src/errors/index.ts
similarity index 65%
rename from packages/backend-core/src/errors/index.js
rename to packages/backend-core/src/errors/index.ts
index 31ffd739a0..be6657093d 100644
--- a/packages/backend-core/src/errors/index.js
+++ b/packages/backend-core/src/errors/index.ts
@@ -1,5 +1,6 @@
-const http = require("./http")
-const licensing = require("./licensing")
+import { HTTPError } from "./http"
+import { UsageLimitError, FeatureDisabledError } from "./licensing"
+import * as licensing from "./licensing"
const codes = {
...licensing.codes,
@@ -11,7 +12,7 @@ const context = {
...licensing.context,
}
-const getPublicError = err => {
+const getPublicError = (err: any) => {
let error
if (err.code || err.type) {
// add generic error information
@@ -32,13 +33,15 @@ const getPublicError = err => {
return error
}
-module.exports = {
+const pkg = {
codes,
types,
errors: {
- UsageLimitError: licensing.UsageLimitError,
- FeatureDisabledError: licensing.FeatureDisabledError,
- HTTPError: http.HTTPError,
+ UsageLimitError,
+ FeatureDisabledError,
+ HTTPError,
},
getPublicError,
}
+
+export = pkg
diff --git a/packages/backend-core/src/errors/licensing.js b/packages/backend-core/src/errors/licensing.js
deleted file mode 100644
index 85d207ac35..0000000000
--- a/packages/backend-core/src/errors/licensing.js
+++ /dev/null
@@ -1,43 +0,0 @@
-const { HTTPError } = require("./http")
-
-const type = "license_error"
-
-const codes = {
- USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded",
- FEATURE_DISABLED: "feature_disabled",
-}
-
-const context = {
- [codes.USAGE_LIMIT_EXCEEDED]: err => {
- return {
- limitName: err.limitName,
- }
- },
- [codes.FEATURE_DISABLED]: err => {
- return {
- featureName: err.featureName,
- }
- },
-}
-
-class UsageLimitError extends HTTPError {
- constructor(message, limitName) {
- super(message, 400, codes.USAGE_LIMIT_EXCEEDED, type)
- this.limitName = limitName
- }
-}
-
-class FeatureDisabledError extends HTTPError {
- constructor(message, featureName) {
- super(message, 400, codes.FEATURE_DISABLED, type)
- this.featureName = featureName
- }
-}
-
-module.exports = {
- type,
- codes,
- context,
- UsageLimitError,
- FeatureDisabledError,
-}
diff --git a/packages/backend-core/src/errors/licensing.ts b/packages/backend-core/src/errors/licensing.ts
new file mode 100644
index 0000000000..7ffcefa167
--- /dev/null
+++ b/packages/backend-core/src/errors/licensing.ts
@@ -0,0 +1,39 @@
+import { HTTPError } from "./http"
+
+export const type = "license_error"
+
+export const codes = {
+ USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded",
+ FEATURE_DISABLED: "feature_disabled",
+}
+
+export const context = {
+ [codes.USAGE_LIMIT_EXCEEDED]: (err: any) => {
+ return {
+ limitName: err.limitName,
+ }
+ },
+ [codes.FEATURE_DISABLED]: (err: any) => {
+ return {
+ featureName: err.featureName,
+ }
+ },
+}
+
+export class UsageLimitError extends HTTPError {
+ limitName: string
+
+ constructor(message: string, limitName: string) {
+ super(message, 400, codes.USAGE_LIMIT_EXCEEDED, type)
+ this.limitName = limitName
+ }
+}
+
+export class FeatureDisabledError extends HTTPError {
+ featureName: string
+
+ constructor(message: string, featureName: string) {
+ super(message, 400, codes.FEATURE_DISABLED, type)
+ this.featureName = featureName
+ }
+}
diff --git a/packages/builder/src/pages/builder/portal/settings/upgrade.svelte b/packages/builder/src/pages/builder/portal/settings/upgrade.svelte
index 5200834ffa..68ace1a157 100644
--- a/packages/builder/src/pages/builder/portal/settings/upgrade.svelte
+++ b/packages/builder/src/pages/builder/portal/settings/upgrade.svelte
@@ -35,10 +35,14 @@
}
const activate = async () => {
- await API.activateLicenseKey({ licenseKey })
- await auth.getSelf()
- await setLicenseInfo()
- notifications.success("Successfully activated")
+ try {
+ await API.activateLicenseKey({ licenseKey })
+ await auth.getSelf()
+ await setLicenseInfo()
+ notifications.success("Successfully activated")
+ } catch (e) {
+ notifications.error(e.message)
+ }
}
const refresh = async () => {
diff --git a/packages/types/src/documents/account/account.ts b/packages/types/src/documents/account/account.ts
index e6f48ff001..33c96033a0 100644
--- a/packages/types/src/documents/account/account.ts
+++ b/packages/types/src/documents/account/account.ts
@@ -33,6 +33,7 @@ export interface Account extends CreateAccount {
tier: string // deprecated
stripeCustomerId?: string
licenseKey?: string
+ licenseKeyActivatedAt?: number
}
export interface PasswordAccount extends Account {
From 490735bff6054ff980c368d5246369ef2b8cce70 Mon Sep 17 00:00:00 2001
From: Rory Powell
Date: Mon, 12 Sep 2022 13:38:21 +0100
Subject: [PATCH 16/27] Update string-templates/yarn.lock
---
packages/string-templates/yarn.lock | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/packages/string-templates/yarn.lock b/packages/string-templates/yarn.lock
index dc7e8f1852..8e71e59912 100644
--- a/packages/string-templates/yarn.lock
+++ b/packages/string-templates/yarn.lock
@@ -568,15 +568,6 @@
magic-string "^0.25.7"
resolve "^1.17.0"
-"@rollup/plugin-inject@^4.0.0":
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/@rollup/plugin-inject/-/plugin-inject-4.0.4.tgz#fbeee66e9a700782c4f65c8b0edbafe58678fbc2"
- integrity sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==
- dependencies:
- "@rollup/pluginutils" "^3.1.0"
- estree-walker "^2.0.1"
- magic-string "^0.25.7"
-
"@rollup/plugin-json@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
@@ -3796,13 +3787,6 @@ rollup-plugin-node-resolve@^5.2.0:
resolve "^1.11.1"
rollup-pluginutils "^2.8.1"
-rollup-plugin-polyfill-node@^0.10.2:
- version "0.10.2"
- resolved "https://registry.yarnpkg.com/rollup-plugin-polyfill-node/-/rollup-plugin-polyfill-node-0.10.2.tgz#b2128646851fcb9475ddfd5bc22ca1a8c568738d"
- integrity sha512-5GMywXiLiuQP6ZzED/LO/Q0HyDi2W6b8VN+Zd3oB0opIjyRs494Me2ZMaqKWDNbGiW4jvvzl6L2n4zRgxS9cSQ==
- dependencies:
- "@rollup/plugin-inject" "^4.0.0"
-
rollup-plugin-terser@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
From 98333b87912a93da212da930b1b0abc01a4f2be2 Mon Sep 17 00:00:00 2001
From: Dean
Date: Tue, 13 Sep 2022 11:52:31 +0100
Subject: [PATCH 17/27] Merge commit
---
packages/bbui/src/Banner/BannerDisplay.svelte | 22 ++-
packages/bbui/src/Stores/banner.js | 18 ++-
.../bbui/src/Tooltip/TooltipWrapper.svelte | 3 +-
packages/bbui/src/index.js | 1 +
packages/builder/src/App.svelte | 4 +
packages/builder/src/builderStore/index.js | 2 +
.../src/builderStore/store/temporal.js | 44 ++++++
.../components/common/TemplateDisplay.svelte | 21 +--
.../licensing/AccountDowngradedModal.svelte | 51 +++++++
.../portal/licensing/AppLimitModal.svelte | 46 ++++++
.../licensing/DayPassWarningModal.svelte | 54 +++++++
.../portal/licensing/LicensingOverlays.svelte | 117 ++++++++++++++++
.../licensing/PaymentFailedModal.svelte | 87 ++++++++++++
.../components/portal/licensing/banners.js | 132 ++++++++++++++++++
.../components/portal/licensing/constants.js | 15 ++
.../builder/src/pages/builder/_layout.svelte | 5 +-
.../pages/builder/portal/apps/create.svelte | 27 +++-
.../pages/builder/portal/apps/index.svelte | 18 ++-
.../builder/src/stores/portal/licensing.js | 121 +++++++++-------
19 files changed, 707 insertions(+), 81 deletions(-)
create mode 100644 packages/builder/src/builderStore/store/temporal.js
create mode 100644 packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte
create mode 100644 packages/builder/src/components/portal/licensing/AppLimitModal.svelte
create mode 100644 packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte
create mode 100644 packages/builder/src/components/portal/licensing/LicensingOverlays.svelte
create mode 100644 packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte
create mode 100644 packages/builder/src/components/portal/licensing/banners.js
create mode 100644 packages/builder/src/components/portal/licensing/constants.js
diff --git a/packages/bbui/src/Banner/BannerDisplay.svelte b/packages/bbui/src/Banner/BannerDisplay.svelte
index aad742b1bd..4785fcb9ba 100644
--- a/packages/bbui/src/Banner/BannerDisplay.svelte
+++ b/packages/bbui/src/Banner/BannerDisplay.svelte
@@ -4,22 +4,30 @@
import { banner } from "../Stores/banner"
import Banner from "./Banner.svelte"
import { fly } from "svelte/transition"
+ import TooltipWrapper from "../Tooltip/TooltipWrapper.svelte"
- {#if $banner.message}
+ {#each $banner.messages as message}
{
+ message.onChange()
+ }}
+ showCloseButton={typeof message.showCloseButton === "boolean"
+ ? message.showCloseButton
+ : true}
>
- {$banner.message}
+
+ {message.message}
+
- {/if}
+ {/each}
diff --git a/packages/bbui/src/Stores/banner.js b/packages/bbui/src/Stores/banner.js
index 81a9ee2204..745c77e188 100644
--- a/packages/bbui/src/Stores/banner.js
+++ b/packages/bbui/src/Stores/banner.js
@@ -1,7 +1,9 @@
import { writable } from "svelte/store"
export function createBannerStore() {
- const DEFAULT_CONFIG = {}
+ const DEFAULT_CONFIG = {
+ messages: [],
+ }
const banner = writable(DEFAULT_CONFIG)
@@ -28,9 +30,23 @@ export function createBannerStore() {
await show(config)
}
+ const queue = async entries => {
+ banner.update(store => {
+ const sorted = [...store.messages, ...entries].sort(
+ (a, b) => a.priority > b.priority
+ )
+ return {
+ ...store,
+ messages: sorted,
+ }
+ })
+ }
+
return {
subscribe: banner.subscribe,
showStatus,
+ show,
+ queue,
}
}
diff --git a/packages/bbui/src/Tooltip/TooltipWrapper.svelte b/packages/bbui/src/Tooltip/TooltipWrapper.svelte
index 92f5c6f474..0c6c8e167b 100644
--- a/packages/bbui/src/Tooltip/TooltipWrapper.svelte
+++ b/packages/bbui/src/Tooltip/TooltipWrapper.svelte
@@ -54,7 +54,6 @@
transform: scale(0.75);
}
.icon-small {
- margin-top: -2px;
- margin-bottom: -5px;
+ margin-bottom: -2px;
}
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js
index b45f3e9ed6..11424b1261 100644
--- a/packages/bbui/src/index.js
+++ b/packages/bbui/src/index.js
@@ -34,6 +34,7 @@ export { default as Layout } from "./Layout/Layout.svelte"
export { default as Page } from "./Layout/Page.svelte"
export { default as Link } from "./Link/Link.svelte"
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
+export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
export { default as Menu } from "./Menu/Menu.svelte"
export { default as MenuSection } from "./Menu/Section.svelte"
export { default as MenuSeparator } from "./Menu/Separator.svelte"
diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte
index 0fb0fe59d5..4d193df104 100644
--- a/packages/builder/src/App.svelte
+++ b/packages/builder/src/App.svelte
@@ -4,6 +4,7 @@
import { NotificationDisplay, BannerDisplay } from "@budibase/bbui"
import { parse, stringify } from "qs"
import HelpIcon from "components/common/HelpIcon.svelte"
+ import LicensingOverlays from "components/portal/licensing/LicensingOverlays.svelte"
const queryHandler = { parse, stringify }
@@ -12,6 +13,9 @@
+
+
+
diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js
index 35c4587874..69bca7eac3 100644
--- a/packages/builder/src/builderStore/index.js
+++ b/packages/builder/src/builderStore/index.js
@@ -1,5 +1,6 @@
import { getFrontendStore } from "./store/frontend"
import { getAutomationStore } from "./store/automation"
+import { getTemporalStore } from "./store/temporal"
import { getThemeStore } from "./store/theme"
import { derived } from "svelte/store"
import { findComponent, findComponentPath } from "./componentUtils"
@@ -8,6 +9,7 @@ import { RoleUtils } from "@budibase/frontend-core"
export const store = getFrontendStore()
export const automationStore = getAutomationStore()
export const themeStore = getThemeStore()
+export const temporalStore = getTemporalStore()
export const selectedScreen = derived(store, $store => {
return $store.screens.find(screen => screen._id === $store.selectedScreenId)
diff --git a/packages/builder/src/builderStore/store/temporal.js b/packages/builder/src/builderStore/store/temporal.js
new file mode 100644
index 0000000000..b8a75e1905
--- /dev/null
+++ b/packages/builder/src/builderStore/store/temporal.js
@@ -0,0 +1,44 @@
+import { createLocalStorageStore } from "@budibase/frontend-core"
+import { get } from "svelte/store"
+
+export const getTemporalStore = () => {
+ const initialValue = {}
+
+ //const appId = window["##BUDIBASE_APP_ID##"] || "app"
+ const localStorageKey = `${123}.bb-temporal`
+ const store = createLocalStorageStore(localStorageKey, initialValue)
+
+ const setExpiring = (key, data, duration) => {
+ const updated = {
+ ...data,
+ expiry: Date.now() + duration * 1000,
+ }
+
+ store.update(state => ({
+ ...state,
+ [key]: updated,
+ }))
+ }
+
+ const getExpiring = key => {
+ const entry = get(store)[key]
+ if (!entry) {
+ return
+ }
+ const currentExpiry = entry.expiry
+ if (currentExpiry < Date.now()) {
+ store.update(state => {
+ delete state[key]
+ return state
+ })
+ return null
+ } else {
+ return entry
+ }
+ }
+
+ return {
+ subscribe: store.subscribe,
+ actions: { setExpiring, getExpiring },
+ }
+}
diff --git a/packages/builder/src/components/common/TemplateDisplay.svelte b/packages/builder/src/components/common/TemplateDisplay.svelte
index c96bff8d5e..6041a904fd 100644
--- a/packages/builder/src/components/common/TemplateDisplay.svelte
+++ b/packages/builder/src/components/common/TemplateDisplay.svelte
@@ -10,6 +10,7 @@
} from "@budibase/bbui"
import TemplateCard from "components/common/TemplateCard.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte"
+ import { licensing } from "stores/portal"
export let templates
@@ -96,15 +97,17 @@
backgroundColour={templateEntry.background}
icon={templateEntry.icon}
>
- {
- template = templateEntry
- creationModal.show()
- }}
- >
- Use template
-
+ {#if $licensing?.usageMetrics?.apps < 100}
+ {
+ template = templateEntry
+ creationModal.show()
+ }}
+ >
+ Use template
+
+ {/if}
+ import { Modal, ModalContent, Body } from "@budibase/bbui"
+ import { auth, admin } from "stores/portal"
+
+ export let onDismiss = () => {}
+ export let onShow = () => {}
+
+ let accountDowngradeModal
+
+ const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
+
+ export function show() {
+ accountDowngradeModal.show()
+ }
+
+ export function hide() {
+ accountDowngradeModal.hide()
+ }
+
+
+
+ {
+ window.location.href = upgradeUrl
+ }
+ : null}
+ >
+
+ The payment for your Business Subscription failed and we have downgraded
+ your account to the Free plan .
+
+
+ Update to Business to get all your apps and user sessions back up and
+ running.
+
+ {#if !$auth.user.accountPortalAccess}
+ Please contact the account holder.
+ {/if}
+
+
+
+
diff --git a/packages/builder/src/components/portal/licensing/AppLimitModal.svelte b/packages/builder/src/components/portal/licensing/AppLimitModal.svelte
new file mode 100644
index 0000000000..45259edd76
--- /dev/null
+++ b/packages/builder/src/components/portal/licensing/AppLimitModal.svelte
@@ -0,0 +1,46 @@
+
+
+
+ {
+ window.location.href = upgradeUrl
+ }
+ : null}
+ >
+
+ You are currently on our Free plan . Upgrade
+ to our Pro plan to get unlimited apps.
+
+ {#if !$auth.user.accountPortalAccess}
+ Please contact the account holder.
+ {/if}
+
+
+
+
diff --git a/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte b/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte
new file mode 100644
index 0000000000..f164df1105
--- /dev/null
+++ b/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte
@@ -0,0 +1,54 @@
+
+
+
+ {#if $auth.user.accountPortalAccess}
+ {
+ window.location.href = upgradeUrl
+ }}
+ >
+
+ You have used {sessionsUsed}% of
+ your plans Day Passes with {daysRemaining} day{daysRemaining == 1
+ ? ""
+ : "s"} remaining.
+
+ Upgrade your account to prevent your apps from going offline.
+
+ {:else}
+
+
+ You have used {sessionsUsed}% of
+ your plans Day Passes with {daysRemaining} day{daysRemaining == 1
+ ? ""
+ : "s"} remaining.
+
+ Please contact your account holder.
+
+ {/if}
+
diff --git a/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte
new file mode 100644
index 0000000000..2a744179c0
--- /dev/null
+++ b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte
@@ -0,0 +1,117 @@
+
+
+
+
+
diff --git a/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte
new file mode 100644
index 0000000000..f952684998
--- /dev/null
+++ b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte
@@ -0,0 +1,87 @@
+
+
+
+ {#if $auth.user.accountPortalAccess}
+ {
+ window.location.href = upgradeUrl
+ }}
+ >
+ The payment for your business plan subscription has failed
+
+ Please upgrade your billing details before your account gets downgraded
+ to the free plan
+
+
+
+ {`${$licensing.paymentDueDaysRemaining} day${
+ $licensing.paymentDueDaysRemaining == 1 ? "" : "s"
+ } remaining`}
+
+
+
+
+
+
+ {:else}
+
+ The payment for your business plan subscription has failed
+
+ Please upgrade your billing details before your account gets downgraded
+ to the free plan
+
+ Please contact your account holder.
+
+
+ {`${$licensing.paymentDueDaysRemaining} day${
+ $licensing.paymentDueDaysRemaining == 1 ? "" : "s"
+ } remaining`}
+
+
+
+
+
+
+ {/if}
+
+
+
diff --git a/packages/builder/src/components/portal/licensing/banners.js b/packages/builder/src/components/portal/licensing/banners.js
new file mode 100644
index 0000000000..3f8a2fbb0f
--- /dev/null
+++ b/packages/builder/src/components/portal/licensing/banners.js
@@ -0,0 +1,132 @@
+import { ExpiringKeys } from "./constants"
+import { temporalStore } from "builderStore"
+import { admin, auth, licensing } from "stores/portal"
+import { get } from "svelte/store"
+
+const oneDayInSeconds = 86400
+const upgradeUrl = `${get(admin).accountPortalUrl}/portal/upgrade`
+
+const defaultCacheFn = key => {
+ temporalStore.actions.setExpiring(key, {}, oneDayInSeconds)
+}
+
+const defaultAction = key => {
+ if (!get(auth).user.accountPortalAccess) {
+ return {}
+ }
+ return {
+ extraButtonText: "Upgrade Plan",
+ extraButtonAction: () => {
+ defaultCacheFn(key)
+ window.location.href = upgradeUrl
+ },
+ }
+}
+
+const buildUsageInfoBanner = (metricKey, metricLabel, cacheKey, percentage) => {
+ const appAuth = get(auth)
+ const appLicensing = get(licensing)
+
+ let bannerConfig = {
+ key: cacheKey,
+ type: "info",
+ onChange: () => {
+ defaultCacheFn(cacheKey)
+ },
+ message: `You have used ${
+ appLicensing?.usageMetrics[metricKey]
+ }% of your monthly usage of ${metricLabel} with ${
+ appLicensing.quotaResetDaysRemaining
+ } day${
+ appLicensing.quotaResetDaysRemaining == 1 ? "" : "s"
+ } remaining. All apps will be taken offline if this limit is reached. ${
+ appAuth.user.accountPortalAccess
+ ? ""
+ : "Please contact your account holder."
+ }`,
+ criteria: () => {
+ return appLicensing?.usageMetrics[metricKey] >= percentage
+ },
+ priority: 0, //Banners.Priority 0, 1, 2 ??
+ }
+
+ return !get(auth).user.accountPortalAccess
+ ? bannerConfig
+ : {
+ ...bannerConfig,
+ ...defaultAction(cacheKey),
+ }
+}
+
+const buildDayPassBanner = () => {
+ const appAuth = get(auth)
+ if (get(licensing)?.usageMetrics["dayPasses"] >= 100) {
+ return {
+ key: "max_dayPasses",
+ type: "negative",
+ criteria: () => {
+ return true
+ },
+ message: `Your apps are currently offline. You have exceeded your plans limit for Day Passes. ${
+ appAuth.user.accountPortalAccess
+ ? ""
+ : "Please contact your account holder."
+ }`,
+ ...defaultAction(),
+ showCloseButton: false,
+ }
+ }
+
+ return buildUsageInfoBanner(
+ "dayPasses",
+ "Day Passes",
+ ExpiringKeys.LICENSING_DAYPASS_WARNING_BANNER,
+ 90
+ )
+}
+
+const buildPaymentFailedBanner = () => {
+ return {
+ key: "payment_Failed",
+ type: "negative",
+ criteria: () => {
+ return get(licensing)?.accountPastDue
+ },
+ message: `Payment Failed - Please update your billing details or your account will be downgrades in
+ ${get(licensing)?.paymentDueDaysRemaining} day${
+ get(licensing)?.paymentDueDaysRemaining == 1 ? "" : "s"
+ }`,
+ ...defaultAction(),
+ showCloseButton: false,
+ }
+}
+
+export const getBanners = () => {
+ return [
+ buildPaymentFailedBanner(),
+ buildDayPassBanner(ExpiringKeys.LICENSING_DAYPASS_WARNING_BANNER),
+ buildUsageInfoBanner(
+ "rows",
+ "Rows",
+ ExpiringKeys.LICENSING_ROWS_WARNING_BANNER,
+ 90
+ ),
+ buildUsageInfoBanner(
+ "automations",
+ "Automations",
+ ExpiringKeys.LICENSING_AUTOMATIONS_WARNING_BANNER,
+ 90
+ ),
+ buildUsageInfoBanner(
+ "queries",
+ "Queries",
+ ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER,
+ 90 // could be an array [50,75,90]
+ ),
+ ].filter(licensingBanner => {
+ return (
+ !temporalStore.actions.getExpiring(licensingBanner.key) &&
+ licensingBanner.criteria()
+ )
+ })
+}
diff --git a/packages/builder/src/components/portal/licensing/constants.js b/packages/builder/src/components/portal/licensing/constants.js
new file mode 100644
index 0000000000..6474b7c78f
--- /dev/null
+++ b/packages/builder/src/components/portal/licensing/constants.js
@@ -0,0 +1,15 @@
+export const ExpiringKeys = {
+ LICENSING_DAYPASS_WARNING_MODAL: "licensing_daypass_warning_90_modal",
+ LICENSING_DAYPASS_WARNING_BANNER: "licensing_daypass_warning_90_banner",
+ LICENSING_PAYMENT_FAILED: "licensing_payment_failed",
+ LICENSING_ACCOUNT_DOWNGRADED_MODAL: "licensing_account_downgraded_modal",
+ LICENSING_APP_LIMIT_MODAL: "licensing_app_limit_modal",
+ LICENSING_ROWS_WARNING_BANNER: "licensing_rows_warning_banner",
+ LICENSING_AUTOMATIONS_WARNING_BANNER: "licensing_automations_warning_banner",
+ LICENSING_QUERIES_WARNING_BANNER: "licensing_automations_warning_banner",
+}
+
+export const StripeStatus = {
+ PAST_DUE: "past_due",
+ ACTIVE: "active",
+}
diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte
index 2e8ea2ef0a..ee8b1bb8df 100644
--- a/packages/builder/src/pages/builder/_layout.svelte
+++ b/packages/builder/src/pages/builder/_layout.svelte
@@ -1,6 +1,6 @@
@@ -121,6 +133,7 @@
>
+
diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte
index 21259c4d84..a5642cf639 100644
--- a/packages/builder/src/pages/builder/portal/_layout.svelte
+++ b/packages/builder/src/pages/builder/portal/_layout.svelte
@@ -42,6 +42,7 @@
{
title: "Usage",
href: "/builder/portal/settings/usage",
+ badge: "New",
},
])
}
@@ -53,6 +54,13 @@
href: "/builder/portal/manage/users",
heading: "Manage",
},
+ isEnabled(FEATURE_FLAGS.USER_GROUPS)
+ ? {
+ title: "User Groups",
+ href: "/builder/portal/manage/groups",
+ badge: "New",
+ }
+ : undefined,
{ title: "Auth", href: "/builder/portal/manage/auth" },
{ title: "Email", href: "/builder/portal/manage/email" },
{
@@ -66,15 +74,6 @@
},
])
- if (isEnabled(FEATURE_FLAGS.USER_GROUPS)) {
- let item = {
- title: "User Groups",
- href: "/builder/portal/manage/groups",
- }
-
- menu.splice(2, 0, item)
- }
-
if (!$adminStore.cloud) {
menu = menu.concat([
{
@@ -87,6 +86,7 @@
menu = menu.concat({
title: "Upgrade",
href: "/builder/portal/settings/upgrade",
+ badge: "New",
})
}
}
@@ -106,9 +106,21 @@
{
title: "Account",
href: $adminStore.accountPortalUrl,
+ heading: "Account",
},
])
+
+ if (isEnabled(FEATURE_FLAGS.LICENSING)) {
+ menu = menu.concat([
+ {
+ title: "Upgrade",
+ href: $adminStore.accountPortalUrl + "/portal/upgrade",
+ badge: "New",
+ },
+ ])
+ }
}
+ menu = menu.filter(item => !!item)
return menu
}
@@ -159,11 +171,12 @@
{#if showTooltip}
- {`${$licensing.paymentDueDaysRemaining} day${
- $licensing.paymentDueDaysRemaining == 1 ? "" : "s"
+ {`${$licensing.pastDueDaysRemaining} day${
+ $licensing.pastDueDaysRemaining == 1 ? "" : "s"
} remaining`}
-
+
Please contact your account holder.
- {`${$licensing.paymentDueDaysRemaining} day${
- $licensing.paymentDueDaysRemaining == 1 ? "" : "s"
+ {`${$licensing.pastDueDaysRemaining} day${
+ $licensing.pastDueDaysRemaining == 1 ? "" : "s"
} remaining`}
-
+