diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index cb27b30f3f..bbab4d5bb7 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -12,6 +12,9 @@ on: - master - develop +env: + BRANCH: ${{ github.event.pull_request.head.ref }} + jobs: build: runs-on: ubuntu-latest @@ -27,6 +30,18 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} + + # Add @budibase/pro to filesystem + - name: Checkout pro + uses: actions/checkout@v2 + with: + repository: budibase/budibase-pro + ref: ${{ env.BRANCH }} + path: './pro' + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + - name: Setup pro + run: mv pro ../budibase-pro && cd ../budibase-pro && yarn setup + - run: yarn - run: yarn bootstrap - run: yarn lint diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index 4467cd6c81..9e0e2b3ed1 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -25,10 +25,27 @@ jobs: runs-on: ubuntu-latest steps: + - name: Extract branch name + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF_NAME})" + id: extract_branch + - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: 14.x + + # Add @budibase/pro to filesystem + - name: Checkout pro + uses: actions/checkout@v2 + with: + repository: budibase/budibase-pro + ref: ${{ steps.extract_branch.outputs.branch }} + path: './pro' + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + - name: Setup pro + run: mv pro ../budibase-pro && cd ../budibase-pro && yarn setup + - run: yarn - run: yarn bootstrap - run: yarn lint @@ -46,12 +63,27 @@ jobs: env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: | - # setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default - git config user.name "Budibase Staging Release Bot" - git config user.email "<>" + # setup the username and email. + git config --global user.name "Budibase Staging Release Bot" + git config --global user.email "<>" echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc yarn release:develop + - name: Get the latest budibase release version + id: version + run: | + release_version=$(cat lerna.json | jq -r '.version') + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + + - name: Publish @budibase/pro package to NPM + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + VERSION: ${{ steps.previoustag.outputs.tag }} + run: | + cd ../budibase-pro + echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc + yarn release:develop $RELEASE_VERSION + - name: Build/release Docker images run: | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index fd80602293..c80cfa2ecc 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -98,10 +98,6 @@ spec: value: http://worker-service:{{ .Values.services.worker.port }} - name: PLATFORM_URL value: {{ .Values.globals.platformUrl | quote }} - - name: USE_QUOTAS - value: {{ .Values.globals.useQuotas | quote }} - - name: EXCLUDE_QUOTAS_TENANTS - value: {{ .Values.globals.excludeQuotasTenants | quote }} - name: ACCOUNT_PORTAL_URL value: {{ .Values.globals.accountPortalUrl | quote }} - name: ACCOUNT_PORTAL_API_KEY diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index ab9e150f19..81fdfb63d2 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -93,15 +93,13 @@ globals: logLevel: info selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs - useQuotas: "0" - excludeQuotasTenants: "" # comma seperated list of tenants to exclude from quotas accountPortalUrl: "" accountPortalApiKey: "" cookieDomain: "" platformUrl: "" httpMigrations: "0" google: - clientId: "" + clientId: "" secret: "" automationMaxIterations: "500" diff --git a/lerna.json b/lerna.json index a4b80ecff1..57b20fe60b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.105-alpha.23", + "version": "1.0.105-alpha.24", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 0a52900ca3..953c4bcd76 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,8 @@ }, "scripts": { "setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev", - "bootstrap": "lerna link && lerna bootstrap", + "bootstrap": "lerna link && lerna bootstrap && ./scripts/link-dependencies.sh", "build": "lerna run build", - "publishdev": "lerna run publishdev", - "publishnpm": "yarn build && lerna publish --force-publish", "release": "lerna publish patch --yes --force-publish", "release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop", "restore": "yarn run clean && yarn run bootstrap && yarn run build", @@ -32,7 +30,6 @@ "nuke:packages": "yarn run restore", "nuke:docker": "lerna run --parallel dev:stack:nuke", "clean": "lerna clean", - "kill-port": "kill-port 4001", "kill-builder": "kill-port 3000", "kill-server": "kill-port 4001 4002", "kill-all": "yarn run kill-builder && yarn run kill-server", diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index d8926e5518..033be41582 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.105-alpha.23", + "version": "1.0.105-alpha.24", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/constants.js b/packages/backend-core/src/constants.js index 42450190e5..172e66e603 100644 --- a/packages/backend-core/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -13,6 +13,7 @@ exports.Cookies = { exports.Headers = { API_KEY: "x-budibase-api-key", + LICENSE_KEY: "x-budibase-license-key", API_VER: "x-budibase-api-version", APP_ID: "x-budibase-app-id", TYPE: "x-budibase-type", diff --git a/packages/backend-core/src/db/constants.js b/packages/backend-core/src/db/constants.js index 5ee8033e05..271d4f412d 100644 --- a/packages/backend-core/src/db/constants.js +++ b/packages/backend-core/src/db/constants.js @@ -23,6 +23,7 @@ exports.StaticDatabases = { docs: { apiKeys: "apikeys", usageQuota: "usage_quota", + licenseInfo: "license_info", }, }, // contains information about tenancy and so on diff --git a/packages/backend-core/src/db/utils.js b/packages/backend-core/src/db/utils.js index feb17c4129..ac401dea85 100644 --- a/packages/backend-core/src/db/utils.js +++ b/packages/backend-core/src/db/utils.js @@ -27,6 +27,7 @@ const UNICODE_MAX = "\ufff0" exports.ViewNames = { USER_BY_EMAIL: "by_email", BY_API_KEY: "by_api_key", + USER_BY_BUILDERS: "by_builders", } exports.StaticDatabases = StaticDatabases @@ -429,34 +430,9 @@ async function getScopedConfig(db, params) { return configDoc && configDoc.config ? configDoc.config : configDoc } -function generateNewUsageQuotaDoc() { - return { - _id: StaticDatabases.GLOBAL.docs.usageQuota, - quotaReset: Date.now() + 2592000000, - usageQuota: { - automationRuns: 0, - rows: 0, - storage: 0, - apps: 0, - users: 0, - views: 0, - emails: 0, - }, - usageLimits: { - automationRuns: 1000, - rows: 4000, - apps: 4, - storage: 1000, - users: 10, - emails: 50, - }, - } -} - exports.Replication = Replication exports.getScopedConfig = getScopedConfig exports.generateConfigID = generateConfigID exports.getConfigParams = getConfigParams exports.getScopedFullConfig = getScopedFullConfig -exports.generateNewUsageQuotaDoc = generateNewUsageQuotaDoc exports.generateDevInfoID = generateDevInfoID diff --git a/packages/backend-core/src/db/views.js b/packages/backend-core/src/db/views.js index e5be8e6b40..e0281c6584 100644 --- a/packages/backend-core/src/db/views.js +++ b/packages/backend-core/src/db/views.js @@ -56,10 +56,34 @@ exports.createApiKeyView = async () => { await db.put(designDoc) } +exports.createUserBuildersView = async () => { + const db = getGlobalDB() + let designDoc + try { + designDoc = await db.get("_design/database") + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + map: `function(doc) { + if (doc.builder && doc.builder.global === true) { + emit(doc._id, doc._id) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewNames.USER_BY_BUILDERS]: view, + } + await db.put(designDoc) +} + exports.queryGlobalView = async (viewName, params, db = null) => { const CreateFuncByName = { [ViewNames.USER_BY_EMAIL]: exports.createUserEmailView, [ViewNames.BY_API_KEY]: exports.createApiKeyView, + [ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView, } // can pass DB in if working with something specific if (!db) { diff --git a/packages/backend-core/src/environment.js b/packages/backend-core/src/environment.js index d112ad8599..856ab1b97c 100644 --- a/packages/backend-core/src/environment.js +++ b/packages/backend-core/src/environment.js @@ -28,6 +28,7 @@ module.exports = { SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED), COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, PLATFORM_URL: process.env.PLATFORM_URL, + TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS, isTest, _set(key, value) { process.env[key] = value diff --git a/packages/backend-core/src/errors/base.js b/packages/backend-core/src/errors/base.js new file mode 100644 index 0000000000..d31f9838f4 --- /dev/null +++ b/packages/backend-core/src/errors/base.js @@ -0,0 +1,11 @@ +class BudibaseError extends Error { + constructor(message, type, code) { + super(message) + this.type = type + this.code = code + } +} + +module.exports = { + BudibaseError, +} diff --git a/packages/backend-core/src/errors/index.js b/packages/backend-core/src/errors/index.js new file mode 100644 index 0000000000..4f3b4e0c41 --- /dev/null +++ b/packages/backend-core/src/errors/index.js @@ -0,0 +1,41 @@ +const licensing = require("./licensing") + +const codes = { + ...licensing.codes, +} + +const types = { + ...licensing.types, +} + +const context = { + ...licensing.context, +} + +const getPublicError = err => { + let error + if (err.code || err.type) { + // add generic error information + error = { + code: err.code, + type: err.type, + } + + if (err.code && context[err.code]) { + error = { + ...error, + // get any additional context from this error + ...context[err.code](err), + } + } + } + + return error +} + +module.exports = { + codes, + types, + UsageLimitError: licensing.UsageLimitError, + getPublicError, +} diff --git a/packages/backend-core/src/errors/licensing.js b/packages/backend-core/src/errors/licensing.js new file mode 100644 index 0000000000..c05f9c561e --- /dev/null +++ b/packages/backend-core/src/errors/licensing.js @@ -0,0 +1,32 @@ +const { BudibaseError } = require("./base") + +const types = { + LICENSE_ERROR: "license_error", +} + +const codes = { + USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded", +} + +const context = { + [codes.USAGE_LIMIT_EXCEEDED]: err => { + return { + limitName: err.limitName, + } + }, +} + +class UsageLimitError extends BudibaseError { + constructor(message, limitName) { + super(message, types.LICENSE_ERROR, codes.USAGE_LIMIT_EXCEEDED) + this.limitName = limitName + this.status = 400 + } +} + +module.exports = { + types, + codes, + context, + UsageLimitError, +} diff --git a/packages/backend-core/src/featureFlags/index.js b/packages/backend-core/src/featureFlags/index.js new file mode 100644 index 0000000000..6d3d86978a --- /dev/null +++ b/packages/backend-core/src/featureFlags/index.js @@ -0,0 +1,52 @@ +const env = require("../environment") +const tenancy = require("../tenancy") + +/** + * Read the TENANT_FEATURE_FLAGS env var and return an array of features flags for each tenant. + * The env var is formatted as: + * tenant1:feature1:feature2,tenant2:feature1 + */ +const getFeatureFlags = () => { + if (!env.TENANT_FEATURE_FLAGS) { + return + } + + const tenantFeatureFlags = {} + + env.TENANT_FEATURE_FLAGS.split(",").forEach(tenantToFeatures => { + const [tenantId, ...features] = tenantToFeatures.split(":") + + features.forEach(feature => { + if (!tenantFeatureFlags[tenantId]) { + tenantFeatureFlags[tenantId] = [] + } + tenantFeatureFlags[tenantId].push(feature) + }) + }) + + return tenantFeatureFlags +} + +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) + ) +} + +exports.getTenantFeatureFlags = tenantId => { + if (TENANT_FEATURE_FLAGS && TENANT_FEATURE_FLAGS[tenantId]) { + return TENANT_FEATURE_FLAGS[tenantId] + } + + return [] +} + +exports.FeatureFlag = { + LICENSING: "LICENSING", +} diff --git a/packages/backend-core/src/index.js b/packages/backend-core/src/index.js index b0bc524d9b..8f71580162 100644 --- a/packages/backend-core/src/index.js +++ b/packages/backend-core/src/index.js @@ -15,4 +15,9 @@ module.exports = { auth: require("../auth"), constants: require("../constants"), migrations: require("../migrations"), + errors: require("./errors"), + env: require("./environment"), + accounts: require("./cloud/accounts"), + tenancy: require("./tenancy"), + featureFlags: require("./featureFlags"), } diff --git a/packages/backend-core/src/middleware/passport/google.js b/packages/backend-core/src/middleware/passport/google.js index 8fd0961ea1..5e95a906d8 100644 --- a/packages/backend-core/src/middleware/passport/google.js +++ b/packages/backend-core/src/middleware/passport/google.js @@ -2,24 +2,27 @@ const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy const { authenticateThirdParty } = require("./third-party-common") -async function authenticate(accessToken, refreshToken, profile, done) { - const thirdPartyUser = { - provider: profile.provider, // should always be 'google' - providerType: "google", - userId: profile.id, - profile: profile, - email: profile._json.email, - oauth2: { - accessToken: accessToken, - refreshToken: refreshToken, - }, - } +const buildVerifyFn = async saveUserFn => { + return (accessToken, refreshToken, profile, done) => { + const thirdPartyUser = { + provider: profile.provider, // should always be 'google' + providerType: "google", + userId: profile.id, + profile: profile, + email: profile._json.email, + oauth2: { + accessToken: accessToken, + refreshToken: refreshToken, + }, + } - return authenticateThirdParty( - thirdPartyUser, - true, // require local accounts to exist - done - ) + return authenticateThirdParty( + thirdPartyUser, + true, // require local accounts to exist + done, + saveUserFn + ) + } } /** @@ -27,11 +30,7 @@ async function authenticate(accessToken, refreshToken, profile, done) { * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * @returns Dynamically configured Passport Google Strategy */ -exports.strategyFactory = async function ( - config, - callbackUrl, - verify = authenticate -) { +exports.strategyFactory = async function (config, callbackUrl, saveUserFn) { try { const { clientID, clientSecret } = config @@ -41,6 +40,7 @@ exports.strategyFactory = async function ( ) } + const verify = buildVerifyFn(saveUserFn) return new GoogleStrategy( { clientID: config.clientID, @@ -58,4 +58,4 @@ exports.strategyFactory = async function ( } } // expose for testing -exports.authenticate = authenticate +exports.buildVerifyFn = buildVerifyFn diff --git a/packages/backend-core/src/middleware/passport/oidc.js b/packages/backend-core/src/middleware/passport/oidc.js index 3a75dfcf8e..1e93e20b1c 100644 --- a/packages/backend-core/src/middleware/passport/oidc.js +++ b/packages/backend-core/src/middleware/passport/oidc.js @@ -2,46 +2,49 @@ const fetch = require("node-fetch") const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy const { authenticateThirdParty } = require("./third-party-common") -/** - * @param {*} issuer The identity provider base URL - * @param {*} sub The user ID - * @param {*} profile The user profile information. Created by passport from the /userinfo response - * @param {*} jwtClaims The parsed id_token claims - * @param {*} accessToken The access_token for contacting the identity provider - may or may not be a JWT - * @param {*} refreshToken The refresh_token for obtaining a new access_token - usually not a JWT - * @param {*} idToken The id_token - always a JWT - * @param {*} params The response body from requesting an access_token - * @param {*} done The passport callback: err, user, info - */ -async function authenticate( - issuer, - sub, - profile, - jwtClaims, - accessToken, - refreshToken, - idToken, - params, - done -) { - const thirdPartyUser = { - // store the issuer info to enable sync in future - provider: issuer, - providerType: "oidc", - userId: profile.id, - profile: profile, - email: getEmail(profile, jwtClaims), - oauth2: { - accessToken: accessToken, - refreshToken: refreshToken, - }, - } - - return authenticateThirdParty( - thirdPartyUser, - false, // don't require local accounts to exist +const buildVerifyFn = saveUserFn => { + /** + * @param {*} issuer The identity provider base URL + * @param {*} sub The user ID + * @param {*} profile The user profile information. Created by passport from the /userinfo response + * @param {*} jwtClaims The parsed id_token claims + * @param {*} accessToken The access_token for contacting the identity provider - may or may not be a JWT + * @param {*} refreshToken The refresh_token for obtaining a new access_token - usually not a JWT + * @param {*} idToken The id_token - always a JWT + * @param {*} params The response body from requesting an access_token + * @param {*} done The passport callback: err, user, info + */ + return async ( + issuer, + sub, + profile, + jwtClaims, + accessToken, + refreshToken, + idToken, + params, done - ) + ) => { + const thirdPartyUser = { + // store the issuer info to enable sync in future + provider: issuer, + providerType: "oidc", + userId: profile.id, + profile: profile, + email: getEmail(profile, jwtClaims), + oauth2: { + accessToken: accessToken, + refreshToken: refreshToken, + }, + } + + return authenticateThirdParty( + thirdPartyUser, + false, // don't require local accounts to exist + done, + saveUserFn + ) + } } /** @@ -86,7 +89,7 @@ function validEmail(value) { * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * @returns Dynamically configured Passport OIDC Strategy */ -exports.strategyFactory = async function (config, callbackUrl) { +exports.strategyFactory = async function (config, callbackUrl, saveUserFn) { try { const { clientID, clientSecret, configUrl } = config @@ -106,6 +109,7 @@ exports.strategyFactory = async function (config, callbackUrl) { const body = await response.json() + const verify = buildVerifyFn(saveUserFn) return new OIDCStrategy( { issuer: body.issuer, @@ -116,7 +120,7 @@ exports.strategyFactory = async function (config, callbackUrl) { clientSecret: clientSecret, callbackURL: callbackUrl, }, - authenticate + verify ) } catch (err) { console.error(err) @@ -125,4 +129,4 @@ exports.strategyFactory = async function (config, callbackUrl) { } // expose for testing -exports.authenticate = authenticate +exports.buildVerifyFn = buildVerifyFn diff --git a/packages/backend-core/src/middleware/passport/tests/google.spec.js b/packages/backend-core/src/middleware/passport/tests/google.spec.js index 9cc878bba9..c5580ea309 100644 --- a/packages/backend-core/src/middleware/passport/tests/google.spec.js +++ b/packages/backend-core/src/middleware/passport/tests/google.spec.js @@ -58,8 +58,10 @@ describe("google", () => { it("delegates authentication to third party common", async () => { const google = require("../google") + const mockSaveUserFn = jest.fn() + const authenticate = await google.buildVerifyFn(mockSaveUserFn) - await google.authenticate( + await authenticate( data.accessToken, data.refreshToken, profile, @@ -69,7 +71,8 @@ describe("google", () => { expect(authenticateThirdParty).toHaveBeenCalledWith( user, true, - mockDone) + mockDone, + mockSaveUserFn) }) }) }) diff --git a/packages/backend-core/src/middleware/passport/tests/oidc.spec.js b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js index 44538b9135..bfe9f97dc0 100644 --- a/packages/backend-core/src/middleware/passport/tests/oidc.spec.js +++ b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js @@ -83,8 +83,10 @@ describe("oidc", () => { async function doAuthenticate() { const oidc = require("../oidc") + const mockSaveUserFn = jest.fn() + const authenticate = await oidc.buildVerifyFn(mockSaveUserFn) - await oidc.authenticate( + await authenticate( issuer, sub, profile, diff --git a/packages/backend-core/src/middleware/passport/third-party-common.js b/packages/backend-core/src/middleware/passport/third-party-common.js index b467c0b10b..3fbfb145bc 100644 --- a/packages/backend-core/src/middleware/passport/third-party-common.js +++ b/packages/backend-core/src/middleware/passport/third-party-common.js @@ -1,7 +1,6 @@ const env = require("../../environment") const jwt = require("jsonwebtoken") const { generateGlobalUserID } = require("../../db/utils") -const { saveUser } = require("../../utils") const { authError } = require("./utils") const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") @@ -16,8 +15,11 @@ exports.authenticateThirdParty = async function ( thirdPartyUser, requireLocalAccount = true, done, - saveUserFn = saveUser + saveUserFn ) { + if (!saveUserFn) { + throw new Error("Save user function must be provided") + } if (!thirdPartyUser.provider) { return authError(done, "third party user provider required") } diff --git a/packages/backend-core/src/redis/utils.js b/packages/backend-core/src/redis/utils.js index 4c2b2f5cae..77f64f6593 100644 --- a/packages/backend-core/src/redis/utils.js +++ b/packages/backend-core/src/redis/utils.js @@ -17,6 +17,7 @@ exports.Databases = { FLAGS: "flags", APP_METADATA: "appMetadata", QUERY_VARS: "queryVars", + LICENSES: "license", } exports.SEPARATOR = SEPARATOR diff --git a/packages/backend-core/src/utils.js b/packages/backend-core/src/utils.js index 8909f62995..e4b358a676 100644 --- a/packages/backend-core/src/utils.js +++ b/packages/backend-core/src/utils.js @@ -176,6 +176,13 @@ exports.getGlobalUserByEmail = async email => { }) } +exports.getBuildersCount = async () => { + const builders = await queryGlobalView(ViewNames.USER_BY_BUILDERS, { + include_docs: false, + }) + return builders ? builders.length : 0 +} + exports.saveUser = async ( user, tenantId, @@ -289,4 +296,5 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { userId, sessions.map(({ sessionId }) => sessionId) ) + await userCache.invalidateUser(userId) } diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 2b7acf4567..816bd5cc30 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.105-alpha.23", + "version": "1.0.105-alpha.24", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.2.1", - "@budibase/string-templates": "^1.0.105-alpha.23", + "@budibase/string-templates": "^1.0.105-alpha.24", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte index 9658f9b9f1..c12d54787b 100644 --- a/packages/bbui/src/Layout/Page.svelte +++ b/packages/bbui/src/Layout/Page.svelte @@ -1,8 +1,9 @@ -
+
@@ -12,7 +13,7 @@ flex-direction: column; justify-content: flex-start; align-items: stretch; - max-width: 80ch; + max-width: var(--max-width); margin: 0 auto; padding: calc(var(--spacing-xl) * 2); min-height: calc(100% - var(--spacing-xl) * 4); diff --git a/packages/bbui/src/ProgressBar/ProgressBar.svelte b/packages/bbui/src/ProgressBar/ProgressBar.svelte index 221453d428..0bc50fb452 100644 --- a/packages/bbui/src/ProgressBar/ProgressBar.svelte +++ b/packages/bbui/src/ProgressBar/ProgressBar.svelte @@ -16,11 +16,11 @@ easing: easing, }) - $: if (value) $progress = value + $: if (value || value === 0) $progress = value
{#if $$slots}
{/if} - {#if value} + {#if value || value === 0}
@@ -47,7 +47,7 @@