diff --git a/.gitignore b/.gitignore index f063e2224f..32c6faf980 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,6 @@ packages/builder/cypress/reports stats.html # TypeScript cache -*.tsbuildinfo \ No newline at end of file +*.tsbuildinfo +budibase-component +budibase-datasource diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 422097e699..6517133a58 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -124,11 +124,15 @@ spec: value: {{ .Values.globals.tenantFeatureFlags | quote }} {{ if .Values.globals.bbAdminUserEmail }} - name: BB_ADMIN_USER_EMAIL - value: { { .Values.globals.bbAdminUserEmail | quote } } + value: {{ .Values.globals.bbAdminUserEmail | quote }} {{ end }} {{ if .Values.globals.bbAdminUserPassword }} - name: BB_ADMIN_USER_PASSWORD - value: { { .Values.globals.bbAdminUserPassword | quote } } + value: {{ .Values.globals.bbAdminUserPassword | quote }} + {{ end }} + {{ if .Values.globals.pluginsDir }} + - name: PLUGINS_DIR + value: {{ .Values.globals.pluginsDir | quote }} {{ end }} {{ if .Values.services.apps.nodeDebug }} - name: NODE_DEBUG diff --git a/examples/nextjs-api-sales/package.json b/examples/nextjs-api-sales/package.json index 6d75c85f01..41ce52e952 100644 --- a/examples/nextjs-api-sales/package.json +++ b/examples/nextjs-api-sales/package.json @@ -11,8 +11,8 @@ "dependencies": { "bulma": "^0.9.3", "next": "12.1.0", - "node-fetch": "^3.2.2", - "node-sass": "^7.0.1", + "node-fetch": "^3.2.10", + "sass": "^1.52.3", "react": "17.0.2", "react-dom": "17.0.2", "react-notifications-component": "^3.4.1" @@ -24,4 +24,4 @@ "eslint-config-next": "12.1.0", "typescript": "4.6.2" } -} +} \ No newline at end of file diff --git a/examples/nextjs-api-sales/yarn.lock b/examples/nextjs-api-sales/yarn.lock index 52c89967b2..f47fb84e33 100644 --- a/examples/nextjs-api-sales/yarn.lock +++ b/examples/nextjs-api-sales/yarn.lock @@ -2020,10 +2020,10 @@ node-domexception@^1.0.0: resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -node-fetch@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.2.tgz#16d33fbe32ca7c6ca1ca8ba5dfea1dd885c59f04" - integrity sha512-Cwhq1JFIoon15wcIkFzubVNFE5GvXGV82pKf4knXXjvGmn7RJKcypeuqcVNZMGDZsAFWyIRya/anwAJr7TWJ7w== +node-fetch@^3.2.10: + version "3.2.10" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8" + integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA== dependencies: data-uri-to-buffer "^4.0.0" fetch-blob "^3.1.4" diff --git a/hosting/.env b/hosting/.env index 11dd661bf1..c5638a266f 100644 --- a/hosting/.env +++ b/hosting/.env @@ -22,4 +22,7 @@ BUDIBASE_ENVIRONMENT=PRODUCTION # An admin user can be automatically created initially if these are set BB_ADMIN_USER_EMAIL= -BB_ADMIN_USER_PASSWORD= \ No newline at end of file +BB_ADMIN_USER_PASSWORD= + +# A path that is watched for plugin bundles. Any bundles found are imported automatically/ +PLUGINS_DIR= \ No newline at end of file diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index c55ca34547..5b2adc2665 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -25,9 +25,12 @@ services: REDIS_PASSWORD: ${REDIS_PASSWORD} BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL} BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD} + PLUGINS_DIR: ${PLUGINS_DIR} depends_on: - worker-service - redis-service +# volumes: +# - /some/path/to/plugins:/plugins worker-service: restart: unless-stopped diff --git a/hosting/hosting.properties b/hosting/hosting.properties index 11dd661bf1..c5638a266f 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -22,4 +22,7 @@ BUDIBASE_ENVIRONMENT=PRODUCTION # An admin user can be automatically created initially if these are set BB_ADMIN_USER_EMAIL= -BB_ADMIN_USER_PASSWORD= \ No newline at end of file +BB_ADMIN_USER_PASSWORD= + +# A path that is watched for plugin bundles. Any bundles found are imported automatically/ +PLUGINS_DIR= \ No newline at end of file diff --git a/hosting/nginx.dev.conf.hbs b/hosting/nginx.dev.conf.hbs index 20c4d3d182..14c32b1bba 100644 --- a/hosting/nginx.dev.conf.hbs +++ b/hosting/nginx.dev.conf.hbs @@ -65,10 +65,6 @@ http { proxy_pass http://{{ address }}:4001; } - location /preview { - proxy_pass http://{{ address }}:4001; - } - location /builder { proxy_pass http://{{ address }}:3000; rewrite ^/builder(.*)$ /builder/$1 break; @@ -84,6 +80,20 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + location /vite/ { + proxy_pass http://{{ address }}:3000; + rewrite ^/vite(.*)$ /$1 break; + } + + location /socket/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_pass http://{{ address }}:4001; + } + location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/hosting/nginx.prod.conf.hbs b/hosting/nginx.prod.conf.hbs index 0ff986d0a7..f3202ad4a4 100644 --- a/hosting/nginx.prod.conf.hbs +++ b/hosting/nginx.prod.conf.hbs @@ -88,10 +88,6 @@ http { proxy_pass http://$apps:4002; } - location /preview { - proxy_pass http://$apps:4002; - } - location = / { proxy_pass http://$apps:4002; } @@ -162,6 +158,15 @@ http { rewrite ^/db/(.*)$ /$1 break; } + location /socket/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_pass http://$apps:4002; + } + location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/hosting/scripts/build-target-paths.sh b/hosting/scripts/build-target-paths.sh index ee314c1ce4..fce768e2ee 100644 --- a/hosting/scripts/build-target-paths.sh +++ b/hosting/scripts/build-target-paths.sh @@ -4,9 +4,9 @@ echo ${TARGETBUILD} > /buildtarget.txt if [[ "${TARGETBUILD}" = "aas" ]]; then # Azure AppService uses /home for persisent data & SSH on port 2222 DATA_DIR=/home - mkdir -p $DATA_DIR/{search,minio,couchdb} - mkdir -p $DATA_DIR/couchdb/{dbs,views} - chown -R couchdb:couchdb $DATA_DIR/couchdb/ + mkdir -p $DATA_DIR/{search,minio,couch} + mkdir -p $DATA_DIR/couch/{dbs,views} + chown -R couchdb:couchdb $DATA_DIR/couch/ apt update apt-get install -y openssh-server sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config @@ -16,5 +16,4 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then else sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini - fi \ No newline at end of file diff --git a/hosting/single/couch/local.ini b/hosting/single/couch/local.ini index 35f0383dfc..266c0d4b60 100644 --- a/hosting/single/couch/local.ini +++ b/hosting/single/couch/local.ini @@ -1,5 +1,5 @@ ; CouchDB Configuration Settings [couchdb] -database_dir = DATA_DIR/couchdb/dbs -view_index_dir = DATA_DIR/couchdb/views +database_dir = DATA_DIR/couch/dbs +view_index_dir = DATA_DIR/couch/views diff --git a/hosting/single/nginx/nginx-default-site.conf b/hosting/single/nginx/nginx-default-site.conf index c0d80a0185..bd89e21251 100644 --- a/hosting/single/nginx/nginx-default-site.conf +++ b/hosting/single/nginx/nginx-default-site.conf @@ -66,6 +66,15 @@ server { rewrite ^/db/(.*)$ /$1 break; } + location /socket/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_pass http://127.0.0.1:4001; + } + location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index 09387343ba..77015d75ee 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -36,10 +36,10 @@ fi export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984 # make these directories in runner, incase of mount -mkdir -p ${DATA_DIR}/couchdb/{dbs,views} +mkdir -p ${DATA_DIR}/couch/{dbs,views} mkdir -p ${DATA_DIR}/minio mkdir -p ${DATA_DIR}/search -chown -R couchdb:couchdb ${DATA_DIR}/couchdb +chown -R couchdb:couchdb ${DATA_DIR}/couch redis-server --requirepass $REDIS_PASSWORD & /opt/clouseau/bin/clouseau & /minio/minio server ${DATA_DIR}/minio & diff --git a/lerna.json b/lerna.json index a2dcf1a4ad..1089acf87a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.3.4-alpha.2", + "version": "1.3.15-alpha.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index d6d8d83530..9a770d3887 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.3.4-alpha.2", + "version": "1.3.15-alpha.3", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -20,7 +20,8 @@ "test:watch": "jest --watchAll" }, "dependencies": { - "@budibase/types": "1.3.4-alpha.2", + "@budibase/types": "1.3.15-alpha.3", + "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-sdk": "2.1030.0", "bcrypt": "5.0.1", @@ -60,7 +61,6 @@ ] }, "devDependencies": { - "@shopify/jest-koa-mocks": "3.1.5", "@types/jest": "27.5.1", "@types/koa": "2.0.52", "@types/lodash": "4.14.180", diff --git a/packages/backend-core/plugins.js b/packages/backend-core/plugins.js new file mode 100644 index 0000000000..018e214dcb --- /dev/null +++ b/packages/backend-core/plugins.js @@ -0,0 +1,3 @@ +module.exports = { + ...require("./src/plugin"), +} diff --git a/packages/backend-core/src/db/Replication.ts b/packages/backend-core/src/db/Replication.ts index b46f6072be..e0bd3c7a43 100644 --- a/packages/backend-core/src/db/Replication.ts +++ b/packages/backend-core/src/db/Replication.ts @@ -1,4 +1,5 @@ import { dangerousGetDB, closeDB } from "." +import { DocumentType } from "./constants" class Replication { source: any @@ -53,6 +54,14 @@ class Replication { return this.replication } + appReplicateOpts() { + return { + filter: (doc: any) => { + return doc._id !== DocumentType.APP_METADATA + }, + } + } + /** * Rollback the target DB back to the state of the source DB */ @@ -60,6 +69,7 @@ class Replication { await this.target.destroy() // Recreate the DB again this.target = dangerousGetDB(this.target.name) + // take the opportunity to remove deleted tombstones await this.replicate() } diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index 321ebd7f58..4926a60150 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -254,7 +254,16 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) { return false }) if (idsOnly) { - return appDbNames + const devAppIds = appDbNames.filter(appId => isDevAppID(appId)) + const prodAppIds = appDbNames.filter(appId => !isDevAppID(appId)) + switch (dev) { + case true: + return devAppIds + case false: + return prodAppIds + default: + return appDbNames + } } const appPromises = appDbNames.map((app: any) => // skip setup otherwise databases could be re-created diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 0348d921ab..04d09d2eb7 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -50,6 +50,7 @@ const env = { GLOBAL_BUCKET_NAME: process.env.GLOBAL_BUCKET_NAME || "global", GLOBAL_CLOUD_BUCKET_NAME: process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads", + PLUGIN_BUCKET_NAME: process.env.PLUGIN_BUCKET_NAME || "plugins", USE_COUCH: process.env.USE_COUCH || true, DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE, DEFAULT_LICENSE: process.env.DEFAULT_LICENSE, diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts index 74e79e7b95..d9dbe58264 100644 --- a/packages/backend-core/src/index.ts +++ b/packages/backend-core/src/index.ts @@ -18,6 +18,7 @@ import * as dbConstants from "./db/constants" import logging from "./logging" import pino from "./pino" import * as middleware from "./middleware" +import plugins from "./plugin" // mimic the outer package exports import * as db from "./pkg/db" @@ -56,6 +57,7 @@ const core = { errors, logging, roles, + plugins, ...pino, ...errorClasses, middleware, diff --git a/packages/backend-core/src/objectStore/index.ts b/packages/backend-core/src/objectStore/index.ts index 1b880ef7b2..a97aa8f65d 100644 --- a/packages/backend-core/src/objectStore/index.ts +++ b/packages/backend-core/src/objectStore/index.ts @@ -57,7 +57,11 @@ function publicPolicy(bucketName: any) { } } -const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS, ObjectStoreBuckets.GLOBAL] +const PUBLIC_BUCKETS = [ + ObjectStoreBuckets.APPS, + ObjectStoreBuckets.GLOBAL, + ObjectStoreBuckets.PLUGINS, +] /** * Gets a connection to the object store using the S3 SDK. @@ -172,6 +176,14 @@ export const streamUpload = async ( const objectStore = ObjectStore(bucketName) await makeSureBucketExists(objectStore, bucketName) + // Set content type for certain known extensions + if (filename?.endsWith(".js")) { + extra = { + ...extra, + ContentType: "application/javascript", + } + } + const params = { Bucket: sanitizeBucket(bucketName), Key: sanitizeKey(filename), @@ -295,9 +307,13 @@ export const uploadDirectory = async ( return files } -exports.downloadTarballDirect = async (url: string, path: string) => { +exports.downloadTarballDirect = async ( + url: string, + path: string, + headers = {} +) => { path = sanitizeKey(path) - const response = await fetch(url) + const response = await fetch(url, { headers }) if (!response.ok) { throw new Error(`unexpected response ${response.statusText}`) } diff --git a/packages/backend-core/src/objectStore/utils.js b/packages/backend-core/src/objectStore/utils.js index a243553df8..acc1b9904e 100644 --- a/packages/backend-core/src/objectStore/utils.js +++ b/packages/backend-core/src/objectStore/utils.js @@ -8,6 +8,7 @@ exports.ObjectStoreBuckets = { TEMPLATES: env.TEMPLATES_BUCKET_NAME, GLOBAL: env.GLOBAL_BUCKET_NAME, GLOBAL_CLOUD: env.GLOBAL_CLOUD_BUCKET_NAME, + PLUGINS: env.PLUGIN_BUCKET_NAME, } exports.budibaseTempDir = function () { diff --git a/packages/backend-core/src/plugin/index.ts b/packages/backend-core/src/plugin/index.ts new file mode 100644 index 0000000000..a6d1853007 --- /dev/null +++ b/packages/backend-core/src/plugin/index.ts @@ -0,0 +1,7 @@ +import * as utils from "./utils" + +const pkg = { + ...utils, +} + +export = pkg diff --git a/packages/backend-core/src/plugin/utils.js b/packages/backend-core/src/plugin/utils.js new file mode 100644 index 0000000000..020fb4484d --- /dev/null +++ b/packages/backend-core/src/plugin/utils.js @@ -0,0 +1,94 @@ +const { + DatasourceFieldType, + QueryType, + PluginType, +} = require("@budibase/types") +const joi = require("joi") + +const DATASOURCE_TYPES = [ + "Relational", + "Non-relational", + "Spreadsheet", + "Object store", + "Graph", + "API", +] + +function runJoi(validator, schema) { + const { error } = validator.validate(schema) + if (error) { + throw error + } +} + +function validateComponent(schema) { + const validator = joi.object({ + type: joi.string().allow("component").required(), + metadata: joi.object().unknown(true).required(), + hash: joi.string().optional(), + version: joi.string().optional(), + schema: joi + .object({ + name: joi.string().required(), + settings: joi.array().items(joi.object().unknown(true)).required(), + }) + .unknown(true), + }) + runJoi(validator, schema) +} + +function validateDatasource(schema) { + const fieldValidator = joi.object({ + type: joi + .string() + .allow(...Object.values(DatasourceFieldType)) + .required(), + required: joi.boolean().required(), + default: joi.any(), + display: joi.string(), + }) + + const queryValidator = joi + .object({ + type: joi.string().allow(...Object.values(QueryType)), + fields: joi.object().pattern(joi.string(), fieldValidator), + }) + .required() + + const validator = joi.object({ + type: joi.string().allow("datasource").required(), + metadata: joi.object().unknown(true).required(), + hash: joi.string().optional(), + version: joi.string().optional(), + schema: joi.object({ + docs: joi.string(), + friendlyName: joi.string().required(), + type: joi.string().allow(...DATASOURCE_TYPES), + description: joi.string().required(), + datasource: joi.object().pattern(joi.string(), fieldValidator).required(), + query: joi + .object({ + create: queryValidator, + read: queryValidator, + update: queryValidator, + delete: queryValidator, + }) + .unknown(true) + .required(), + }), + }) + runJoi(validator, schema) +} + +exports.validate = schema => { + switch (schema?.type) { + case PluginType.COMPONENT: + validateComponent(schema) + break + case PluginType.DATASOURCE: + validateDatasource(schema) + break + default: + throw new Error(`Unknown plugin type - check schema.json: ${schema.type}`) + } +} diff --git a/packages/backend-core/yarn.lock b/packages/backend-core/yarn.lock index 9f71691f44..22c17a9444 100644 --- a/packages/backend-core/yarn.lock +++ b/packages/backend-core/yarn.lock @@ -543,13 +543,13 @@ semver "^7.3.5" tar "^6.1.11" -"@shopify/jest-koa-mocks@3.1.5": - version "3.1.5" - resolved "https://registry.yarnpkg.com/@shopify/jest-koa-mocks/-/jest-koa-mocks-3.1.5.tgz#11f77ccfbcaf35cf5ee2c6108a286e61e6bea084" - integrity sha512-gQ3/7ELerv00TWO37AGFX5mT9CsFCS+3/UbKMuoIlKEU0QH2OX8BV9WBf/EKw7adCDNlxss0lqV6J8kf5pgr4A== +"@shopify/jest-koa-mocks@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@shopify/jest-koa-mocks/-/jest-koa-mocks-5.0.1.tgz#fba490b6b7985fbb571eb9974897d396a3642e94" + integrity sha512-4YskS9q8+TEHNoyopmuoy2XyhInyqeOl7CF5ShJs19sm6m0EA/jGGvgf/osv2PeTfuf42/L2G9CzWUSg49yTSg== dependencies: koa "^2.13.4" - node-mocks-http "^1.5.8" + node-mocks-http "^1.11.0" "@sideway/address@^4.1.3": version "4.1.4" @@ -3914,7 +3914,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-mocks-http@^1.5.8: +node-mocks-http@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.11.0.tgz#defc0febf6b935f08245397d47534a8de592996e" integrity sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw== diff --git a/packages/bbui/package.json b/packages/bbui/package.json index a563b54328..41ca994b87 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.3.4-alpha.2", + "version": "1.3.15-alpha.3", "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.3.4-alpha.2", + "@budibase/string-templates": "1.3.15-alpha.3", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", diff --git a/packages/bbui/src/Label/Label.svelte b/packages/bbui/src/Label/Label.svelte index 3395ab4179..6b3392ce2d 100644 --- a/packages/bbui/src/Label/Label.svelte +++ b/packages/bbui/src/Label/Label.svelte @@ -4,10 +4,15 @@ export let size = "M" export let tooltip = "" + export let muted - @@ -17,4 +22,8 @@ padding: 0; white-space: nowrap; } + + .muted { + opacity: 0.5; + } diff --git a/packages/bbui/src/Modal/ModalContent.svelte b/packages/bbui/src/Modal/ModalContent.svelte index 6d609d6f1b..25fac63ec8 100644 --- a/packages/bbui/src/Modal/ModalContent.svelte +++ b/packages/bbui/src/Modal/ModalContent.svelte @@ -24,7 +24,6 @@ export let secondaryAction = undefined export let secondaryButtonWarning = false export let dataCy = null - const { hide, cancel } = getContext(Context.Modal) let loading = false $: confirmDisabled = disabled || loading @@ -88,12 +87,11 @@
- {#if showCancelButton || showConfirmButton} + {#if showCancelButton || showConfirmButton || $$slots.footer}
- {#if showSecondaryButton && secondaryButtonText && secondaryAction}
+
+ + + + + + + + diff --git a/packages/builder/src/pages/builder/portal/manage/plugins/index.svelte b/packages/builder/src/pages/builder/portal/manage/plugins/index.svelte new file mode 100644 index 0000000000..5d73447710 --- /dev/null +++ b/packages/builder/src/pages/builder/portal/manage/plugins/index.svelte @@ -0,0 +1,95 @@ + + + + + Plugins + Add your own custom datasources and components + + + +
+
+ +
+
+
+
-