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" + + +
Budibase

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 @@
- +
+ {#if showWarning} + + {/if} +
+ {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 @@ + + +
+
+
+ +
+ {description} +
+ {title} +
+ {subtitle} +
+
+ {#each textRows as row} + {row} + {/each} +
+
+
+
+ {#if secondaryDefined} +
+ +
+ {/if} + {#if primaryDefined} +
+ +
+ {/if} +
+
+
+ +
+
+ + 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" 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 @@ + + + {#if showTooltip}
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index 11424b1261..ead226bdc3 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -95,7 +95,7 @@ export { default as clickOutside } from "./Actions/click_outside" // Stores export { notifications, createNotificationStore } from "./Stores/notifications" -export { banner } from "./Stores/banner" +export { banner, BANNER_TYPES } from "./Stores/banner" // Helpers export * as Helpers from "./helpers" diff --git a/packages/builder/src/builderStore/store/temporal.js b/packages/builder/src/builderStore/store/temporal.js index b8a75e1905..ca70fd6293 100644 --- a/packages/builder/src/builderStore/store/temporal.js +++ b/packages/builder/src/builderStore/store/temporal.js @@ -4,8 +4,7 @@ import { get } from "svelte/store" export const getTemporalStore = () => { const initialValue = {} - //const appId = window["##BUDIBASE_APP_ID##"] || "app" - const localStorageKey = `${123}.bb-temporal` + const localStorageKey = `bb-temporal` const store = createLocalStorageStore(localStorageKey, initialValue) const setExpiring = (key, data, duration) => { diff --git a/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte b/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte index 81fe340354..53683faa87 100644 --- a/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte +++ b/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte @@ -7,7 +7,8 @@ let accountDowngradeModal - const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + $: accountUrl = $admin.accountPortalUrl + $: upgradeUrl = `${accountUrl}/portal/upgrade` export function show() { accountDowngradeModal.show() @@ -31,15 +32,12 @@ : 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. + The payment for your subscription has failed and we have downgraded your + account to the Free plan. + Upgrade to restore full functionality. {#if !$auth.user.accountPortalAccess} - Please contact the account holder. + Please contact the account holder to upgrade. {/if} diff --git a/packages/builder/src/components/portal/licensing/AppLimitModal.svelte b/packages/builder/src/components/portal/licensing/AppLimitModal.svelte index 45259edd76..39f553517e 100644 --- a/packages/builder/src/components/portal/licensing/AppLimitModal.svelte +++ b/packages/builder/src/components/portal/licensing/AppLimitModal.svelte @@ -6,7 +6,8 @@ let appLimitModal - const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + $: accountUrl = $admin.accountPortalUrl + $: upgradeUrl = `${accountUrl}/portal/upgrade` export function show() { appLimitModal.show() @@ -19,7 +20,7 @@ You are currently on our Free plan. Upgrade - to our Pro plan to get unlimited apps. + to our Pro plan to get unlimited apps and additional features. {#if !$auth.user.accountPortalAccess} - Please contact the account holder. + Please contact the account holder to upgrade. {/if} diff --git a/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte b/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte index f164df1105..b1aade2ca5 100644 --- a/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte +++ b/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte @@ -1,31 +1,40 @@ - + {#if $auth.user.accountPortalAccess} { @@ -33,22 +42,37 @@ }} > - You have used {sessionsUsed}% of + You have used {dayPassesUsed}% of your plans Day Passes with {daysRemaining} day{daysRemaining == 1 ? "" : "s"} remaining. + + + - Upgrade your account to prevent your apps from going offline. + {dayPassesBody} {:else} - + - You have used {sessionsUsed}% of + You have used {dayPassesUsed}% of your plans Day Passes with {daysRemaining} day{daysRemaining == 1 ? "" : "s"} remaining. + + + - Please contact your account holder. + Please contact your account holder to upgrade. {/if} + + diff --git a/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte index 2a744179c0..8431b860e8 100644 --- a/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte +++ b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte @@ -6,7 +6,7 @@ import PaymentFailedModal from "./PaymentFailedModal.svelte" import AccountDowngradedModal from "./AccountDowngradedModal.svelte" import { ExpiringKeys } from "./constants" - import { getBanners } from "./banners" + import { getBanners } from "./licensingBanners" import { banner } from "@budibase/bbui" const oneDayInSeconds = 86400 @@ -42,7 +42,7 @@ { key: ExpiringKeys.LICENSING_PAYMENT_FAILED, criteria: () => { - return $licensing.accountPastDue + return $licensing.accountPastDue && !$licensing.isFreePlan() }, action: () => { paymentFailedModal.show() @@ -69,14 +69,7 @@ }) } - $: if (userLoaded && licensingLoaded && loaded) { - queuedModals = processModals() - queuedBanners = getBanners() - showNext() - banner.queue(queuedBanners) - } - - const showNext = () => { + const showNextModal = () => { if (currentModalCfg) { currentModalCfg.cache() } @@ -88,6 +81,13 @@ } } + $: if (userLoaded && licensingLoaded && loaded) { + queuedModals = processModals() + queuedBanners = getBanners() + showNextModal() + banner.queue(queuedBanners) + } + onMount(async () => { auth.subscribe(state => { if (state.user && !userLoaded) { @@ -100,18 +100,13 @@ licensingLoaded = true } }) - - temporalStore.subscribe(state => { - console.log("Stored temporal ", state) - }) - loaded = true }) - - + + diff --git a/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte index f952684998..591742a3d0 100644 --- a/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte +++ b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte @@ -7,10 +7,11 @@ export let onShow = () => {} let paymentFailedModal - let pastDueAt + let pastDueEndDate const paymentFailedTitle = "Payment failed" - const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + $: accountUrl = $admin.accountPortalUrl + $: upgradeUrl = `${accountUrl}/portal/upgrade` export function show() { paymentFailedModal.show() @@ -21,12 +22,8 @@ } onMount(() => { - auth.subscribe(state => { - if (state.user && state.user.license?.billing?.subscription) { - pastDueAt = new Date( - state.user.license?.billing?.subscription.pastDueAt * 1000 - ) - } + licensing.subscribe(state => { + pastDueEndDate = state.pastDueEndDate }) }) @@ -48,11 +45,11 @@
- {`${$licensing.paymentDueDaysRemaining} day${ - $licensing.paymentDueDaysRemaining == 1 ? "" : "s" + {`${$licensing.pastDueDaysRemaining} day${ + $licensing.pastDueDaysRemaining == 1 ? "" : "s" } remaining`} - +
@@ -67,11 +64,11 @@ Please contact your account holder.
- {`${$licensing.paymentDueDaysRemaining} day${ - $licensing.paymentDueDaysRemaining == 1 ? "" : "s" + {`${$licensing.pastDueDaysRemaining} day${ + $licensing.pastDueDaysRemaining == 1 ? "" : "s" } remaining`} - +
diff --git a/packages/builder/src/components/portal/licensing/constants.js b/packages/builder/src/components/portal/licensing/constants.js index 6474b7c78f..57f3a36709 100644 --- a/packages/builder/src/components/portal/licensing/constants.js +++ b/packages/builder/src/components/portal/licensing/constants.js @@ -6,7 +6,7 @@ export const ExpiringKeys = { 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", + LICENSING_QUERIES_WARNING_BANNER: "licensing_queries_warning_banner", } export const StripeStatus = { diff --git a/packages/builder/src/components/portal/licensing/banners.js b/packages/builder/src/components/portal/licensing/licensingBanners.js similarity index 62% rename from packages/builder/src/components/portal/licensing/banners.js rename to packages/builder/src/components/portal/licensing/licensingBanners.js index 3f8a2fbb0f..37d8e4a540 100644 --- a/packages/builder/src/components/portal/licensing/banners.js +++ b/packages/builder/src/components/portal/licensing/licensingBanners.js @@ -2,9 +2,9 @@ import { ExpiringKeys } from "./constants" import { temporalStore } from "builderStore" import { admin, auth, licensing } from "stores/portal" import { get } from "svelte/store" +import { BANNER_TYPES } from "@budibase/bbui" const oneDayInSeconds = 86400 -const upgradeUrl = `${get(admin).accountPortalUrl}/portal/upgrade` const defaultCacheFn = key => { temporalStore.actions.setExpiring(key, {}, oneDayInSeconds) @@ -18,36 +18,47 @@ const defaultAction = key => { extraButtonText: "Upgrade Plan", extraButtonAction: () => { defaultCacheFn(key) - window.location.href = upgradeUrl + window.location.href = `${get(admin).accountPortalUrl}/portal/upgrade` }, } } -const buildUsageInfoBanner = (metricKey, metricLabel, cacheKey, percentage) => { +const buildUsageInfoBanner = ( + metricKey, + metricLabel, + cacheKey, + percentageThreshold, + customMessage +) => { const appAuth = get(auth) const appLicensing = get(licensing) + const displayPercent = + appLicensing?.usageMetrics[metricKey] > 100 + ? 100 + : appLicensing?.usageMetrics[metricKey] + let bannerConfig = { key: cacheKey, - type: "info", + type: BANNER_TYPES.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." - }`, + message: customMessage + ? customMessage + : `You have used ${displayPercent}% of your monthly usage of ${metricLabel} with ${ + appLicensing.quotaResetDaysRemaining + } day${ + appLicensing.quotaResetDaysRemaining == 1 ? "" : "s" + } remaining. ${ + appAuth.user.accountPortalAccess + ? "" + : "Please contact your account holder to upgrade" + }`, criteria: () => { - return appLicensing?.usageMetrics[metricKey] >= percentage + return appLicensing?.usageMetrics[metricKey] >= percentageThreshold }, - priority: 0, //Banners.Priority 0, 1, 2 ?? + tooltip: appLicensing?.quotaResetDate, } return !get(auth).user.accountPortalAccess @@ -60,17 +71,18 @@ const buildUsageInfoBanner = (metricKey, metricLabel, cacheKey, percentage) => { const buildDayPassBanner = () => { const appAuth = get(auth) + const appLicensing = get(licensing) if (get(licensing)?.usageMetrics["dayPasses"] >= 100) { return { key: "max_dayPasses", - type: "negative", + type: BANNER_TYPES.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." + : "Please contact your account holder to upgrade." }`, ...defaultAction(), showCloseButton: false, @@ -81,23 +93,35 @@ const buildDayPassBanner = () => { "dayPasses", "Day Passes", ExpiringKeys.LICENSING_DAYPASS_WARNING_BANNER, - 90 + 90, + `You have used ${ + appLicensing?.usageMetrics["dayPasses"] + }% of your monthly usage of Day Passes with ${ + appLicensing?.quotaResetDaysRemaining + } day${ + get(licensing).quotaResetDaysRemaining == 1 ? "" : "s" + } remaining. All apps will be taken offline if this limit is reached. ${ + appAuth.user.accountPortalAccess + ? "" + : "Please contact your account holder to upgrade." + }` ) } const buildPaymentFailedBanner = () => { return { key: "payment_Failed", - type: "negative", + type: BANNER_TYPES.NEGATIVE, criteria: () => { - return get(licensing)?.accountPastDue + return get(licensing)?.accountPastDue && !get(licensing).isFreePlan() }, message: `Payment Failed - Please update your billing details or your account will be downgrades in - ${get(licensing)?.paymentDueDaysRemaining} day${ - get(licensing)?.paymentDueDaysRemaining == 1 ? "" : "s" + ${get(licensing)?.pastDueDaysRemaining} day${ + get(licensing)?.pastDueDaysRemaining == 1 ? "" : "s" }`, ...defaultAction(), showCloseButton: false, + tooltip: get(licensing).pastDueEndDate, } } @@ -121,7 +145,7 @@ export const getBanners = () => { "queries", "Queries", ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER, - 90 // could be an array [50,75,90] + 90 ), ].filter(licensingBanner => { return ( diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte index 1f5a761a42..3b606480cc 100644 --- a/packages/builder/src/pages/builder/apps/index.svelte +++ b/packages/builder/src/pages/builder/apps/index.svelte @@ -13,13 +13,14 @@ notifications, } from "@budibase/bbui" import { onMount } from "svelte" - import { apps, organisation, auth, groups } from "stores/portal" + import { apps, organisation, auth, groups, licensing } from "stores/portal" import { goto } from "@roxi/routify" import { AppStatus } from "constants" import { gradient } from "actions" import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte" import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte" import { processStringSync } from "@budibase/string-templates" + import Spaceman from "assets/bb-space-man.svg" import Logo from "assets/bb-emblem.svg" let loaded = false @@ -91,7 +92,7 @@
- logo +
- {#if userApps.length} + {#if $licensing.usageMetrics.dayPasses >= 100} +
+ + spaceman + + {"Your apps are currently offline."} + + Please contact the account holder to get them back online. + +
+ {:else if userApps.length} Apps
@@ -194,10 +205,13 @@ justify-content: space-between; align-items: center; } - img { + img.logo { width: 40px; margin-bottom: -12px; } + img.spaceman { + width: 100px; + } .avatar { display: grid; grid-template-columns: auto auto; diff --git a/packages/builder/src/stores/portal/licensing.js b/packages/builder/src/stores/portal/licensing.js index cccb733a5e..ecf0ccb8c2 100644 --- a/packages/builder/src/stores/portal/licensing.js +++ b/packages/builder/src/stores/portal/licensing.js @@ -8,7 +8,6 @@ export const createLicensingStore = () => { const DEFAULT = { plans: {}, } - const oneDayInMilliseconds = 86400000 const store = writable(DEFAULT) @@ -26,8 +25,7 @@ export const createLicensingStore = () => { getUsageMetrics: async () => { const quota = get(store).quotaUsage const license = get(auth).user.license - const now = Date.now() - const nowSeconds = now / 1000 + const now = new Date() const getMetrics = (keys, license, quota) => { if (!license || !quota || !keys) { @@ -36,16 +34,12 @@ export const createLicensingStore = () => { return keys.reduce((acc, key) => { const quotaLimit = license[key].value const quotaUsed = (quota[key] / quotaLimit) * 100 - - // Catch for sessions - key = key === "sessions" ? "dayPasses" : key - acc[key] = quotaLimit > -1 ? Math.round(quotaUsed) : -1 return acc }, {}) } const monthlyMetrics = getMetrics( - ["sessions", "queries", "automations"], + ["dayPasses", "queries", "automations"], license.quotas.usage.monthly, quota.monthly.current ) @@ -55,52 +49,50 @@ export const createLicensingStore = () => { quota.usageQuota ) - // DEBUG - console.log("Store licensing val ", { - ...monthlyMetrics, - ...staticMetrics, - }) - - let subscriptionDaysRemaining - if (license?.billing?.subscription) { - const currentPeriodEnd = license.billing.subscription.currentPeriodEnd - const currentPeriodEndMilliseconds = currentPeriodEnd * 1000 - - subscriptionDaysRemaining = Math.round( - (currentPeriodEndMilliseconds - now) / oneDayInMilliseconds - ) + const getDaysBetween = (dateStart, dateEnd) => { + return dateEnd > dateStart + ? Math.round( + (dateEnd.getTime() - dateStart.getTime()) / oneDayInMilliseconds + ) + : 0 } - const quotaResetDaysRemaining = - quota.quotaReset > now - ? Math.round((quota.quotaReset - now) / oneDayInMilliseconds) - : 0 + const quotaResetDate = new Date(quota.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 === Constants.PlanType.FREE + license?.plan.type === Constants.PlanType.FREE - const accountPastDue = - nowSeconds >= license?.billing?.subscription?.currentPeriodEnd && - nowSeconds <= license?.billing?.subscription?.pastDueAt && - license?.billing?.subscription?.status === StripeStatus.PAST_DUE && - !accountDowngraded + const pastDueAtMilliseconds = license?.billing?.subscription?.pastDueAt + const downgradeAtMilliseconds = + license?.billing?.subscription?.downgradeAt + let pastDueDaysRemaining + let pastDueEndDate - const pastDueAtSeconds = license?.billing?.subscription?.pastDueAt - const pastDueAtMilliseconds = pastDueAtSeconds * 1000 - const paymentDueDaysRemaining = Math.round( - (pastDueAtMilliseconds - now) / oneDayInMilliseconds - ) + if (pastDueAtMilliseconds && downgradeAtMilliseconds) { + pastDueEndDate = new Date(downgradeAtMilliseconds) + pastDueDaysRemaining = getDaysBetween( + new Date(pastDueAtMilliseconds), + pastDueEndDate + ) + } store.update(state => { return { ...state, usageMetrics: { ...monthlyMetrics, ...staticMetrics }, - subscriptionDaysRemaining, - paymentDueDaysRemaining, quotaResetDaysRemaining, + quotaResetDate, accountDowngraded, - accountPastDue, + accountPastDue: pastDueAtMilliseconds != null, + pastDueEndDate, + pastDueDaysRemaining, + isFreePlan: () => { + return license?.plan.type === Constants.PlanType.FREE + }, } }) }, From 3697a4365b2779b9f87cba13d6034b60be666fae Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Wed, 14 Sep 2022 20:58:58 +0100 Subject: [PATCH 22/27] Update usage page --- .../builder/portal/settings/usage.svelte | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/settings/usage.svelte b/packages/builder/src/pages/builder/portal/settings/usage.svelte index 41a01e3cd3..f2809452fd 100644 --- a/packages/builder/src/pages/builder/portal/settings/usage.svelte +++ b/packages/builder/src/pages/builder/portal/settings/usage.svelte @@ -25,7 +25,8 @@ const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` const manageUrl = `${$admin.accountPortalUrl}/portal/billing` - const warnUsage = ["Queries", "Automations", "Rows", "Day Passes"] + const WARN_USAGE = ["Queries", "Automations", "Rows", "Day Passes"] + const EXCLUDE_QUOTAS = ["Queries"] $: quotaUsage = $licensing.quotaUsage $: license = $auth.user?.license @@ -36,11 +37,14 @@ monthlyUsage = [] if (quotaUsage.monthly) { for (let [key, value] of Object.entries(license.quotas.usage.monthly)) { + if (EXCLUDE_QUOTAS.includes(value.name)) { + continue + } const used = quotaUsage.monthly.current[key] - if (used !== undefined) { + if (value.value !== 0) { monthlyUsage.push({ name: value.name, - used: used, + used: used ? used : 0, total: value.value, }) } @@ -52,11 +56,14 @@ const setStaticUsage = () => { staticUsage = [] for (let [key, value] of Object.entries(license.quotas.usage.static)) { + if (EXCLUDE_QUOTAS.includes(value.name)) { + continue + } const used = quotaUsage.usageQuota[key] - if (used !== undefined) { + if (value.value !== 0) { staticUsage.push({ name: value.name, - used: used, + used: used ? used : 0, total: value.value, }) } @@ -199,7 +206,7 @@
{/each} @@ -222,7 +229,7 @@
{/each} From 25282c94554952e0ffdd29bf76cf1f014b465a55 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Wed, 14 Sep 2022 22:37:33 +0100 Subject: [PATCH 23/27] Show licensed group exceeded notification --- .../src/pages/builder/portal/manage/groups/index.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/portal/manage/groups/index.svelte b/packages/builder/src/pages/builder/portal/manage/groups/index.svelte index ddd734dd69..bb8f18ff13 100644 --- a/packages/builder/src/pages/builder/portal/manage/groups/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/groups/index.svelte @@ -38,7 +38,11 @@ try { await groups.actions.save(group) } catch (error) { - notifications.error(`Failed to save group`) + if (error.status === 400) { + notifications.error(error.message) + } else { + notifications.error(`Failed to save group`) + } } } From ff8760e86083f77bce81319453e27d081fa49cd9 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 15 Sep 2022 11:23:51 +0100 Subject: [PATCH 24/27] Pre-review tidy, add new badge to plugins --- packages/backend-core/src/constants.js | 1 + .../backend-core/src/middleware/authenticated.ts | 12 ++---------- .../builder/src/pages/builder/portal/_layout.svelte | 6 +++++- packages/server/src/api/routes/tests/user.spec.js | 2 -- packages/types/src/sdk/licensing/billing.ts | 8 ++++---- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/backend-core/src/constants.js b/packages/backend-core/src/constants.js index e1901986b4..89d4fd33e6 100644 --- a/packages/backend-core/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -6,6 +6,7 @@ exports.UserStatus = { exports.Cookies = { CurrentApp: "budibase:currentapp", Auth: "budibase:auth", + Init: "budibase:init", RETURN_URL: "budibase:returnurl", DatasourceAuth: "budibase:datasourceauth", OIDC_CONFIG: "budibase:oidc:config", diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index 0e1b31b9b7..a3c6b67cde 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -10,7 +10,6 @@ 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 @@ -68,11 +67,7 @@ async function checkApiKey(apiKey: string, populateUser?: Function) { */ export = ( noAuthPatterns = [], - opts: { - publicAllowed: boolean - populateUser?: Function - checkDayPass?: (ctx: any, user: User, tenantId: string) => Promise - } = { + opts: { publicAllowed: boolean; populateUser?: Function } = { publicAllowed: false, } ) => { @@ -112,10 +107,7 @@ export = ( } user.csrfToken = session.csrfToken - if ( - !session.lastAccessedAt || - session.lastAccessedAt < timeMinusOneMinute() - ) { + if (session?.lastAccessedAt < timeMinusOneMinute()) { // make sure we denote that the session is still in use await updateSessionTTL(session) } diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index 69dfa646b5..ff653c4cb7 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -54,7 +54,11 @@ : undefined, { title: "Auth", href: "/builder/portal/manage/auth" }, { title: "Email", href: "/builder/portal/manage/email" }, - { title: "Plugins", href: "/builder/portal/manage/plugins" }, + { + title: "Plugins", + href: "/builder/portal/manage/plugins", + badge: "New", + }, { title: "Organisation", diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 8436b80152..29c33b3899 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -2,8 +2,6 @@ const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles") const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") -jest.setTimeout(100000) - jest.mock("../../../utilities/workerRequests", () => ({ getGlobalUsers: jest.fn(() => { return {} diff --git a/packages/types/src/sdk/licensing/billing.ts b/packages/types/src/sdk/licensing/billing.ts index 6743b9cb17..da2aca1615 100644 --- a/packages/types/src/sdk/licensing/billing.ts +++ b/packages/types/src/sdk/licensing/billing.ts @@ -1,11 +1,11 @@ import { PriceDuration } from "./plan" -export interface CustomerBilling { +export interface Customer { balance: number | null | undefined currency: string | null | undefined } -export interface SubscriptionBilling { +export interface Subscription { amount: number quantity: number duration: PriceDuration @@ -15,6 +15,6 @@ export interface SubscriptionBilling { } export interface Billing { - customer: CustomerBilling - subscription?: SubscriptionBilling + customer: Customer + subscription?: Subscription } From c2fa27038673f26aa75796d1570b8ab2f62c20a4 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 15 Sep 2022 11:59:46 +0100 Subject: [PATCH 25/27] Fixes --- packages/backend-core/src/constants.js | 2 +- packages/builder/src/pages/builder/_layout.svelte | 5 +++-- packages/builder/src/pages/builder/portal/_layout.svelte | 3 +-- packages/builder/src/stores/portal/licensing.js | 4 ++++ packages/frontend-core/src/constants.js | 1 + 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/backend-core/src/constants.js b/packages/backend-core/src/constants.js index 89d4fd33e6..44c271a4f8 100644 --- a/packages/backend-core/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -7,7 +7,7 @@ exports.Cookies = { CurrentApp: "budibase:currentapp", Auth: "budibase:auth", Init: "budibase:init", - RETURN_URL: "budibase:returnurl", + ACCOUNT_RETURN_URL: "budibase:account:returnurl", DatasourceAuth: "budibase:datasourceauth", OIDC_CONFIG: "budibase:oidc:config", } diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index ee8b1bb8df..8d604e8790 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -63,8 +63,9 @@ await auth.getSelf() await admin.init() - await licensing.getQuotaUsage() - await licensing.getUsageMetrics() + if ($auth.user) { + await licensing.init() + } // Set init info if present if ($params["?template"]) { diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index ff653c4cb7..bdf18ab508 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -13,8 +13,7 @@ notifications, } from "@budibase/bbui" import ConfigChecklist from "components/common/ConfigChecklist.svelte" - import { organisation, auth } from "stores/portal" - import { admin as adminStore } from "stores/portal" + import { organisation, auth, admin as adminStore } from "stores/portal" import { onMount } from "svelte" import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte" import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte" diff --git a/packages/builder/src/stores/portal/licensing.js b/packages/builder/src/stores/portal/licensing.js index ecf0ccb8c2..9d63a9edb5 100644 --- a/packages/builder/src/stores/portal/licensing.js +++ b/packages/builder/src/stores/portal/licensing.js @@ -13,6 +13,10 @@ export const createLicensingStore = () => { const store = writable(DEFAULT) const actions = { + init: async () => { + await actions.getQuotaUsage() + await actions.getUsageMetrics() + }, getQuotaUsage: async () => { const quotaUsage = await API.getQuotaUsage() store.update(state => { diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js index a5b3650b1c..633534dddb 100644 --- a/packages/frontend-core/src/constants.js +++ b/packages/frontend-core/src/constants.js @@ -57,6 +57,7 @@ export const Cookies = { Auth: "budibase:auth", CurrentApp: "budibase:currentapp", ReturnUrl: "budibase:returnurl", + AccountReturnUrl: "budibase:account:returnurl", } // Table names From 59e8e97bbca1c810a54778dd58a9415a4d389855 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 15 Sep 2022 12:45:47 +0100 Subject: [PATCH 26/27] Updated copy from the payment modal --- .../src/components/portal/licensing/PaymentFailedModal.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte index 591742a3d0..21a3c2dcd9 100644 --- a/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte +++ b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte @@ -38,7 +38,7 @@ window.location.href = upgradeUrl }} > - The payment for your business plan subscription has failed + The payment for your subscription has failed Please upgrade your billing details before your account gets downgraded to the free plan @@ -56,7 +56,7 @@ {:else} - The payment for your business plan subscription has failed + The payment for your subscription has failed Please upgrade your billing details before your account gets downgraded to the free plan From c8789412e2346c2dcd46aec47f937b4830ff9078 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 15 Sep 2022 13:22:17 +0100 Subject: [PATCH 27/27] Don't show budibase logo on free plan in self hosted installations --- .../client/src/components/app/Layout.svelte | 11 +++++-- packages/client/src/sdk.js | 2 ++ packages/client/src/stores/environment.js | 31 +++++++++++++++++++ packages/client/src/stores/index.js | 1 + packages/client/src/stores/initialise.js | 2 ++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/stores/environment.js diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index eb77e7a550..7897782b3e 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -9,7 +9,14 @@ import licensing from "../../licensing" const sdk = getContext("sdk") - const { routeStore, styleable, linkable, builderStore, currentRole } = sdk + const { + routeStore, + styleable, + linkable, + builderStore, + currentRole, + environmentStore, + } = sdk const component = getContext("component") const context = getContext("context") @@ -228,7 +235,7 @@
{/if} - {#if !$builderStore.inBuilder && licensing.logoEnabled()} + {#if !$builderStore.inBuilder && licensing.logoEnabled() && $environmentStore.cloud} {/if} diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index 13190e0a4f..aa778388f6 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -9,6 +9,7 @@ import { rowSelectionStore, componentStore, currentRole, + environmentStore, } from "stores" import { styleable } from "utils/styleable" import { linkable } from "utils/linkable" @@ -27,6 +28,7 @@ export default { builderStore, uploadStore, componentStore, + environmentStore, currentRole, styleable, linkable, diff --git a/packages/client/src/stores/environment.js b/packages/client/src/stores/environment.js new file mode 100644 index 0000000000..ebeb67c622 --- /dev/null +++ b/packages/client/src/stores/environment.js @@ -0,0 +1,31 @@ +import { API } from "api" +import { writable } from "svelte/store" + +const initialState = { + cloud: false, +} + +const createEnvironmentStore = () => { + const store = writable(initialState) + + const actions = { + fetchEnvironment: async () => { + try { + const environment = await API.getEnvironment() + store.set({ + ...initialState, + ...environment, + }) + } catch (error) { + store.set(initialState) + } + }, + } + + return { + subscribe: store.subscribe, + actions, + } +} + +export const environmentStore = createEnvironmentStore() diff --git a/packages/client/src/stores/index.js b/packages/client/src/stores/index.js index e28fbaee42..378d3febd2 100644 --- a/packages/client/src/stores/index.js +++ b/packages/client/src/stores/index.js @@ -17,6 +17,7 @@ export { devToolsStore } from "./devTools" export { componentStore } from "./components" export { uploadStore } from "./uploads.js" export { rowSelectionStore } from "./rowSelection.js" +export { environmentStore } from "./environment" // Context stores are layered and duplicated, so it is not a singleton export { createContextStore } from "./context" diff --git a/packages/client/src/stores/initialise.js b/packages/client/src/stores/initialise.js index 1900e62ce1..4ad85dfd40 100644 --- a/packages/client/src/stores/initialise.js +++ b/packages/client/src/stores/initialise.js @@ -1,7 +1,9 @@ import { routeStore } from "./routes" import { appStore } from "./app" +import { environmentStore } from "./environment" export async function initialise() { await routeStore.actions.fetchRoutes() await appStore.actions.fetchAppDefinition() + await environmentStore.actions.fetchEnvironment() }