Merge branch 'develop' of github.com:Budibase/budibase into cheeks-lab-day-eject-blocks

This commit is contained in:
Andrew Kingston 2022-09-13 11:31:45 +01:00
commit 3283a0ab5a
195 changed files with 7711 additions and 5426 deletions

4
.gitignore vendored
View File

@ -102,4 +102,6 @@ packages/builder/cypress/reports
stats.html stats.html
# TypeScript cache # TypeScript cache
*.tsbuildinfo *.tsbuildinfo
budibase-component
budibase-datasource

View File

@ -124,11 +124,15 @@ spec:
value: {{ .Values.globals.tenantFeatureFlags | quote }} value: {{ .Values.globals.tenantFeatureFlags | quote }}
{{ if .Values.globals.bbAdminUserEmail }} {{ if .Values.globals.bbAdminUserEmail }}
- name: BB_ADMIN_USER_EMAIL - name: BB_ADMIN_USER_EMAIL
value: { { .Values.globals.bbAdminUserEmail | quote } } value: {{ .Values.globals.bbAdminUserEmail | quote }}
{{ end }} {{ end }}
{{ if .Values.globals.bbAdminUserPassword }} {{ if .Values.globals.bbAdminUserPassword }}
- name: BB_ADMIN_USER_PASSWORD - 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 }} {{ end }}
{{ if .Values.services.apps.nodeDebug }} {{ if .Values.services.apps.nodeDebug }}
- name: NODE_DEBUG - name: NODE_DEBUG

View File

@ -11,8 +11,8 @@
"dependencies": { "dependencies": {
"bulma": "^0.9.3", "bulma": "^0.9.3",
"next": "12.1.0", "next": "12.1.0",
"node-fetch": "^3.2.2", "node-fetch": "^3.2.10",
"node-sass": "^7.0.1", "sass": "^1.52.3",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-notifications-component": "^3.4.1" "react-notifications-component": "^3.4.1"
@ -24,4 +24,4 @@
"eslint-config-next": "12.1.0", "eslint-config-next": "12.1.0",
"typescript": "4.6.2" "typescript": "4.6.2"
} }
} }

View File

@ -2020,10 +2020,10 @@ node-domexception@^1.0.0:
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@^3.2.2: node-fetch@^3.2.10:
version "3.2.2" version "3.2.10"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.2.tgz#16d33fbe32ca7c6ca1ca8ba5dfea1dd885c59f04" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8"
integrity sha512-Cwhq1JFIoon15wcIkFzubVNFE5GvXGV82pKf4knXXjvGmn7RJKcypeuqcVNZMGDZsAFWyIRya/anwAJr7TWJ7w== integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
dependencies: dependencies:
data-uri-to-buffer "^4.0.0" data-uri-to-buffer "^4.0.0"
fetch-blob "^3.1.4" fetch-blob "^3.1.4"

View File

@ -22,4 +22,7 @@ BUDIBASE_ENVIRONMENT=PRODUCTION
# An admin user can be automatically created initially if these are set # An admin user can be automatically created initially if these are set
BB_ADMIN_USER_EMAIL= BB_ADMIN_USER_EMAIL=
BB_ADMIN_USER_PASSWORD= BB_ADMIN_USER_PASSWORD=
# A path that is watched for plugin bundles. Any bundles found are imported automatically/
PLUGINS_DIR=

View File

@ -25,9 +25,12 @@ services:
REDIS_PASSWORD: ${REDIS_PASSWORD} REDIS_PASSWORD: ${REDIS_PASSWORD}
BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL} BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL}
BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD} BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD}
PLUGINS_DIR: ${PLUGINS_DIR}
depends_on: depends_on:
- worker-service - worker-service
- redis-service - redis-service
# volumes:
# - /some/path/to/plugins:/plugins
worker-service: worker-service:
restart: unless-stopped restart: unless-stopped

View File

@ -22,4 +22,7 @@ BUDIBASE_ENVIRONMENT=PRODUCTION
# An admin user can be automatically created initially if these are set # An admin user can be automatically created initially if these are set
BB_ADMIN_USER_EMAIL= BB_ADMIN_USER_EMAIL=
BB_ADMIN_USER_PASSWORD= BB_ADMIN_USER_PASSWORD=
# A path that is watched for plugin bundles. Any bundles found are imported automatically/
PLUGINS_DIR=

View File

@ -65,10 +65,6 @@ http {
proxy_pass http://{{ address }}:4001; proxy_pass http://{{ address }}:4001;
} }
location /preview {
proxy_pass http://{{ address }}:4001;
}
location /builder { location /builder {
proxy_pass http://{{ address }}:3000; proxy_pass http://{{ address }}:3000;
rewrite ^/builder(.*)$ /builder/$1 break; rewrite ^/builder(.*)$ /builder/$1 break;
@ -84,6 +80,20 @@ http {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 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 / { location / {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -88,10 +88,6 @@ http {
proxy_pass http://$apps:4002; proxy_pass http://$apps:4002;
} }
location /preview {
proxy_pass http://$apps:4002;
}
location = / { location = / {
proxy_pass http://$apps:4002; proxy_pass http://$apps:4002;
} }
@ -162,6 +158,15 @@ http {
rewrite ^/db/(.*)$ /$1 break; 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 / { location / {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -4,9 +4,9 @@ echo ${TARGETBUILD} > /buildtarget.txt
if [[ "${TARGETBUILD}" = "aas" ]]; then if [[ "${TARGETBUILD}" = "aas" ]]; then
# Azure AppService uses /home for persisent data & SSH on port 2222 # Azure AppService uses /home for persisent data & SSH on port 2222
DATA_DIR=/home DATA_DIR=/home
mkdir -p $DATA_DIR/{search,minio,couchdb} mkdir -p $DATA_DIR/{search,minio,couch}
mkdir -p $DATA_DIR/couchdb/{dbs,views} mkdir -p $DATA_DIR/couch/{dbs,views}
chown -R couchdb:couchdb $DATA_DIR/couchdb/ chown -R couchdb:couchdb $DATA_DIR/couch/
apt update apt update
apt-get install -y openssh-server apt-get install -y openssh-server
sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config
@ -16,5 +16,4 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then
else else
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
fi fi

View File

@ -1,5 +1,5 @@
; CouchDB Configuration Settings ; CouchDB Configuration Settings
[couchdb] [couchdb]
database_dir = DATA_DIR/couchdb/dbs database_dir = DATA_DIR/couch/dbs
view_index_dir = DATA_DIR/couchdb/views view_index_dir = DATA_DIR/couch/views

View File

@ -66,6 +66,15 @@ server {
rewrite ^/db/(.*)$ /$1 break; 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 / { location / {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -36,10 +36,10 @@ fi
export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984 export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984
# make these directories in runner, incase of mount # 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}/minio
mkdir -p ${DATA_DIR}/search mkdir -p ${DATA_DIR}/search
chown -R couchdb:couchdb ${DATA_DIR}/couchdb chown -R couchdb:couchdb ${DATA_DIR}/couch
redis-server --requirepass $REDIS_PASSWORD & redis-server --requirepass $REDIS_PASSWORD &
/opt/clouseau/bin/clouseau & /opt/clouseau/bin/clouseau &
/minio/minio server ${DATA_DIR}/minio & /minio/minio server ${DATA_DIR}/minio &

View File

@ -1,5 +1,5 @@
{ {
"version": "1.3.4-alpha.2", "version": "1.3.15-alpha.3",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "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", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -20,7 +20,8 @@
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
}, },
"dependencies": { "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", "@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0", "aws-sdk": "2.1030.0",
"bcrypt": "5.0.1", "bcrypt": "5.0.1",
@ -60,7 +61,6 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@shopify/jest-koa-mocks": "3.1.5",
"@types/jest": "27.5.1", "@types/jest": "27.5.1",
"@types/koa": "2.0.52", "@types/koa": "2.0.52",
"@types/lodash": "4.14.180", "@types/lodash": "4.14.180",

View File

@ -0,0 +1,3 @@
module.exports = {
...require("./src/plugin"),
}

View File

@ -1,4 +1,5 @@
import { dangerousGetDB, closeDB } from "." import { dangerousGetDB, closeDB } from "."
import { DocumentType } from "./constants"
class Replication { class Replication {
source: any source: any
@ -53,6 +54,14 @@ class Replication {
return this.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 * Rollback the target DB back to the state of the source DB
*/ */
@ -60,6 +69,7 @@ class Replication {
await this.target.destroy() await this.target.destroy()
// Recreate the DB again // Recreate the DB again
this.target = dangerousGetDB(this.target.name) this.target = dangerousGetDB(this.target.name)
// take the opportunity to remove deleted tombstones
await this.replicate() await this.replicate()
} }

View File

@ -254,7 +254,16 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) {
return false return false
}) })
if (idsOnly) { 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) => const appPromises = appDbNames.map((app: any) =>
// skip setup otherwise databases could be re-created // skip setup otherwise databases could be re-created

View File

@ -50,6 +50,7 @@ const env = {
GLOBAL_BUCKET_NAME: process.env.GLOBAL_BUCKET_NAME || "global", GLOBAL_BUCKET_NAME: process.env.GLOBAL_BUCKET_NAME || "global",
GLOBAL_CLOUD_BUCKET_NAME: GLOBAL_CLOUD_BUCKET_NAME:
process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads", 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, USE_COUCH: process.env.USE_COUCH || true,
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE, DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE, DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,

View File

@ -18,6 +18,7 @@ import * as dbConstants from "./db/constants"
import logging from "./logging" import logging from "./logging"
import pino from "./pino" import pino from "./pino"
import * as middleware from "./middleware" import * as middleware from "./middleware"
import plugins from "./plugin"
// mimic the outer package exports // mimic the outer package exports
import * as db from "./pkg/db" import * as db from "./pkg/db"
@ -56,6 +57,7 @@ const core = {
errors, errors,
logging, logging,
roles, roles,
plugins,
...pino, ...pino,
...errorClasses, ...errorClasses,
middleware, middleware,

View File

@ -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. * Gets a connection to the object store using the S3 SDK.
@ -172,6 +176,14 @@ export const streamUpload = async (
const objectStore = ObjectStore(bucketName) const objectStore = ObjectStore(bucketName)
await makeSureBucketExists(objectStore, bucketName) await makeSureBucketExists(objectStore, bucketName)
// Set content type for certain known extensions
if (filename?.endsWith(".js")) {
extra = {
...extra,
ContentType: "application/javascript",
}
}
const params = { const params = {
Bucket: sanitizeBucket(bucketName), Bucket: sanitizeBucket(bucketName),
Key: sanitizeKey(filename), Key: sanitizeKey(filename),
@ -295,9 +307,13 @@ export const uploadDirectory = async (
return files return files
} }
exports.downloadTarballDirect = async (url: string, path: string) => { exports.downloadTarballDirect = async (
url: string,
path: string,
headers = {}
) => {
path = sanitizeKey(path) path = sanitizeKey(path)
const response = await fetch(url) const response = await fetch(url, { headers })
if (!response.ok) { if (!response.ok) {
throw new Error(`unexpected response ${response.statusText}`) throw new Error(`unexpected response ${response.statusText}`)
} }

View File

@ -8,6 +8,7 @@ exports.ObjectStoreBuckets = {
TEMPLATES: env.TEMPLATES_BUCKET_NAME, TEMPLATES: env.TEMPLATES_BUCKET_NAME,
GLOBAL: env.GLOBAL_BUCKET_NAME, GLOBAL: env.GLOBAL_BUCKET_NAME,
GLOBAL_CLOUD: env.GLOBAL_CLOUD_BUCKET_NAME, GLOBAL_CLOUD: env.GLOBAL_CLOUD_BUCKET_NAME,
PLUGINS: env.PLUGIN_BUCKET_NAME,
} }
exports.budibaseTempDir = function () { exports.budibaseTempDir = function () {

View File

@ -0,0 +1,7 @@
import * as utils from "./utils"
const pkg = {
...utils,
}
export = pkg

View File

@ -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}`)
}
}

View File

@ -543,13 +543,13 @@
semver "^7.3.5" semver "^7.3.5"
tar "^6.1.11" tar "^6.1.11"
"@shopify/jest-koa-mocks@3.1.5": "@shopify/jest-koa-mocks@5.0.1":
version "3.1.5" version "5.0.1"
resolved "https://registry.yarnpkg.com/@shopify/jest-koa-mocks/-/jest-koa-mocks-3.1.5.tgz#11f77ccfbcaf35cf5ee2c6108a286e61e6bea084" resolved "https://registry.yarnpkg.com/@shopify/jest-koa-mocks/-/jest-koa-mocks-5.0.1.tgz#fba490b6b7985fbb571eb9974897d396a3642e94"
integrity sha512-gQ3/7ELerv00TWO37AGFX5mT9CsFCS+3/UbKMuoIlKEU0QH2OX8BV9WBf/EKw7adCDNlxss0lqV6J8kf5pgr4A== integrity sha512-4YskS9q8+TEHNoyopmuoy2XyhInyqeOl7CF5ShJs19sm6m0EA/jGGvgf/osv2PeTfuf42/L2G9CzWUSg49yTSg==
dependencies: dependencies:
koa "^2.13.4" koa "^2.13.4"
node-mocks-http "^1.5.8" node-mocks-http "^1.11.0"
"@sideway/address@^4.1.3": "@sideway/address@^4.1.3":
version "4.1.4" 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" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
node-mocks-http@^1.5.8: node-mocks-http@^1.11.0:
version "1.11.0" version "1.11.0"
resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.11.0.tgz#defc0febf6b935f08245397d47534a8de592996e" resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.11.0.tgz#defc0febf6b935f08245397d47534a8de592996e"
integrity sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw== integrity sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw==

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "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", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1", "@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/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2", "@spectrum-css/avatar": "^3.0.2",

View File

@ -4,10 +4,15 @@
export let size = "M" export let size = "M"
export let tooltip = "" export let tooltip = ""
export let muted
</script> </script>
<TooltipWrapper {tooltip} {size}> <TooltipWrapper {tooltip} {size}>
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}> <label
class:muted
for=""
class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}
>
<slot /> <slot />
</label> </label>
</TooltipWrapper> </TooltipWrapper>
@ -17,4 +22,8 @@
padding: 0; padding: 0;
white-space: nowrap; white-space: nowrap;
} }
.muted {
opacity: 0.5;
}
</style> </style>

View File

@ -24,7 +24,6 @@
export let secondaryAction = undefined export let secondaryAction = undefined
export let secondaryButtonWarning = false export let secondaryButtonWarning = false
export let dataCy = null export let dataCy = null
const { hide, cancel } = getContext(Context.Modal) const { hide, cancel } = getContext(Context.Modal)
let loading = false let loading = false
$: confirmDisabled = disabled || loading $: confirmDisabled = disabled || loading
@ -88,12 +87,11 @@
<section class="spectrum-Dialog-content content-grid"> <section class="spectrum-Dialog-content content-grid">
<slot /> <slot />
</section> </section>
{#if showCancelButton || showConfirmButton} {#if showCancelButton || showConfirmButton || $$slots.footer}
<div <div
class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter" class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter"
> >
<slot name="footer" /> <slot name="footer" />
{#if showSecondaryButton && secondaryButtonText && secondaryAction} {#if showSecondaryButton && secondaryButtonText && secondaryAction}
<div class="secondary-action"> <div class="secondary-action">
<Button <Button

View File

@ -10,6 +10,7 @@
export let noHorizPadding = false export let noHorizPadding = false
export let quiet = false export let quiet = false
export let emphasized = false export let emphasized = false
export let onTop = false
export let size = "M" export let size = "M"
let thisSelected = undefined let thisSelected = undefined
@ -75,6 +76,7 @@
bind:this={container} bind:this={container}
class:spectrum-Tabs--quiet={quiet} class:spectrum-Tabs--quiet={quiet}
class:noHorizPadding class:noHorizPadding
class:onTop
class:spectrum-Tabs--vertical={vertical} class:spectrum-Tabs--vertical={vertical}
class:spectrum-Tabs--horizontal={!vertical} class:spectrum-Tabs--horizontal={!vertical}
class="spectrum-Tabs spectrum-Tabs--size{size}" class="spectrum-Tabs spectrum-Tabs--size{size}"
@ -122,4 +124,7 @@
.noPadding { .noPadding {
margin: 0; margin: 0;
} }
.onTop {
z-index: 100;
}
</style> </style>

View File

@ -74,11 +74,11 @@ filterTests(["smoke", "all"], () => {
.contains("Update role") .contains("Update role")
.click({ force: true }) .click({ force: true })
}) })
cy.reload({ timeout: 5000 }) cy.reload()
cy.wait(1000) cy.wait(1000)
} }
// Confirm roles exist within Configure roles table // Confirm roles exist within Configure roles table
cy.get(interact.SPECTRUM_TABLE, { timeout: 2000 }) cy.get(interact.SPECTRUM_TABLE, { timeout: 20000 })
.eq(0) .eq(0)
.within(assginedRoles => { .within(assginedRoles => {
expect(assginedRoles).to.contain("Admin") expect(assginedRoles).to.contain("Admin")
@ -180,7 +180,7 @@ filterTests(["smoke", "all"], () => {
cy.reload() cy.reload()
// Confirm details have been saved // Confirm details have been saved
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { cy.get(interact.FIELD, { timeout: 20000 }).eq(1).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb") cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb")
}) })
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => { cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {

View File

@ -1,7 +1,7 @@
import filterTests from "../../support/filterTests" import filterTests from "../../support/filterTests"
filterTests(['all'], () => { filterTests(['all'], () => {
context("Datasource Wizard", () => { xcontext("Datasource Wizard", () => {
if (Cypress.env("TEST_ENV")) { if (Cypress.env("TEST_ENV")) {
before(() => { before(() => {
cy.login() cy.login()

View File

@ -1,7 +1,7 @@
import filterTests from "../../support/filterTests" import filterTests from "../../support/filterTests"
filterTests(["all"], () => { filterTests(["all"], () => {
context("Oracle Datasource Testing", () => { xcontext("Oracle Datasource Testing", () => {
if (Cypress.env("TEST_ENV")) { if (Cypress.env("TEST_ENV")) {
before(() => { before(() => {
cy.login() cy.login()

View File

@ -162,7 +162,7 @@ filterTests(["all"], () => {
switchSchema("randomText") switchSchema("randomText")
// No tables displayed // No tables displayed
cy.get(".spectrum-Body", { timeout: 20000 }).eq(2).should("contain", "No tables found") cy.get(".spectrum-Body", { timeout: 10000 }).eq(2, { timeout: 10000 }).should("contain", "No tables found")
// Previously created query should be visible // Previously created query should be visible
cy.get(".spectrum-Table").should("contain", queryName) cy.get(".spectrum-Table").should("contain", queryName)

View File

@ -14,7 +14,7 @@ filterTests(["smoke", "all"], () => {
// Select REST data source // Select REST data source
cy.selectExternalDatasource(datasource) cy.selectExternalDatasource(datasource)
// Enter incorrect api & attempt to send query // Enter incorrect api & attempt to send query
cy.get(".spectrum-Button", { timeout: 500 }).contains("Add query").click({ force: true }) cy.get(".query-buttons", { timeout: 1000 }).contains("Add query").click({ force: true })
cy.intercept("**/preview").as("queryError") cy.intercept("**/preview").as("queryError")
cy.get("input").clear().type("random text") cy.get("input").clear().type("random text")
cy.get(".spectrum-Button").contains("Send").click({ force: true }) cy.get(".spectrum-Button").contains("Send").click({ force: true })

View File

@ -457,8 +457,8 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
cy.get(".spectrum-ButtonGroup").contains("Create").click() cy.get(".spectrum-ButtonGroup").contains("Create").click()
}) })
// Ensure modal has closed and table is created // Ensure modal has closed and table is created
cy.get(".spectrum-Modal").should("not.exist") cy.get(".spectrum-Modal", { timeout: 2000 }).should("not.exist")
cy.get(".spectrum-Tabs-content", { timeout: 1000 }).should( cy.get(".spectrum-Tabs-content", { timeout: 2000 }).should(
"contain", "contain",
tableName tableName
) )
@ -637,30 +637,32 @@ Cypress.Commands.add(
(datasourceNames, accessLevelLabel) => { (datasourceNames, accessLevelLabel) => {
cy.contains("Design").click() cy.contains("Design").click()
cy.get(".spectrum-Button").contains("Add screen").click({ force: true }) cy.get(".spectrum-Button").contains("Add screen").click({ force: true })
cy.get(".spectrum-Modal").within(() => { cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".item").contains("Autogenerated screens").click() cy.get("[data-cy='autogenerated-screens']").click()
cy.intercept("**/api/datasources").as("autoScreens")
cy.get(".spectrum-Button").contains("Continue").click({ force: true }) cy.get(".spectrum-Button").contains("Continue").click({ force: true })
cy.wait("@autoScreens")
cy.wait(5000)
}) })
cy.get(".spectrum-Modal [data-cy='data-source-modal']", { cy.get("[data-cy='autogenerated-screens']").should("not.exist")
timeout: 500, cy.get("[data-cy='data-source-modal']", { timeout: 10000 }).within(() => {
}).within(() => {
for (let i = 0; i < datasourceNames.length; i++) { for (let i = 0; i < datasourceNames.length; i++) {
cy.wait(500) cy.get(".data-source-entry")
cy.get(".data-source-entry").contains(datasourceNames[i]).click() .contains(datasourceNames[i], { timeout: 20000 })
//Ensure the check mark is visible .click({ force: true })
// Ensure the check mark is visible
cy.get(".data-source-entry") cy.get(".data-source-entry")
.contains(datasourceNames[i]) .contains(datasourceNames[i])
.get(".data-source-check") .get(".data-source-check", { timeout: 20000 })
.should("exist") .should("exist")
} }
cy.get(".spectrum-Button").contains("Confirm").click({ force: true }) cy.get(".spectrum-Button").contains("Confirm").click({ force: true })
}) })
cy.get(".spectrum-Modal").within(() => { cy.get(".spectrum-Modal", { timeout: 10000 }).within(() => {
if (accessLevelLabel) { if (accessLevelLabel) {
cy.get(".spectrum-Picker-label").click() cy.get(".spectrum-Picker-label", { timeout: 10000 }).click()
cy.wait(500)
cy.contains(accessLevelLabel).click() cy.contains(accessLevelLabel).click()
} }
cy.get(".spectrum-Button").contains("Done").click({ force: true }) cy.get(".spectrum-Button").contains("Done").click({ force: true })
@ -915,8 +917,9 @@ Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
Cypress.Commands.add("closeModal", () => { Cypress.Commands.add("closeModal", () => {
cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => { cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => {
cy.get(".close-icon").click() cy.get(".close-icon").click()
cy.wait(1000) // Wait for modal to close
}) })
// Confirm modal has closed
cy.get(".spectrum-Modal", { timeout: 10000 }).should("not.exist")
}) })
Cypress.Commands.add("expandBudibaseConnection", () => { Cypress.Commands.add("expandBudibaseConnection", () => {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "1.3.4-alpha.2", "version": "1.3.15-alpha.3",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -69,10 +69,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.4-alpha.2", "@budibase/bbui": "1.3.15-alpha.3",
"@budibase/client": "1.3.4-alpha.2", "@budibase/client": "1.3.15-alpha.3",
"@budibase/frontend-core": "1.3.4-alpha.2", "@budibase/frontend-core": "1.3.15-alpha.3",
"@budibase/string-templates": "1.3.4-alpha.2", "@budibase/string-templates": "1.3.15-alpha.3",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",
@ -96,7 +96,7 @@
"@babel/runtime": "^7.13.10", "@babel/runtime": "^7.13.10",
"@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-replace": "^2.4.2",
"@roxi/routify": "2.18.5", "@roxi/routify": "2.18.5",
"@sveltejs/vite-plugin-svelte": "1.0.0-next.19", "@sveltejs/vite-plugin-svelte": "1.0.1",
"@testing-library/jest-dom": "^5.11.10", "@testing-library/jest-dom": "^5.11.10",
"@testing-library/svelte": "^3.0.0", "@testing-library/svelte": "^3.0.0",
"babel-jest": "^26.6.3", "babel-jest": "^26.6.3",
@ -118,7 +118,7 @@
"ts-node": "^10.4.0", "ts-node": "^10.4.0",
"tsconfig-paths": "4.0.0", "tsconfig-paths": "4.0.0",
"typescript": "^4.5.5", "typescript": "^4.5.5",
"vite": "^2.1.5" "vite": "^3.0.8"
}, },
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072" "gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
} }

View File

@ -9,7 +9,7 @@ function prepareData(config) {
ds => ds.source === config.type ds => ds.source === config.type
).length ).length
let baseName = IntegrationNames[config.type] let baseName = IntegrationNames[config.type] || config.name
let name = let name =
existingTypeCount === 0 ? baseName : `${baseName}-${existingTypeCount + 1}` existingTypeCount === 0 ? baseName : `${baseName}-${existingTypeCount + 1}`

View File

@ -90,13 +90,21 @@ export const getFrontendStore = () => {
// Fetch component definitions. // Fetch component definitions.
// Allow errors to propagate. // Allow errors to propagate.
let components = await API.fetchComponentLibDefinitions(application.appId) const components = await API.fetchComponentLibDefinitions(
application.appId
)
// Filter out custom component keys so we can flag them
const customComponents = Object.keys(components).filter(name =>
name.startsWith("plugin/")
)
// Reset store state // Reset store state
store.update(state => ({ store.update(state => ({
...state, ...state,
libraries: application.componentLibraries, libraries: application.componentLibraries,
components, components,
customComponents,
clientFeatures: { clientFeatures: {
...INITIAL_FRONTEND_STATE.clientFeatures, ...INITIAL_FRONTEND_STATE.clientFeatures,
...components.features, ...components.features,
@ -116,6 +124,7 @@ export const getFrontendStore = () => {
version: application.version, version: application.version,
revertableVersion: application.revertableVersion, revertableVersion: application.revertableVersion,
navigation: application.navigation || {}, navigation: application.navigation || {},
usedPlugins: application.usedPlugins || [],
})) }))
// Initialise backend stores // Initialise backend stores
@ -189,9 +198,18 @@ export const getFrontendStore = () => {
}) })
}, },
save: async screen => { save: async screen => {
const state = get(store)
const creatingNewScreen = screen._id === undefined const creatingNewScreen = screen._id === undefined
const savedScreen = await API.saveScreen(screen) const savedScreen = await API.saveScreen(screen)
const routesResponse = await API.fetchAppRoutes() const routesResponse = await API.fetchAppRoutes()
let usedPlugins = state.usedPlugins
// If plugins changed we need to fetch the latest app metadata
if (savedScreen.pluginAdded) {
const { application } = await API.fetchAppPackage(state.appId)
usedPlugins = application.usedPlugins || []
}
store.update(state => { store.update(state => {
// Update screen object // Update screen object
const idx = state.screens.findIndex(x => x._id === savedScreen._id) const idx = state.screens.findIndex(x => x._id === savedScreen._id)
@ -210,6 +228,9 @@ export const getFrontendStore = () => {
// Update routes // Update routes
state.routes = routesResponse.routes state.routes = routesResponse.routes
// Update used plugins
state.usedPlugins = usedPlugins
return state return state
}) })
return savedScreen return savedScreen
@ -378,9 +399,6 @@ export const getFrontendStore = () => {
if (!componentName) { if (!componentName) {
return null return null
} }
if (!componentName.startsWith("@budibase")) {
componentName = `@budibase/standard-components/${componentName}`
}
return get(store).components[componentName] return get(store).components[componentName]
}, },
createInstance: (componentName, presetProps) => { createInstance: (componentName, presetProps) => {

View File

@ -1,6 +1,7 @@
<script> <script>
import { Button, Select, Input, Label } from "@budibase/bbui" import { Button, Select, Input, Label } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { onMount, createEventDispatcher } from "svelte"
import { flags } from "stores/backend"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let value export let value
@ -29,11 +30,16 @@
label: "Every Night at Midnight", label: "Every Night at Midnight",
value: "0 0 * * *", value: "0 0 * * *",
}, },
{
label: "Every Budibase Reboot",
value: "@reboot",
},
] ]
onMount(() => {
if (!$flags.cloud) {
CRON_EXPRESSIONS.push({
label: "Every Budibase Reboot",
value: "@reboot",
})
}
})
</script> </script>
<div class="block-field"> <div class="block-field">

View File

@ -13,7 +13,7 @@
customQueryIconColor, customQueryIconColor,
customQueryText, customQueryText,
} from "helpers/data/utils" } from "helpers/data/utils"
import ICONS from "./icons" import { getIcon } from "./icons"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
let openDataSources = [] let openDataSources = []
@ -124,7 +124,7 @@
> >
<div class="datasource-icon" slot="icon"> <div class="datasource-icon" slot="icon">
<svelte:component <svelte:component
this={ICONS[datasource.source]} this={getIcon(datasource.source, datasource.schema)}
height="18" height="18"
width="18" width="18"
/> />

View File

@ -8,6 +8,7 @@
notifications, notifications,
Modal, Modal,
Table, Table,
Toggle,
} from "@budibase/bbui" } from "@budibase/bbui"
import { datasources, integrations, tables } from "stores/backend" import { datasources, integrations, tables } from "stores/backend"
import CreateEditRelationship from "components/backend/Datasources/CreateEditRelationship.svelte" import CreateEditRelationship from "components/backend/Datasources/CreateEditRelationship.svelte"
@ -15,6 +16,7 @@
import ArrayRenderer from "components/common/renderers/ArrayRenderer.svelte" import ArrayRenderer from "components/common/renderers/ArrayRenderer.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import ValuesList from "components/common/ValuesList.svelte"
export let datasource export let datasource
export let save export let save
@ -31,6 +33,8 @@
let createExternalTableModal let createExternalTableModal
let selectedFromRelationship, selectedToRelationship let selectedFromRelationship, selectedToRelationship
let confirmDialog let confirmDialog
let specificTables = null
let requireSpecificTables = false
$: integration = datasource && $integrations[datasource.source] $: integration = datasource && $integrations[datasource.source]
$: plusTables = datasource?.plus $: plusTables = datasource?.plus
@ -87,7 +91,7 @@
async function updateDatasourceSchema() { async function updateDatasourceSchema() {
try { try {
await datasources.updateSchema(datasource) await datasources.updateSchema(datasource, specificTables)
notifications.success(`Datasource ${name} tables updated successfully.`) notifications.success(`Datasource ${name} tables updated successfully.`)
await tables.fetch() await tables.fetch()
} catch (error) { } catch (error) {
@ -150,6 +154,19 @@
warning={false} warning={false}
title="Confirm table fetch" title="Confirm table fetch"
> >
<Toggle
bind:value={requireSpecificTables}
on:change={e => {
requireSpecificTables = e.detail
specificTables = null
}}
thin
text="Fetch listed tables only (one per line)"
/>
{#if requireSpecificTables}
<ValuesList label="" bind:values={specificTables} />
{/if}
<br />
<Body> <Body>
If you have fetched tables from this database before, this action may If you have fetched tables from this database before, this action may
overwrite any changes you made after your initial fetch. overwrite any changes you made after your initial fetch.

View File

@ -0,0 +1,65 @@
<script>
import { createEventDispatcher } from "svelte"
import { Heading, Detail } from "@budibase/bbui"
import { getIcon } from "../icons"
export let integration
export let integrationType
export let schema
let dispatcher = createEventDispatcher()
</script>
<div
class:selected={integration.type === integrationType}
on:click={() => dispatcher("selected", integrationType)}
class="item hoverable"
>
<div class="item-body" class:with-type={!!schema.type}>
<svelte:component
this={getIcon(integrationType, schema)}
height="20"
width="20"
/>
<div class="text">
<Heading size="XXS">{schema.friendlyName}</Heading>
{#if schema.type}
<Detail size="S">{schema.type || ""}</Detail>
{/if}
</div>
</div>
</div>
<style>
.item {
cursor: pointer;
display: grid;
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
padding: var(--spectrum-alias-item-padding-s)
var(--spectrum-alias-item-padding-m);
background: var(--spectrum-alias-background-color-secondary);
transition: background 0.13s ease-out;
border: solid var(--spectrum-alias-border-color);
border-radius: 5px;
box-sizing: border-box;
border-width: 2px;
}
.item:hover,
.item.selected {
background: var(--spectrum-alias-background-color-tertiary);
}
.item-body {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
}
.item-body.with-type {
align-items: flex-start;
}
.item-body.with-type :global(svg) {
margin-top: 4px;
}
</style>

View File

@ -0,0 +1,52 @@
<script>
export let width = "100"
export let height = "100"
</script>
<svg
{width}
{height}
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 18.43 17.62"
xml:space="preserve"
>
<style type="text/css">
.custom-st0 {
fill: var(
--spectrum-heading-xxs-text-color,
var(--spectrum-alias-heading-text-color)
);
}
</style>
<ellipse
id="Ellipse_9514"
class="custom-st0"
cx="9.14"
cy="2.88"
rx="8"
ry="2.5"
/>
<path
id="Path_8355"
class="custom-st0"
d="M7.64,12.88c0-0.48,0.06-0.95,0.17-1.41c-2.53-0.16-5.92-0.76-6.67-1.95v4.36
c0,1.34,3.39,2.43,7.63,2.49C8.03,15.36,7.63,14.13,7.64,12.88z"
/>
<path
id="Path_8356"
class="custom-st0"
d="M13.64,6.88c1.25,0,2.47,0.39,3.48,1.12c0.01-0.04,0.02-0.08,0.02-0.12V4.51
c-1.22,1.55-5.53,2-8,2s-7.11-0.58-8-2v3.36c0,1.28,3.09,2.33,7.06,2.48C9.18,8.24,11.3,6.88,13.64,6.88z"
/>
<path
id="Path_8353"
class="custom-st0"
d="M10.32,14.42c-0.56-0.56-0.56-1.47,0-2.03l1.24-1.24l-0.01-0.01c-0.25-0.25-0.25-0.65,0-0.9l0,0
L12,9.79c0.25-0.25,0.65-0.25,0.9,0c0,0,0,0,0,0l0,0l0.45,0.45l1.53-1.53c0.09-0.09,0.24-0.09,0.34,0l0.34,0.34
c0.09,0.09,0.09,0.24,0,0.34l-1.53,1.53l1.35,1.35l1.53-1.53c0.09-0.09,0.24-0.09,0.34,0l0.34,0.34c0.09,0.09,0.09,0.24,0,0.34
l-1.53,1.53l0.45,0.45c0.25,0.25,0.25,0.65,0,0.9l0,0l-0.45,0.45c-0.25,0.25-0.65,0.25-0.9,0l0,0l-0.01-0.01l-1.24,1.24
c-0.56,0.56-1.47,0.56-2.03,0l0,0L10.32,14.42z"
/>
</svg>

View File

@ -15,8 +15,9 @@ import GoogleSheets from "./GoogleSheets.svelte"
import Firebase from "./Firebase.svelte" import Firebase from "./Firebase.svelte"
import Redis from "./Redis.svelte" import Redis from "./Redis.svelte"
import Snowflake from "./Snowflake.svelte" import Snowflake from "./Snowflake.svelte"
import Custom from "./Custom.svelte"
export default { const ICONS = {
BUDIBASE: Budibase, BUDIBASE: Budibase,
POSTGRES: Postgres, POSTGRES: Postgres,
DYNAMODB: DynamoDB, DYNAMODB: DynamoDB,
@ -34,4 +35,15 @@ export default {
FIRESTORE: Firebase, FIRESTORE: Firebase,
REDIS: Redis, REDIS: Redis,
SNOWFLAKE: Snowflake, SNOWFLAKE: Snowflake,
CUSTOM: Custom,
}
export default ICONS
export function getIcon(integrationType, schema) {
if (schema?.custom || !ICONS[integrationType]) {
return ICONS.CUSTOM
} else {
return ICONS[integrationType]
}
} }

View File

@ -18,6 +18,7 @@
import { createRestDatasource } from "builderStore/datasource" import { createRestDatasource } from "builderStore/datasource"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import ImportRestQueriesModal from "./ImportRestQueriesModal.svelte" import ImportRestQueriesModal from "./ImportRestQueriesModal.svelte"
import DatasourceCard from "../_components/DatasourceCard.svelte"
export let modal export let modal
let integrations = {} let integrations = {}
@ -27,6 +28,9 @@
let importModal let importModal
$: showImportButton = false $: showImportButton = false
$: customIntegrations = Object.entries(integrations).filter(
entry => entry[1].custom
)
checkShowImport() checkShowImport()
@ -49,6 +53,9 @@
schema: selected.datasource, schema: selected.datasource,
auth: selected.auth, auth: selected.auth,
} }
if (selected.friendlyName) {
integration.name = selected.friendlyName
}
checkShowImport() checkShowImport()
} }
@ -150,36 +157,39 @@
<Layout noPadding gap="XS"> <Layout noPadding gap="XS">
<Body size="S">Connect to an external data source</Body> <Body size="S">Connect to an external data source</Body>
<div class="item-list"> <div class="item-list">
{#each Object.entries(integrations).filter(([key]) => key !== IntegrationTypes.INTERNAL) as [integrationType, schema]} {#each Object.entries(integrations).filter(([key, val]) => key !== IntegrationTypes.INTERNAL && !val.custom) as [integrationType, schema]}
<div <DatasourceCard
class:selected={integration.type === integrationType} on:selected={evt => selectIntegration(evt.detail)}
on:click={() => selectIntegration(integrationType)} {schema}
class="item hoverable" bind:integrationType
> {integration}
<div class="item-body" class:with-type={!!schema.type}> />
<svelte:component
this={ICONS[integrationType]}
height="20"
width="20"
/>
<div class="text">
<Heading size="XXS">{schema.friendlyName}</Heading>
{#if schema.type}
<Detail size="S">{schema.type || ""}</Detail>
{/if}
</div>
</div>
</div>
{/each} {/each}
</div> </div>
</Layout> </Layout>
{#if customIntegrations.length > 0}
<Layout noPadding gap="XS">
<Body size="S">Custom data source</Body>
<div class="item-list">
{#each customIntegrations as [integrationType, schema]}
<DatasourceCard
on:selected={evt => selectIntegration(evt.detail)}
{schema}
bind:integrationType
{integration}
/>
{/each}
</div>
</Layout>
{/if}
</ModalContent> </ModalContent>
</Modal> </Modal>
<style> <style>
.item-list { .item-list {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(2, minmax(150px, 1fr));
grid-gap: var(--spectrum-alias-grid-baseline); grid-gap: var(--spectrum-alias-grid-baseline);
} }

View File

@ -14,8 +14,14 @@
let datasource = cloneDeep(integration) let datasource = cloneDeep(integration)
let skipFetch = false let skipFetch = false
$: name =
IntegrationNames[datasource.type] || datasource.name || datasource.type
async function saveDatasource() { async function saveDatasource() {
try { try {
if (!datasource.name) {
datasource.name = name
}
const resp = await save(datasource, skipFetch) const resp = await save(datasource, skipFetch)
$goto(`./datasource/${resp._id}`) $goto(`./datasource/${resp._id}`)
notifications.success(`Datasource updated successfully.`) notifications.success(`Datasource updated successfully.`)
@ -32,7 +38,7 @@
</script> </script>
<ModalContent <ModalContent
title={`Connect to ${IntegrationNames[datasource.type]}`} title={`Connect to ${name}`}
onConfirm={() => saveDatasource()} onConfirm={() => saveDatasource()}
onCancel={() => modal.show()} onCancel={() => modal.show()}
confirmText={datasource.plus confirmText={datasource.plus

View File

@ -23,6 +23,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let bindingDrawer let bindingDrawer
let valid = true let valid = true
let currentVal = value
$: readableValue = runtimeToReadableBinding(bindings, value) $: readableValue = runtimeToReadableBinding(bindings, value)
$: tempValue = readableValue $: tempValue = readableValue
@ -30,11 +31,17 @@
const saveBinding = () => { const saveBinding = () => {
onChange(tempValue) onChange(tempValue)
onBlur()
bindingDrawer.hide() bindingDrawer.hide()
} }
const onChange = value => { const onChange = value => {
dispatch("change", readableToRuntimeBinding(bindings, value)) currentVal = readableToRuntimeBinding(bindings, value)
dispatch("change", currentVal)
}
const onBlur = () => {
dispatch("blur", currentVal)
} }
</script> </script>
@ -45,6 +52,7 @@
readonly={isJS} readonly={isJS}
value={isJS ? "(JavaScript function)" : readableValue} value={isJS ? "(JavaScript function)" : readableValue}
on:change={event => onChange(event.detail)} on:change={event => onChange(event.detail)}
on:blur={onBlur}
{placeholder} {placeholder}
{updateOnChange} {updateOnChange}
/> />

View File

@ -52,7 +52,7 @@
) )
newBlock.inputs = { newBlock.inputs = {
fields: Object.keys(parameters.fields).reduce((fields, key) => { fields: Object.keys(parameters.fields ?? {}).reduce((fields, key) => {
fields[key] = "string" fields[key] = "string"
return fields return fields
}, {}), }, {}),

View File

@ -107,7 +107,7 @@
placeholder={keyPlaceholder} placeholder={keyPlaceholder}
readonly={readOnly} readonly={readOnly}
bind:value={field.name} bind:value={field.name}
on:change={changed} on:blur={changed}
/> />
{#if options} {#if options}
<Select bind:value={field.value} on:change={changed} {options} /> <Select bind:value={field.value} on:change={changed} {options} />
@ -115,7 +115,10 @@
<DrawerBindableInput <DrawerBindableInput
{bindings} {bindings}
placeholder="Value" placeholder="Value"
on:change={e => (field.value = e.detail)} on:blur={e => {
field.value = e.detail
changed()
}}
disabled={readOnly} disabled={readOnly}
value={field.value} value={field.value}
allowJS={false} allowJS={false}
@ -127,7 +130,7 @@
placeholder={valuePlaceholder} placeholder={valuePlaceholder}
readonly={readOnly} readonly={readOnly}
bind:value={field.value} bind:value={field.value}
on:change={changed} on:blur={changed}
/> />
{/if} {/if}
{#if toggle} {#if toggle}

View File

@ -1,16 +1,24 @@
<script> <script>
import { ModalContent, Toggle } from "@budibase/bbui" import { ModalContent, Toggle, Body } from "@budibase/bbui"
export let app export let app
export let published
let excludeRows = false let excludeRows = false
$: title = published ? "Export published app" : "Export latest app"
$: confirmText = published ? "Export published" : "Export latest"
const exportApp = () => { const exportApp = () => {
const id = app.deployed ? app.prodId : app.devId const id = published ? app.prodId : app.devId
const appName = encodeURIComponent(app.name) const appName = encodeURIComponent(app.name)
window.location = `/api/backups/export?appId=${id}&appname=${appName}&excludeRows=${excludeRows}` window.location = `/api/backups/export?appId=${id}&appname=${appName}&excludeRows=${excludeRows}`
} }
</script> </script>
<ModalContent title={"Export"} confirmText={"Export"} onConfirm={exportApp}> <ModalContent {title} {confirmText} onConfirm={exportApp}>
<Body
>Apps can be exported with or without data that is within internal tables -
select this below.</Body
>
<Toggle text="Exclude Rows" bind:value={excludeRows} /> <Toggle text="Exclude Rows" bind:value={excludeRows} />
</ModalContent> </ModalContent>

View File

@ -57,3 +57,10 @@ export const DefaultAppTheme = {
navBackground: "var(--spectrum-global-color-gray-50)", navBackground: "var(--spectrum-global-color-gray-50)",
navTextColor: "var(--spectrum-global-color-gray-800)", navTextColor: "var(--spectrum-global-color-gray-800)",
} }
export const PluginSource = {
URL: "URL",
NPM: "NPM",
GITHUB: "Github",
FILE: "File Upload",
}

View File

@ -46,7 +46,7 @@ export function buildQueryString(obj) {
if (str !== "") { if (str !== "") {
str += "&" str += "&"
} }
str += `${key}=${value || ""}` str += `${key}=${encodeURIComponent(value || "")}`
} }
} }
return str return str

View File

@ -31,7 +31,9 @@
class="add-button" class="add-button"
data-cy={`new-${isExternal ? "datasource" : "table"}`} data-cy={`new-${isExternal ? "datasource" : "table"}`}
> >
<Icon hoverable name="AddCircle" on:click={modal.show} /> {#if modal}
<Icon hoverable name="AddCircle" on:click={modal.show} />
{/if}
</div> </div>
</div> </div>
<div class="content"> <div class="content">

View File

@ -28,25 +28,25 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import restUtils from "helpers/data/utils" import restUtils from "helpers/data/utils"
import { import {
RestBodyTypes as bodyTypes,
SchemaTypeOptions,
PaginationLocations, PaginationLocations,
PaginationTypes, PaginationTypes,
RawRestBodyTypes,
RestBodyTypes as bodyTypes,
SchemaTypeOptions,
} from "constants/backend" } from "constants/backend"
import JSONPreview from "components/integration/JSONPreview.svelte" import JSONPreview from "components/integration/JSONPreview.svelte"
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte" import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
import DynamicVariableModal from "../../_components/DynamicVariableModal.svelte" import DynamicVariableModal from "../../_components/DynamicVariableModal.svelte"
import Placeholder from "assets/bb-spaceship.svg" import Placeholder from "assets/bb-spaceship.svg"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { RawRestBodyTypes } from "constants/backend"
import { import {
getRestBindings, getRestBindings,
toBindingsArray,
runtimeToReadableBinding,
readableToRuntimeBinding, readableToRuntimeBinding,
runtimeToReadableMap,
readableToRuntimeMap, readableToRuntimeMap,
runtimeToReadableBinding,
runtimeToReadableMap,
toBindingsArray,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
let query, datasource let query, datasource
@ -95,7 +95,7 @@
$: runtimeUrlQueries = readableToRuntimeMap(mergedBindings, breakQs) $: runtimeUrlQueries = readableToRuntimeMap(mergedBindings, breakQs)
function getSelectedQuery() { function getSelectedQuery() {
const cloneQuery = cloneDeep( return cloneDeep(
$queries.list.find(q => q._id === $queries.selected) || { $queries.list.find(q => q._id === $queries.selected) || {
datasourceId: $params.selectedDatasource, datasourceId: $params.selectedDatasource,
parameters: [], parameters: [],
@ -107,7 +107,6 @@
queryVerb: "read", queryVerb: "read",
} }
) )
return cloneQuery
} }
function checkQueryName(inputUrl = null) { function checkQueryName(inputUrl = null) {
@ -121,14 +120,15 @@
if (!base) { if (!base) {
return base return base
} }
const qs = restUtils.buildQueryString( let qs = restUtils.buildQueryString(
runtimeToReadableMap(mergedBindings, qsObj) runtimeToReadableMap(mergedBindings, qsObj)
) )
let newUrl = base let newUrl = base
if (base.includes("?")) { if (base.includes("?")) {
newUrl = base.split("?")[0] const split = base.split("?")
newUrl = split[0]
} }
return qs.length > 0 ? `${newUrl}?${qs}` : newUrl return qs.length === 0 ? newUrl : `${newUrl}?${qs}`
} }
function buildQuery() { function buildQuery() {
@ -314,6 +314,25 @@
} }
} }
const paramsChanged = evt => {
breakQs = {}
for (let param of evt.detail) {
breakQs[param.name] = param.value
}
}
const urlChanged = evt => {
breakQs = {}
const qs = evt.target.value.split("?")[1]
if (qs && qs.length > 0) {
const parts = qs.split("&")
for (let part of parts) {
const [key, value] = part.split("=")
breakQs[key] = value
}
}
}
onMount(async () => { onMount(async () => {
query = getSelectedQuery() query = getSelectedQuery()
@ -426,7 +445,11 @@
/> />
</div> </div>
<div class="url"> <div class="url">
<Input bind:value={url} placeholder="http://www.api.com/endpoint" /> <Input
on:blur={urlChanged}
bind:value={url}
placeholder="http://www.api.com/endpoint"
/>
</div> </div>
<Button primary disabled={!url} on:click={runQuery}>Send</Button> <Button primary disabled={!url} on:click={runQuery}>Send</Button>
<Button <Button
@ -456,13 +479,16 @@
/> />
</Tab> </Tab>
<Tab title="Params"> <Tab title="Params">
<KeyValueBuilder {#key breakQs}
bind:object={breakQs} <KeyValueBuilder
name="param" on:change={paramsChanged}
headings object={breakQs}
bindings={mergedBindings} name="param"
bindingDrawerLeft="260px" headings
/> bindings={mergedBindings}
bindingDrawerLeft="260px"
/>
{/key}
</Tab> </Tab>
<Tab title="Headers"> <Tab title="Headers">
<KeyValueBuilder <KeyValueBuilder

View File

@ -85,6 +85,8 @@
? [$store.componentToPaste?._id] ? [$store.componentToPaste?._id]
: [], : [],
isBudibaseEvent: true, isBudibaseEvent: true,
usedPlugins: $store.usedPlugins,
location: window.location,
} }
// Refresh the preview when required // Refresh the preview when required
@ -303,7 +305,7 @@
<iframe <iframe
title="componentPreview" title="componentPreview"
bind:this={iframe} bind:this={iframe}
src="/preview" src="/app/preview"
class:hidden={loading || error} class:hidden={loading || error}
class:tablet={$store.previewDevice === "tablet"} class:tablet={$store.previewDevice === "tablet"}
class:mobile={$store.previewDevice === "mobile"} class:mobile={$store.previewDevice === "mobile"}

View File

@ -60,7 +60,7 @@
}, },
} }
const handleKeyAction = async (component, key, ctrlKey = false) => { const handleKeyAction = async (event, component, key, ctrlKey = false) => {
if (!component || !key) { if (!component || !key) {
return false return false
} }
@ -76,6 +76,9 @@
const handler = keyHandlers[key] const handler = keyHandlers[key]
if (!handler) { if (!handler) {
return false return false
} else if (event) {
event.preventDefault()
event.stopPropagation()
} }
return handler(component) return handler(component)
} catch (error) { } catch (error) {
@ -95,14 +98,19 @@
return return
} }
// Key events are always for the selected component // Key events are always for the selected component
return handleKeyAction($selectedComponent, e.key, e.ctrlKey || e.metaKey) return await handleKeyAction(
e,
$selectedComponent,
e.key,
e.ctrlKey || e.metaKey
)
} }
const handleComponentMenu = async e => { const handleComponentMenu = async e => {
// Menu events can be for any component // Menu events can be for any component
const { id, key, ctrlKey } = e.detail const { id, key, ctrlKey } = e.detail
const component = findComponent($selectedScreen.props, id) const component = findComponent($selectedScreen.props, id)
return await handleKeyAction(component, key, ctrlKey) return await handleKeyAction(null, component, key, ctrlKey)
} }
onMount(() => { onMount(() => {

View File

@ -28,7 +28,7 @@
{#if $selectedComponent} {#if $selectedComponent}
{#key $selectedComponent._id} {#key $selectedComponent._id}
<Panel {title} icon={componentDefinition.icon} borderLeft> <Panel {title} icon={componentDefinition?.icon} borderLeft>
<ComponentSettingsSection <ComponentSettingsSection
{componentInstance} {componentInstance}
{componentDefinition} {componentDefinition}

View File

@ -24,7 +24,11 @@
$: currentDefinition = store.actions.components.getDefinition( $: currentDefinition = store.actions.components.getDefinition(
$selectedComponent?._component $selectedComponent?._component
) )
$: enrichedStructure = enrichStructure(structure, $store.components) $: enrichedStructure = enrichStructure(
structure,
$store.components,
$store.customComponents
)
$: filteredStructure = filterStructure( $: filteredStructure = filterStructure(
enrichedStructure, enrichedStructure,
section, section,
@ -46,8 +50,25 @@
// Parses the structure in the manifest and returns an enriched structure with // Parses the structure in the manifest and returns an enriched structure with
// explicit categories // explicit categories
const enrichStructure = (structure, definitions) => { const enrichStructure = (structure, definitions, customComponents) => {
let enrichedStructure = [] let enrichedStructure = []
// Add custom components category
if (customComponents?.length) {
enrichedStructure.push({
name: "Plugins",
isCategory: true,
children: customComponents
.map(x => ({
...definitions[x],
name: definitions[x].friendlyName || definitions[x].name,
}))
.sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1
}),
})
}
structure.forEach(item => { structure.forEach(item => {
if (typeof item === "string") { if (typeof item === "string") {
const def = definitions[`@budibase/standard-components/${item}`] const def = definitions[`@budibase/standard-components/${item}`]
@ -65,6 +86,7 @@
}) })
} }
}) })
return enrichedStructure return enrichedStructure
} }
@ -225,7 +247,7 @@
position: fixed; position: fixed;
right: 0; right: 0;
z-index: 1; z-index: 1;
height: 100%; height: calc(100% - 60px);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: stretch; align-items: stretch;

View File

@ -55,6 +55,8 @@
}, },
{ title: "Auth", href: "/builder/portal/manage/auth" }, { title: "Auth", href: "/builder/portal/manage/auth" },
{ title: "Email", href: "/builder/portal/manage/email" }, { title: "Email", href: "/builder/portal/manage/email" },
{ title: "Plugins", href: "/builder/portal/manage/plugins" },
{ {
title: "Organisation", title: "Organisation",
href: "/builder/portal/settings/organisation", href: "/builder/portal/settings/organisation",

View File

@ -15,7 +15,6 @@
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte" import CreateAppModal from "components/start/CreateAppModal.svelte"
import UpdateAppModal from "components/start/UpdateAppModal.svelte" import UpdateAppModal from "components/start/UpdateAppModal.svelte"
import ExportAppModal from "components/start/ExportAppModal.svelte"
import { store, automationStore } from "builderStore" import { store, automationStore } from "builderStore"
import { API } from "api" import { API } from "api"
@ -33,7 +32,6 @@
let selectedApp let selectedApp
let creationModal let creationModal
let updatingModal let updatingModal
let exportModal
let creatingApp = false let creatingApp = false
let loaded = $apps?.length || $templates?.length let loaded = $apps?.length || $templates?.length
let searchTerm = "" let searchTerm = ""
@ -407,10 +405,6 @@
<UpdateAppModal app={selectedApp} /> <UpdateAppModal app={selectedApp} />
</Modal> </Modal>
<Modal bind:this={exportModal} padding={false} width="600px">
<ExportAppModal app={selectedApp} />
</Modal>
<style> <style>
.appTable { .appTable {
border-top: var(--border-light); border-top: var(--border-light);

View File

@ -0,0 +1,127 @@
<script>
import {
ModalContent,
Label,
Input,
Select,
Dropzone,
Body,
notifications,
} from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { plugins } from "stores/portal"
import { PluginSource } from "constants"
function opt(name, optional) {
if (optional) {
return { name, optional }
}
return { name }
}
let authOptions = {
[PluginSource.URL]: [opt("URL"), opt("Headers", true)],
[PluginSource.NPM]: [opt("URL")],
[PluginSource.GITHUB]: [opt("URL"), opt("Github Token", true)],
[PluginSource.FILE]: [opt("File Upload")],
}
let file
let source = PluginSource.URL
let dynamicValues = {}
let validation
$: validation = source === "File Upload" ? file : dynamicValues["URL"]
function infoMessage(optionName) {
switch (optionName) {
case PluginSource.URL:
return "Please specify a URL which directs to a built plugin TAR archive. You can provide headers if authentication is required."
case PluginSource.NPM:
return "Please specify the URL to a public NPM package which contains the built version of the plugin you wish to install."
case PluginSource.GITHUB:
return "Please specify the URL to a Github repository which contains built plugin releases. If this is a private repo you can provide a token to access it."
case PluginSource.FILE:
return "Please provide a built plugin TAR archive. You can build a plugin locally using the Budibase CLI."
}
}
async function save() {
try {
if (source === PluginSource.FILE) {
await plugins.uploadPlugin(file)
} else {
const url = dynamicValues["URL"]
let auth =
source === PluginSource.GITHUB
? dynamicValues["Github Token"]
: source === PluginSource.URL
? dynamicValues["Headers"]
: undefined
await plugins.createPlugin(source, url, auth)
}
notifications.success("Plugin added successfully.")
} catch (err) {
const msg = err?.message ? err.message : JSON.stringify(err)
notifications.error(`Failed to add plugin: ${msg}`)
}
}
</script>
<ModalContent
confirmText={"Save"}
onConfirm={save}
disabled={!validation}
size="M"
title="Add new plugin"
>
<div class="form-row">
<Label size="M">Source</Label>
<Select
placeholder={null}
bind:value={source}
options={Object.values(PluginSource)}
/>
</div>
<Body size="S">{infoMessage(source)}</Body>
{#each authOptions[source] as option}
{#if option.name === PluginSource.FILE}
<div class="form-row">
<Label size="M">{option.name}</Label>
<Dropzone
gallery={false}
value={[file]}
on:change={e => {
if (!e.detail || e.detail.length === 0) {
file = null
} else {
file = e.detail[0]
}
}}
/>
</div>
{:else}
<div class="form-row">
<div>
<Label size="M">{option.name}</Label>
{#if option.optional}
<Label size="S" muted><i>Optional</i></Label>
{/if}
</div>
{#if option.name === "Headers"}
<KeyValueBuilder bind:object={dynamicValues[option.name]} />
{:else}
<Input bind:value={dynamicValues[option.name]} />
{/if}
</div>
{/if}
{/each}
</ModalContent>
<style>
.form-row {
display: grid;
grid-template-columns: 60px 1fr;
grid-gap: var(--spacing-l);
align-items: center;
}
</style>

View File

@ -0,0 +1,33 @@
<script>
import { Body, ModalContent, notifications } from "@budibase/bbui"
import { plugins } from "stores/portal"
import { createEventDispatcher } from "svelte"
export let plugin
let dispatch = createEventDispatcher()
async function deletePlugin() {
try {
await plugins.deletePlugin(plugin._id)
notifications.success(`Plugin ${plugin?.name} deleted`)
dispatch("deleted")
} catch (error) {
const msg = error?.message ? error.message : JSON.stringify(error)
notifications.error(`Error deleting plugin: ${msg}`)
}
}
</script>
<ModalContent
warning
onConfirm={deletePlugin}
title="Delete Plugin"
confirmText="Delete plugin"
cancelText="Cancel"
showCloseIcon={false}
>
<Body>
Are you sure you want to delete <strong>{plugin?.name}</strong>
</Body>
</ModalContent>

View File

@ -0,0 +1,145 @@
<script>
import {
Icon,
Body,
Modal,
ModalContent,
Button,
Label,
Input,
} from "@budibase/bbui"
import DeletePluginModal from "../_components/DeletePluginModal.svelte"
export let plugin
let detailsModal
let deleteModal
let icon =
plugin.schema.type === "component"
? plugin.schema.schema.icon || "Book"
: plugin.schema.schema.icon || "Beaker"
function pluginDeleted() {
if (detailsModal) {
detailsModal.hide()
}
}
</script>
<div class="row" on:click={() => detailsModal.show()}>
<div class="title">
<div class="name">
<div>
<Icon size="M" name={icon} />
</div>
<div>
<Body
size="S"
color="var(--spectrum-global-color-gray-900)"
weight="800"
>
{plugin.name}
</Body>
</div>
</div>
</div>
<div class="desktop">{plugin.version}</div>
<div class="desktop">
{plugin.schema.type.charAt(0).toUpperCase() + plugin.schema.type.slice(1)}
</div>
<div>
<Icon name="ChevronRight" />
</div>
</div>
<Modal bind:this={detailsModal}>
<ModalContent
size="M"
title="Plugin details"
showConfirmButton={false}
showCancelButton={false}
>
<div class="details-row">
<Label size="M">Name</Label>
<Input disabled value={plugin.name} />
</div>
<div class="details-row">
<Label size="M">Type</Label>
<Input
disabled
value={plugin.schema.type.charAt(0).toUpperCase() +
plugin.schema.type.slice(1)}
/>
</div>
<div class="details-row">
<Label size="M">Source</Label>
<Input disabled value={plugin.source || "N/A"} />
</div>
<div class="details-row">
<Label size="M">Version</Label>
<Input disabled value={plugin.version} />
</div>
<div class="details-row">
<Label size="M">License</Label>
<Input disabled value={plugin.package.license} />
</div>
<div class="details-row">
<Label size="M">Author</Label>
<Input disabled value={plugin.package.author || "N/A"} />
</div>
<div class="footer" slot="footer">
<Button newStyles on:click={deleteModal.show()} warning>Delete</Button>
</div>
</ModalContent>
<Modal bind:this={deleteModal}>
<DeletePluginModal {plugin} on:deleted={pluginDeleted} />
</Modal>
</Modal>
<style>
.row {
display: grid;
grid-template-columns: 1fr 110px 140px 20px;
align-items: center;
background: var(--background);
border-radius: 4px;
padding: 0 16px;
height: 56px;
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--spectrum-global-color-gray-300);
transition: background 130ms ease-out;
}
.row:hover {
cursor: pointer;
background: var(--spectrum-global-color-gray-75);
}
.name {
grid-gap: var(--spacing-m);
grid-template-columns: 75px 75px;
align-items: center;
display: flex;
}
.details-row {
display: grid;
grid-template-columns: 60px 1fr;
grid-gap: var(--spacing-l) var(--spacing-l);
align-items: center;
}
@media (max-width: 640px) {
.desktop {
display: none !important;
}
}
.footer {
display: flex;
gap: var(--spacing-l);
}
</style>

View File

@ -0,0 +1,95 @@
<script>
import {
Layout,
Heading,
Body,
Button,
Select,
Divider,
Modal,
Search,
} from "@budibase/bbui"
import { onMount } from "svelte"
import { plugins } from "stores/portal"
import PluginRow from "./_components/PluginRow.svelte"
import AddPluginModal from "./_components/AddPluginModal.svelte"
let modal
let searchTerm = ""
let filter = "all"
let filterOptions = [
{ label: "All plugins", value: "all" },
{ label: "Components", value: "component" },
{ label: "Datasources", value: "datasource" },
]
$: filteredPlugins = $plugins
.filter(plugin => {
return filter === "all" || plugin.schema.type === filter
})
.filter(plugin => {
return (
!searchTerm ||
plugin?.name?.toLowerCase().includes(searchTerm.toLowerCase())
)
})
onMount(async () => {
await plugins.load()
})
</script>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading size="M">Plugins</Heading>
<Body>Add your own custom datasources and components</Body>
</Layout>
<Divider size="S" />
<Layout noPadding>
<div class="controls">
<div>
<Button on:click={modal.show} newStyles cta icon={"Add"}>
Add plugin
</Button>
</div>
<div class="filters">
<div class="select">
<Select
bind:value={filter}
placeholder={null}
options={filterOptions}
autoWidth
quiet
/>
</div>
<Search bind:value={searchTerm} placeholder="Search plugins" />
</div>
</div>
{#if filteredPlugins?.length}
<Layout noPadding gap="S">
{#each filteredPlugins as plugin (plugin._id)}
<PluginRow {plugin} />
{/each}
</Layout>
{/if}
</Layout>
</Layout>
<Modal bind:this={modal}>
<AddPluginModal />
</Modal>
<style>
.filters {
display: flex;
gap: var(--spacing-xl);
}
.controls {
display: flex;
gap: var(--spacing-xl);
justify-content: space-between;
}
.controls :global(.spectrum-Search) {
width: 200px;
}
</style>

View File

@ -16,6 +16,7 @@
MenuItem, MenuItem,
Icon, Icon,
Helpers, Helpers,
Modal,
} from "@budibase/bbui" } from "@budibase/bbui"
import OverviewTab from "../_components/OverviewTab.svelte" import OverviewTab from "../_components/OverviewTab.svelte"
import SettingsTab from "../_components/SettingsTab.svelte" import SettingsTab from "../_components/SettingsTab.svelte"
@ -29,6 +30,7 @@
import EditableIcon from "components/common/EditableIcon.svelte" import EditableIcon from "components/common/EditableIcon.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import HistoryTab from "components/portal/overview/automation/HistoryTab.svelte" import HistoryTab from "components/portal/overview/automation/HistoryTab.svelte"
import ExportAppModal from "components/start/ExportAppModal.svelte"
import { checkIncomingDeploymentStatus } from "components/deploy/utils" import { checkIncomingDeploymentStatus } from "components/deploy/utils"
import { onDestroy, onMount } from "svelte" import { onDestroy, onMount } from "svelte"
@ -38,7 +40,9 @@
let loaded = false let loaded = false
let deletionModal let deletionModal
let unpublishModal let unpublishModal
let exportModal
let appName = "" let appName = ""
let published
// App // App
$: filteredApps = $apps.filter(app => app.devId === application) $: filteredApps = $apps.filter(app => app.devId === application)
@ -140,11 +144,9 @@
notifications.success("App ID copied to clipboard.") notifications.success("App ID copied to clipboard.")
} }
const exportApp = (app, opts = { published: false }) => { const exportApp = opts => {
const appName = encodeURIComponent(app.name) published = opts.published
const id = opts?.published ? app.prodId : app.devId exportModal.show()
// always export the development version
window.location = `/api/backups/export?appId=${id}&appname=${appName}`
} }
const unpublishApp = app => { const unpublishApp = app => {
@ -206,6 +208,10 @@
}) })
</script> </script>
<Modal bind:this={exportModal} padding={false} width="600px">
<ExportAppModal app={selectedApp} {published} />
</Modal>
<span class="overview-wrap"> <span class="overview-wrap">
<Page wide noPadding> <Page wide noPadding>
{#await promise} {#await promise}
@ -269,14 +275,14 @@
<Icon hoverable name="More" /> <Icon hoverable name="More" />
</span> </span>
<MenuItem <MenuItem
on:click={() => exportApp(selectedApp, { published: false })} on:click={() => exportApp({ published: false })}
icon="DownloadFromCloud" icon="DownloadFromCloud"
> >
Export latest Export latest
</MenuItem> </MenuItem>
{#if isPublished} {#if isPublished}
<MenuItem <MenuItem
on:click={() => exportApp(selectedApp, { published: true })} on:click={() => exportApp({ published: true })}
icon="DownloadFromCloudOutline" icon="DownloadFromCloudOutline"
> >
Export published Export published

View File

@ -88,14 +88,14 @@
<Heading size="S">Information</Heading> <Heading size="S">Information</Heading>
<Body size="S">Here you can update your logo and organization name.</Body> <Body size="S">Here you can update your logo and organization name.</Body>
</Layout> </Layout>
<div class="fields"> <div className="fields">
<div class="field"> <div className="field">
<Label size="L">Org. name</Label> <Label size="L">Org. name</Label>
<Input thin bind:value={$values.company} /> <Input thin bind:value={$values.company} />
</div> </div>
<div class="field logo"> <div className="field logo">
<Label size="L">Logo</Label> <Label size="L">Logo</Label>
<div class="file"> <div className="file">
<Dropzone <Dropzone
value={[$values.logo]} value={[$values.logo]}
on:change={e => { on:change={e => {
@ -115,8 +115,8 @@
<Heading size="S">Platform</Heading> <Heading size="S">Platform</Heading>
<Body size="S">Here you can set up general platform settings.</Body> <Body size="S">Here you can set up general platform settings.</Body>
</Layout> </Layout>
<div class="fields"> <div className="fields">
<div class="field"> <div className="field">
<Label <Label
size="L" size="L"
tooltip={"Update the Platform URL to match your Budibase web URL. This keeps email templates and authentication configs up to date."} tooltip={"Update the Platform URL to match your Budibase web URL. This keeps email templates and authentication configs up to date."}
@ -150,15 +150,18 @@
display: grid; display: grid;
grid-gap: var(--spacing-m); grid-gap: var(--spacing-m);
} }
.field { .field {
display: grid; display: grid;
grid-template-columns: 100px 1fr; grid-template-columns: 100px 1fr;
grid-gap: var(--spacing-l); grid-gap: var(--spacing-l);
align-items: center; align-items: center;
} }
.file { .file {
max-width: 30ch; max-width: 30ch;
} }
.logo { .logo {
align-items: start; align-items: start;
} }

View File

@ -62,8 +62,11 @@ export function createDatasourcesStore() {
unselect: () => { unselect: () => {
update(state => ({ ...state, selected: null })) update(state => ({ ...state, selected: null }))
}, },
updateSchema: async datasource => { updateSchema: async (datasource, tablesFilter) => {
const response = await API.buildDatasourceSchema(datasource?._id) const response = await API.buildDatasourceSchema({
datasourceId: datasource?._id,
tablesFilter,
})
return await updateDatasource(response) return await updateDatasource(response)
}, },
save: async (body, fetchSchema = false) => { save: async (body, fetchSchema = false) => {

View File

@ -8,3 +8,4 @@ export { oidc } from "./oidc"
export { templates } from "./templates" export { templates } from "./templates"
export { licensing } from "./licensing" export { licensing } from "./licensing"
export { groups } from "./groups" export { groups } from "./groups"
export { plugins } from "./plugins"

View File

@ -0,0 +1,77 @@
import { writable } from "svelte/store"
import { API } from "api"
import { PluginSource } from "constants"
export function createPluginsStore() {
const { subscribe, set, update } = writable([])
async function load() {
const plugins = await API.getPlugins()
set(plugins)
}
async function deletePlugin(pluginId) {
await API.deletePlugin(pluginId)
update(state => {
state = state.filter(existing => existing._id !== pluginId)
return state
})
}
async function createPlugin(source, url, auth = null) {
let pluginData = {
source,
url,
}
switch (source) {
case PluginSource.URL:
pluginData.headers = auth
break
case PluginSource.GITHUB:
pluginData.githubToken = auth
break
}
let res = await API.createPlugin(pluginData)
let newPlugin = res.plugins[0]
update(state => {
const currentIdx = state.findIndex(plugin => plugin._id === newPlugin._id)
if (currentIdx >= 0) {
state.splice(currentIdx, 1, newPlugin)
} else {
state.push(newPlugin)
}
return state
})
}
async function uploadPlugin(file) {
if (!file) {
return
}
let data = new FormData()
data.append("file", file)
let resp = await API.uploadPlugin(data)
let newPlugin = resp.plugins[0]
update(state => {
const currentIdx = state.findIndex(plugin => plugin._id === newPlugin._id)
if (currentIdx >= 0) {
state.splice(currentIdx, 1, newPlugin)
} else {
state.push(newPlugin)
}
return state
})
}
return {
subscribe,
load,
createPlugin,
deletePlugin,
uploadPlugin,
}
}
export const plugins = createPluginsStore()

View File

@ -1,15 +1,23 @@
import { svelte } from "@sveltejs/vite-plugin-svelte" import { svelte } from "@sveltejs/vite-plugin-svelte"
import replace from "@rollup/plugin-replace" import replace from "@rollup/plugin-replace"
import { defineConfig, loadEnv } from "vite"
import path from "path" import path from "path"
export default ({ mode }) => { export default defineConfig(({ mode }) => {
const isProduction = mode === "production" const isProduction = mode === "production"
const env = loadEnv(mode, process.cwd())
return { return {
server: { server: {
fs: { fs: {
strict: false, strict: false,
}, },
hmr: {
protocol: env.VITE_HMR_PROTOCOL || "ws",
clientPort: env.VITE_HMR_CLIENT_PORT || 3000,
path: env.VITE_HMR_PATH || "/",
},
port: 3000,
}, },
base: "/builder/", base: "/builder/",
build: { build: {
@ -79,4 +87,4 @@ export default ({ mode }) => {
], ],
}, },
} }
} })

View File

@ -967,6 +967,11 @@
debug "^3.1.0" debug "^3.1.0"
lodash.once "^4.1.1" lodash.once "^4.1.1"
"@esbuild/linux-loong64@0.14.54":
version "0.14.54"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
"@hapi/hoek@^9.0.0": "@hapi/hoek@^9.0.0":
version "9.2.1" version "9.2.1"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17"
@ -1215,10 +1220,10 @@
estree-walker "^1.0.1" estree-walker "^1.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@rollup/pluginutils@^4.1.1": "@rollup/pluginutils@^4.2.1":
version "4.1.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
integrity sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ== integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
dependencies: dependencies:
estree-walker "^2.0.1" estree-walker "^2.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
@ -1350,17 +1355,17 @@
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-4.3.0.tgz#03ddf67d3aa8a9a4cb0edbbd259465c9ced7e70d" resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-4.3.0.tgz#03ddf67d3aa8a9a4cb0edbbd259465c9ced7e70d"
integrity sha512-ZQ2XAhgu4G9yBeXQNDAz07Z8oZNnMt5o9vzf/mpBA7Teb/JI+8qXp2wt8D245SzmtNlFkG/bzRYvQc0scgZeCQ== integrity sha512-ZQ2XAhgu4G9yBeXQNDAz07Z8oZNnMt5o9vzf/mpBA7Teb/JI+8qXp2wt8D245SzmtNlFkG/bzRYvQc0scgZeCQ==
"@sveltejs/vite-plugin-svelte@1.0.0-next.19": "@sveltejs/vite-plugin-svelte@1.0.1":
version "1.0.0-next.19" version "1.0.1"
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.19.tgz#9646abc2cd1982146db4bb341aafdb5f32f19dd2" resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.1.tgz#7f468f03c933fcdfc60d4773671c73f33b9ef4d6"
integrity sha512-q9hHkMzodScwDq64pNaWhekpj97vWg3wO9T0rqd8bC2EsrBQs2uD1qMJvDqlNd63AbO2uSJMGo+TQ0Xt2xgyYg== integrity sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==
dependencies: dependencies:
"@rollup/pluginutils" "^4.1.1" "@rollup/pluginutils" "^4.2.1"
debug "^4.3.2" debug "^4.3.4"
kleur "^4.1.4" deepmerge "^4.2.2"
magic-string "^0.25.7" kleur "^4.1.5"
require-relative "^0.8.7" magic-string "^0.26.2"
svelte-hmr "^0.14.7" svelte-hmr "^0.14.12"
"@testing-library/dom@^7.0.3": "@testing-library/dom@^7.0.3":
version "7.31.2" version "7.31.2"
@ -2493,6 +2498,13 @@ debug@^3.1.0:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decamelize@^1.2.0: decamelize@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@ -2655,113 +2667,132 @@ error-ex@^1.3.1:
dependencies: dependencies:
is-arrayish "^0.2.1" is-arrayish "^0.2.1"
esbuild-android-arm64@0.13.14: esbuild-android-64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.14.tgz#c85083ece26be3d67e6c720e088968a98409e023" resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
integrity sha512-Q+Xhfp827r+ma8/DJgpMRUbDZfefsk13oePFEXEIJ4gxFbNv5+vyiYXYuKm43/+++EJXpnaYmEnu4hAKbAWYbA== integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
esbuild-darwin-64@0.13.14: esbuild-android-arm64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.14.tgz#8e4e237ad847cc54a1d3a5caee26a746b9f0b81f" resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
integrity sha512-YmOhRns6QBNSjpVdTahi/yZ8dscx9ai7a6OY6z5ACgOuQuaQ2Qk2qgJ0/siZ6LgD0gJFMV8UINFV5oky5TFNQQ== integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
esbuild-darwin-arm64@0.13.14: esbuild-darwin-64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.14.tgz#b3b5ebd40b2cb06ee0f6fb342dd4bdcca54ad273" resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
integrity sha512-Lp00VTli2jqZghSa68fx3fEFCPsO1hK59RMo1PRap5RUjhf55OmaZTZYnCDI0FVlCtt+gBwX5qwFt4lc6tI1xg== integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
esbuild-freebsd-64@0.13.14: esbuild-darwin-arm64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.14.tgz#175ecb2fa8141428cf70ea2d5f4c27534bad53e0" resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
integrity sha512-BKosI3jtvTfnmsCW37B1TyxMUjkRWKqopR0CE9AF2ratdpkxdR24Vpe3gLKNyWiZ7BE96/SO5/YfhbPUzY8wKw== integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
esbuild-freebsd-arm64@0.13.14: esbuild-freebsd-64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.14.tgz#a7d64e41d1fa581f8db7775e5200f18e67d70c4d" resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
integrity sha512-yd2uh0yf+fWv5114+SYTl4/1oDWtr4nN5Op+PGxAkMqHfYfLjFKpcxwCo/QOS/0NWqPVE8O41IYZlFhbEN2B8Q== integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
esbuild-linux-32@0.13.14: esbuild-freebsd-arm64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.14.tgz#14bdd4f6b6cfd35c65c835894651ba335c2117da" resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
integrity sha512-a8rOnS1oWSfkkYWXoD2yXNV4BdbDKA7PNVQ1klqkY9SoSApL7io66w5H44mTLsfyw7G6Z2vLlaLI2nz9MMAowA== integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
esbuild-linux-64@0.13.14: esbuild-linux-32@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.14.tgz#7fd56851b2982fdd0cd8447ee9858c2c5711708a" resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
integrity sha512-yPZSoMs9W2MC3Dw+6kflKt5FfQm6Dicex9dGIr1OlHRsn3Hm7yGMUTctlkW53KknnZdOdcdd5upxvbxqymczVQ== integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
esbuild-linux-arm64@0.13.14: esbuild-linux-64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.14.tgz#a55634d70679ba509adeafd68eebb9fd1ec5af6c" resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
integrity sha512-Lvo391ln9PzC334e+jJ2S0Rt0cxP47eoH5gFyv/E8HhOnEJTvm7A+RRnMjjHnejELacTTfYgFGQYPjLsi/jObQ== integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
esbuild-linux-arm@0.13.14: esbuild-linux-arm64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.14.tgz#bb96a99677e608b31ff61f37564326d38e846ca2" resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
integrity sha512-8chZE4pkKRvJ/M/iwsNQ1KqsRg2RyU5eT/x2flNt/f8F2TVrDreR7I0HEeCR50wLla3B1C3wTIOzQBmjuc6uWg== integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
esbuild-linux-mips64le@0.13.14: esbuild-linux-arm@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.14.tgz#6a55362a8fd1e593dea2ecc41877beed8b8184b9" resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
integrity sha512-MZhgxbmrWbpY3TOE029O6l5tokG9+Yoj2hW7vdit/d/VnmneqeGrSHADuDL6qXM8L5jaCiaivb4VhsyVCpdAbQ== integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
esbuild-linux-ppc64le@0.13.14: esbuild-linux-mips64le@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.14.tgz#9e0048587ece0a7f184ab147f20d077098045e7f" resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
integrity sha512-un7KMwS7fX1Un6BjfSZxTT8L5cV/8Uf4SAhM7WYy2XF8o8TI+uRxxD03svZnRNIPsN2J5cl6qV4n7Iwz+yhhVw== integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
esbuild-netbsd-64@0.13.14: esbuild-linux-ppc64le@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.14.tgz#dcab16a4bbcfa16e2e8535dadc5f64fdc891c63b" resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
integrity sha512-5ekKx/YbOmmlTeNxBjh38Uh5TGn5C4uyqN17i67k18pS3J+U2hTVD7rCxcFcRS1AjNWumkVL3jWqYXadFwMS0Q== integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
esbuild-openbsd-64@0.13.14: esbuild-linux-riscv64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.14.tgz#3c7453b155ebb68dc34d5aec3bd6505337bdda08" resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
integrity sha512-9bzvwewHjct2Cv5XcVoE1yW5YTW12Sk838EYfA46abgnhxGoFSD1mFcaztp5HHC43AsF+hQxbSFG/RilONARUA== integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
esbuild-sunos-64@0.13.14: esbuild-linux-s390x@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.14.tgz#85addf5fef6b5db154a955d4f2e88953359d75ce" resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
integrity sha512-mjMrZB76M6FmoiTvj/RGWilrioR7gVwtFBRVugr9qLarXMIU1W/pQx+ieEOtflrW61xo8w1fcxyHsVVGRvoQ0w== integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
esbuild-windows-32@0.13.14: esbuild-netbsd-64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.14.tgz#f77f98f30a5c636c44db2428ecdf9bcbbaedb1a7" resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
integrity sha512-GZa6mrx2rgfbH/5uHg0Rdw50TuOKbdoKCpEBitzmG5tsXBdce+cOL+iFO5joZc6fDVCLW3Y6tjxmSXRk/v20Hg== integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
esbuild-windows-64@0.13.14: esbuild-openbsd-64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.14.tgz#bc778674c40d65150d12385e0f23eb3a0badbd0d" resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
integrity sha512-Lsgqah24bT7ClHjLp/Pj3A9wxjhIAJyWQcrOV4jqXAFikmrp2CspA8IkJgw7HFjx6QrJuhpcKVbCAe/xw0i2yw== integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
esbuild-windows-arm64@0.13.14: esbuild-sunos-64@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.14.tgz#91a8dad35ab2c4dd27cd83860742955b25a354d7" resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
integrity sha512-KP8FHVlWGhM7nzYtURsGnskXb/cBCPTfj0gOKfjKq2tHtYnhDZywsUG57nk7TKhhK0fL11LcejHG3LRW9RF/9A== integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
esbuild@^0.13.2: esbuild-windows-32@0.14.54:
version "0.13.14" version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.14.tgz#98a3f7f42809abdc2b57c84565d0f713382dc1a5" resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
integrity sha512-xu4D+1ji9x53ocuomcY+KOrwAnWzhBu/wTEjpdgZ8I1c8i5vboYIeigMdzgY1UowYBKa2vZgVgUB32bu7gkxeg== integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
esbuild-windows-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
esbuild-windows-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
esbuild@^0.14.47:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
optionalDependencies: optionalDependencies:
esbuild-android-arm64 "0.13.14" "@esbuild/linux-loong64" "0.14.54"
esbuild-darwin-64 "0.13.14" esbuild-android-64 "0.14.54"
esbuild-darwin-arm64 "0.13.14" esbuild-android-arm64 "0.14.54"
esbuild-freebsd-64 "0.13.14" esbuild-darwin-64 "0.14.54"
esbuild-freebsd-arm64 "0.13.14" esbuild-darwin-arm64 "0.14.54"
esbuild-linux-32 "0.13.14" esbuild-freebsd-64 "0.14.54"
esbuild-linux-64 "0.13.14" esbuild-freebsd-arm64 "0.14.54"
esbuild-linux-arm "0.13.14" esbuild-linux-32 "0.14.54"
esbuild-linux-arm64 "0.13.14" esbuild-linux-64 "0.14.54"
esbuild-linux-mips64le "0.13.14" esbuild-linux-arm "0.14.54"
esbuild-linux-ppc64le "0.13.14" esbuild-linux-arm64 "0.14.54"
esbuild-netbsd-64 "0.13.14" esbuild-linux-mips64le "0.14.54"
esbuild-openbsd-64 "0.13.14" esbuild-linux-ppc64le "0.14.54"
esbuild-sunos-64 "0.13.14" esbuild-linux-riscv64 "0.14.54"
esbuild-windows-32 "0.13.14" esbuild-linux-s390x "0.14.54"
esbuild-windows-64 "0.13.14" esbuild-netbsd-64 "0.14.54"
esbuild-windows-arm64 "0.13.14" esbuild-openbsd-64 "0.14.54"
esbuild-sunos-64 "0.14.54"
esbuild-windows-32 "0.14.54"
esbuild-windows-64 "0.14.54"
esbuild-windows-arm64 "0.14.54"
escalade@^3.1.1: escalade@^3.1.1:
version "3.1.1" version "3.1.1"
@ -3519,6 +3550,13 @@ is-core-module@^2.2.0:
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
is-core-module@^2.9.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
dependencies:
has "^1.0.3"
is-data-descriptor@^0.1.4: is-data-descriptor@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@ -4285,10 +4323,10 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
kleur@^4.1.4: kleur@^4.1.5:
version "4.1.4" version "4.1.5"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
lazy-ass@1.6.0, lazy-ass@^1.6.0: lazy-ass@1.6.0, lazy-ass@^1.6.0:
version "1.6.0" version "1.6.0"
@ -4425,6 +4463,13 @@ magic-string@^0.25.7:
dependencies: dependencies:
sourcemap-codec "^1.4.4" sourcemap-codec "^1.4.4"
magic-string@^0.26.2:
version "0.26.2"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.2.tgz#5331700e4158cd6befda738bb6b0c7b93c0d4432"
integrity sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==
dependencies:
sourcemap-codec "^1.4.8"
make-dir@^3.0.0: make-dir@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@ -4615,10 +4660,10 @@ nanoid@^2.1.0:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
nanoid@^3.1.30: nanoid@^3.3.4:
version "3.1.30" version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"
@ -4887,7 +4932,7 @@ path-key@^3.0.0, path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.6: path-parse@^1.0.6, path-parse@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@ -4948,14 +4993,14 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss@^8.3.8: postcss@^8.4.16:
version "8.3.11" version "8.4.16"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
integrity sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA== integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
dependencies: dependencies:
nanoid "^3.1.30" nanoid "^3.3.4"
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^0.6.2" source-map-js "^1.0.2"
posthog-js@1.4.5: posthog-js@1.4.5:
version "1.4.5" version "1.4.5"
@ -5184,11 +5229,6 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
require-relative@^0.8.7:
version "0.8.7"
resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de"
integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=
resolve-cwd@^3.0.0: resolve-cwd@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@ -5206,7 +5246,7 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.20.0: resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1:
version "1.20.0" version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
@ -5214,6 +5254,15 @@ resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.20.0:
is-core-module "^2.2.0" is-core-module "^2.2.0"
path-parse "^1.0.6" path-parse "^1.0.6"
resolve@^1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
restore-cursor@^3.1.0: restore-cursor@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@ -5262,7 +5311,14 @@ rollup-pluginutils@^2.8.2:
dependencies: dependencies:
estree-walker "^0.6.1" estree-walker "^0.6.1"
rollup@^2.44.0, rollup@^2.57.0: "rollup@>=2.75.6 <2.77.0 || ~2.77.0":
version "2.77.3"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12"
integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
optionalDependencies:
fsevents "~2.3.2"
rollup@^2.44.0:
version "2.60.0" version "2.60.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.60.0.tgz#4ee60ab7bdd0356763f87d7099f413e5460fc193" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.60.0.tgz#4ee60ab7bdd0356763f87d7099f413e5460fc193"
integrity sha512-cHdv9GWd58v58rdseC8e8XIaPUo8a9cgZpnCMMDGZFDZKEODOiPPEQFXLriWr/TjXzhPPmG5bkAztPsOARIcGQ== integrity sha512-cHdv9GWd58v58rdseC8e8XIaPUo8a9cgZpnCMMDGZFDZKEODOiPPEQFXLriWr/TjXzhPPmG5bkAztPsOARIcGQ==
@ -5475,10 +5531,10 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0" source-map-resolve "^0.5.0"
use "^3.1.0" use "^3.1.0"
source-map-js@^0.6.2: source-map-js@^1.0.2:
version "0.6.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-resolve@^0.5.0: source-map-resolve@^0.5.0:
version "0.5.3" version "0.5.3"
@ -5527,7 +5583,7 @@ source-map@^0.7.3:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.4: sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
version "1.4.8" version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
@ -5707,15 +5763,20 @@ supports-hyperlinks@^2.0.0:
has-flag "^4.0.0" has-flag "^4.0.0"
supports-color "^7.0.0" supports-color "^7.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
svelte-dnd-action@^0.9.8: svelte-dnd-action@^0.9.8:
version "0.9.12" version "0.9.12"
resolved "https://registry.yarnpkg.com/svelte-dnd-action/-/svelte-dnd-action-0.9.12.tgz#78cf33097986488c6d069eca517af473cd998730" resolved "https://registry.yarnpkg.com/svelte-dnd-action/-/svelte-dnd-action-0.9.12.tgz#78cf33097986488c6d069eca517af473cd998730"
integrity sha512-GlXIB3/56IMR5A0+qUx+FX7Q7n8uCAIeuYdgSBmn9iOlxWc+mgM8P1kNwAKCMSTdQ4IQETVQILNgWVY1KIFzsg== integrity sha512-GlXIB3/56IMR5A0+qUx+FX7Q7n8uCAIeuYdgSBmn9iOlxWc+mgM8P1kNwAKCMSTdQ4IQETVQILNgWVY1KIFzsg==
svelte-hmr@^0.14.7: svelte-hmr@^0.14.12:
version "0.14.7" version "0.14.12"
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.7.tgz#7fa8261c7b225d9409f0a86f3b9ea5c3ca6f6607" resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.12.tgz#a127aec02f1896500b10148b2d4d21ddde39973f"
integrity sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog== integrity sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==
svelte-jester@^1.3.2: svelte-jester@^1.3.2:
version "1.8.2" version "1.8.2"
@ -6081,15 +6142,15 @@ verror@1.10.0:
core-util-is "1.0.2" core-util-is "1.0.2"
extsprintf "^1.2.0" extsprintf "^1.2.0"
vite@^2.1.5: vite@^3.0.8:
version "2.6.14" version "3.0.8"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.6.14.tgz#35c09a15e4df823410819a2a239ab11efb186271" resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.8.tgz#aa095ad8e3e5da46d9ec7e878f262678965d6531"
integrity sha512-2HA9xGyi+EhY2MXo0+A2dRsqsAG3eFNEVIo12olkWhOmc8LfiM+eMdrXf+Ruje9gdXgvSqjLI9freec1RUM5EA== integrity sha512-AOZ4eN7mrkJiOLuw8IA7piS4IdOQyQCA81GxGsAQvAZzMRi9ZwGB3TOaYsj4uLAWK46T5L4AfQ6InNGlxX30IQ==
dependencies: dependencies:
esbuild "^0.13.2" esbuild "^0.14.47"
postcss "^8.3.8" postcss "^8.4.16"
resolve "^1.20.0" resolve "^1.22.1"
rollup "^2.57.0" rollup ">=2.75.6 <2.77.0 || ~2.77.0"
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "1.3.4-alpha.2", "version": "1.3.15-alpha.3",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {
@ -26,14 +26,18 @@
"outputPath": "build" "outputPath": "build"
}, },
"dependencies": { "dependencies": {
"@budibase/backend-core": "1.3.4-alpha.2", "@budibase/backend-core": "1.3.15-alpha.3",
"@budibase/string-templates": "1.3.15-alpha.3",
"@budibase/types": "1.3.15-alpha.3",
"axios": "0.21.2", "axios": "0.21.2",
"chalk": "4.1.0", "chalk": "4.1.0",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",
"commander": "7.1.0", "commander": "7.1.0",
"docker-compose": "0.23.6", "docker-compose": "0.23.6",
"dotenv": "16.0.1", "dotenv": "16.0.1",
"download": "8.0.0",
"inquirer": "8.0.0", "inquirer": "8.0.0",
"joi": "17.6.0",
"lookpath": "1.1.0", "lookpath": "1.1.0",
"node-fetch": "2", "node-fetch": "2",
"pkg": "5.7.0", "pkg": "5.7.0",

View File

@ -3,6 +3,7 @@ exports.CommandWords = {
HOSTING: "hosting", HOSTING: "hosting",
ANALYTICS: "analytics", ANALYTICS: "analytics",
HELP: "help", HELP: "help",
PLUGIN: "plugins",
} }
exports.InitTypes = { exports.InitTypes = {

View File

@ -0,0 +1 @@
process.env.NO_JS = "1"

27
packages/cli/src/exec.js Normal file
View File

@ -0,0 +1,27 @@
const util = require("util")
const exec = util.promisify(require("child_process").exec)
exports.exec = async (command, dir = "./") => {
const { stdout } = await exec(command, { cwd: dir })
return stdout
}
exports.utilityInstalled = async utilName => {
try {
await exports.exec(`${utilName} --version`)
return true
} catch (err) {
return false
}
}
exports.runPkgCommand = async (command, dir = "./") => {
const yarn = await exports.utilityInstalled("yarn")
const npm = await exports.utilityInstalled("npm")
if (!yarn && !npm) {
throw new Error("Must have yarn or npm installed to run build.")
}
const npmCmd = command === "install" ? `npm ${command}` : `npm run ${command}`
const cmd = yarn ? `yarn ${command}` : npmCmd
await exports.exec(cmd, dir)
}

View File

@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
require("./prebuilds") require("./prebuilds")
require("./environment")
const { getCommands } = require("./options") const { getCommands } = require("./options")
const { Command } = require("commander") const { Command } = require("commander")
const { getHelpDescription } = require("./utils") const { getHelpDescription } = require("./utils")

View File

@ -1,7 +1,8 @@
const analytics = require("./analytics") const analytics = require("./analytics")
const hosting = require("./hosting") const hosting = require("./hosting")
const backups = require("./backups") const backups = require("./backups")
const plugins = require("./plugins")
exports.getCommands = () => { exports.getCommands = () => {
return [hosting.command, analytics.command, backups.command] return [hosting.command, analytics.command, backups.command, plugins.command]
} }

View File

@ -0,0 +1,132 @@
const Command = require("../structures/Command")
const { CommandWords } = require("../constants")
const { getSkeleton, fleshOutSkeleton } = require("./skeleton")
const questions = require("../questions")
const fs = require("fs")
const { PLUGIN_TYPE_ARR } = require("@budibase/types")
const { validate } = require("@budibase/backend-core/plugins")
const { runPkgCommand } = require("../exec")
const { join } = require("path")
const { success, error, info } = require("../utils")
function checkInPlugin() {
if (!fs.existsSync("package.json")) {
throw new Error(
"Please run in a plugin directory - must contain package.json"
)
}
if (!fs.existsSync("schema.json")) {
throw new Error(
"Please run in a plugin directory - must contain schema.json"
)
}
}
async function init(opts) {
const type = opts["init"] || opts
if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
console.log(
error(
"Please provide a type to init, either 'component' or 'datasource'."
)
)
return
}
console.log(info("Lets get some details about your new plugin:"))
const name = await questions.string("Name", `budibase-${type}`)
if (fs.existsSync(name)) {
console.log(
error("Directory by plugin name already exists, pick a new name.")
)
return
}
const desc = await questions.string(
"Description",
`An amazing Budibase ${type}!`
)
const version = await questions.string("Version", "1.0.0")
// get the skeleton
console.log(info("Retrieving project..."))
await getSkeleton(type, name)
await fleshOutSkeleton(type, name, desc, version)
console.log(info("Installing dependencies..."))
await runPkgCommand("install", join(process.cwd(), name))
console.log(info(`Plugin created in directory "${name}"`))
}
async function verify() {
// will throw errors if not acceptable
checkInPlugin()
console.log(info("Verifying plugin..."))
const schema = fs.readFileSync("schema.json", "utf8")
const pkg = fs.readFileSync("package.json", "utf8")
let name, version
try {
const schemaJson = JSON.parse(schema)
const pkgJson = JSON.parse(pkg)
if (!pkgJson.name || !pkgJson.version || !pkgJson.description) {
throw new Error(
"package.json is missing one of 'name', 'version' or 'description'."
)
}
name = pkgJson.name
version = pkgJson.version
validate(schemaJson)
return { name, version }
} catch (err) {
if (err && err.message && err.message.includes("not valid JSON")) {
console.log(error(`schema.json is not valid JSON: ${err.message}`))
} else {
console.log(error(`Invalid schema/package.json: ${err.message}`))
}
}
}
async function build() {
const verified = await verify()
if (!verified.name) {
return
}
console.log(success("Verified!"))
console.log(info("Building plugin..."))
await runPkgCommand("build")
const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
console.log(success(`Build complete - output in: ${output}`))
}
async function watch() {
const verified = await verify()
if (!verified.name) {
return
}
const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
console.log(info(`Watching - build in: ${output}`))
try {
await runPkgCommand("watch")
} catch (err) {
// always errors when user escapes
console.log(success("Watch exited."))
}
}
const command = new Command(`${CommandWords.PLUGIN}`)
.addHelp(
"Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
)
.addSubOption(
"--init [type]",
"Init a new plugin project, with a type of either component or datasource.",
init
)
.addSubOption(
"--build",
"Build your plugin, this will verify and produce a final tarball for your project.",
build
)
.addSubOption(
"--watch",
"Automatically build any changes to your plugin.",
watch
)
exports.command = command

View File

@ -0,0 +1,60 @@
const fetch = require("node-fetch")
const download = require("download")
const fs = require("fs")
const os = require("os")
const { join } = require("path")
const tar = require("tar")
const { processStringSync } = require("@budibase/string-templates")
const HBS_FILES = ["package.json.hbs", "schema.json.hbs", "README.md.hbs"]
async function getSkeletonUrl(type) {
const resp = await fetch(
"https://api.github.com/repos/budibase/budibase-skeleton/releases/latest"
)
if (resp.status >= 300) {
throw new Error("Failed to retrieve skeleton metadata")
}
const json = await resp.json()
for (let asset of json["assets"]) {
if (asset.name && asset.name.includes(type)) {
return asset["browser_download_url"]
}
}
throw new Error("No skeleton found in latest release.")
}
exports.getSkeleton = async (type, name) => {
const url = await getSkeletonUrl(type)
const tarballFile = join(os.tmpdir(), "skeleton.tar.gz")
// download the full skeleton tarball
fs.writeFileSync(tarballFile, await download(url))
fs.mkdirSync(name)
// extract it and get what we need
await tar.extract({
file: tarballFile,
C: name,
})
// clear up
fs.rmSync(tarballFile)
}
exports.fleshOutSkeleton = async (type, name, description, version) => {
for (let file of HBS_FILES) {
const oldFile = join(name, file),
newFile = join(name, file.substring(0, file.length - 4))
const hbsContents = fs.readFileSync(oldFile, "utf8")
if (!hbsContents) {
continue
}
const output = processStringSync(hbsContents, {
name,
description,
version,
})
// write the updated file and remove the HBS file
fs.writeFileSync(newFile, output)
fs.rmSync(oldFile)
}
}

View File

@ -1,11 +1,15 @@
const os = require("os") const os = require("os")
const { join } = require("path") const { join } = require("path")
const fs = require("fs") const fs = require("fs")
const { error } = require("./utils")
const PREBUILDS = "prebuilds" const PREBUILDS = "prebuilds"
const ARCH = `${os.platform()}-${os.arch()}` const ARCH = `${os.platform()}-${os.arch()}`
const PREBUILD_DIR = join(process.execPath, "..", PREBUILDS, ARCH) const PREBUILD_DIR = join(process.execPath, "..", PREBUILDS, ARCH)
checkForBinaries() // running as built CLI pkg bundle
if (!process.argv[0].includes("node")) {
checkForBinaries()
}
function checkForBinaries() { function checkForBinaries() {
const readDir = join(__filename, "..", "..", PREBUILDS, ARCH) const readDir = join(__filename, "..", "..", PREBUILDS, ARCH)
@ -22,7 +26,15 @@ function checkForBinaries() {
} }
} }
function cleanup() { function cleanup(evt) {
if (evt && evt.errno) {
console.error(
error(
"Failed to run CLI command - please report with the following message:"
)
)
console.error(error(evt))
}
if (fs.existsSync(PREBUILD_DIR)) { if (fs.existsSync(PREBUILD_DIR)) {
fs.rmSync(PREBUILD_DIR, { recursive: true }) fs.rmSync(PREBUILD_DIR, { recursive: true })
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "1.3.4-alpha.2", "version": "1.3.15-alpha.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.4-alpha.2", "@budibase/bbui": "1.3.15-alpha.3",
"@budibase/frontend-core": "1.3.4-alpha.2", "@budibase/frontend-core": "1.3.15-alpha.3",
"@budibase/string-templates": "1.3.4-alpha.2", "@budibase/string-templates": "1.3.15-alpha.3",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",
@ -39,6 +39,7 @@
"sanitize-html": "^2.7.0", "sanitize-html": "^2.7.0",
"screenfull": "^6.0.1", "screenfull": "^6.0.1",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"socket.io-client": "^4.5.1",
"svelte": "^3.49.0", "svelte": "^3.49.0",
"svelte-apexcharts": "^1.0.2", "svelte-apexcharts": "^1.0.2",
"svelte-flatpickr": "^3.1.0", "svelte-flatpickr": "^3.1.0",

View File

@ -89,6 +89,14 @@
}) })
</script> </script>
<svelte:head>
{#if $builderStore.usedPlugins?.length}
{#each $builderStore.usedPlugins as plugin (plugin.hash)}
<script src={`/plugins/${plugin.jsUrl}?r=${plugin.hash || ""}`}></script>
{/each}
{/if}
</svelte:head>
{#if dataLoaded} {#if dataLoaded}
<div <div
id="spectrum-root" id="spectrum-root"

View File

@ -11,8 +11,6 @@
<script> <script>
import { getContext, setContext, onMount, onDestroy } from "svelte" import { getContext, setContext, onMount, onDestroy } from "svelte"
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import * as AppComponents from "components/app"
import Router from "./Router.svelte"
import { import {
enrichProps, enrichProps,
propsAreSame, propsAreSame,
@ -127,7 +125,9 @@
// Empty components are those which accept children but do not have any. // Empty components are those which accept children but do not have any.
// Empty states can be shown for these components, but can be disabled // Empty states can be shown for these components, but can be disabled
// in the component manifest. // in the component manifest.
$: empty = interactive && !children.length && hasChildren $: empty =
(interactive && !children.length && hasChildren) ||
hasMissingRequiredSettings
$: emptyState = empty && showEmptyState $: emptyState = empty && showEmptyState
// Enrich component settings // Enrich component settings
@ -165,14 +165,14 @@
missingRequiredSettings, missingRequiredSettings,
}) })
const initialise = instance => { const initialise = (instance, force = false) => {
if (instance == null) { if (instance == null) {
return return
} }
// Ensure we're processing a new instance // Ensure we're processing a new instance
const instanceKey = Helpers.hashString(JSON.stringify(instance)) const instanceKey = Helpers.hashString(JSON.stringify(instance))
if (instanceKey === lastInstanceKey) { if (instanceKey === lastInstanceKey && !force) {
return return
} else { } else {
lastInstanceKey = instanceKey lastInstanceKey = instanceKey
@ -180,7 +180,7 @@
// Pull definition and constructor // Pull definition and constructor
const component = instance._component const component = instance._component
constructor = getComponentConstructor(component) constructor = componentStore.actions.getComponentConstructor(component)
definition = componentStore.actions.getComponentDefinition(component) definition = componentStore.actions.getComponentDefinition(component)
if (!definition) { if (!definition) {
return return
@ -237,16 +237,6 @@
}) })
} }
// Gets the component constructor for the specified component
const getComponentConstructor = component => {
const split = component?.split("/")
const name = split?.[split.length - 1]
if (name === "screenslot" && !insideScreenslot) {
return Router
}
return AppComponents[name]
}
const getSettingsDefinitionMap = settingsDefinition => { const getSettingsDefinitionMap = settingsDefinition => {
let map = {} let map = {}
settingsDefinition?.forEach(setting => { settingsDefinition?.forEach(setting => {
@ -419,9 +409,11 @@
!componentStore.actions.isComponentRegistered(id) !componentStore.actions.isComponentRegistered(id)
) { ) {
componentStore.actions.registerInstance(id, { componentStore.actions.registerInstance(id, {
component: instance._component,
getSettings: () => cachedSettings, getSettings: () => cachedSettings,
getRawSettings: () => ({ ...staticSettings, ...dynamicSettings }), getRawSettings: () => ({ ...staticSettings, ...dynamicSettings }),
getDataContext: () => get(context), getDataContext: () => get(context),
reload: () => initialise(instance, true),
}) })
} }
}) })

View File

@ -2,28 +2,25 @@
import { getContext } from "svelte" import { getContext } from "svelte"
import { builderStore } from "stores" import { builderStore } from "stores"
const { styleable } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
$: requiredSetting = $component.missingRequiredSettings?.[0] $: requiredSetting = $component.missingRequiredSettings?.[0]
</script> </script>
{#if $builderStore.inBuilder && requiredSetting} {#if $builderStore.inBuilder && requiredSetting}
<div use:styleable={$component.styles}> <div class="component-placeholder">
<div class="component-placeholder"> <span>
<span> Add the <mark>{requiredSetting.label}</mark> setting to start using your component
Add the <mark>{requiredSetting.label}</mark> setting to start using your -
component - </span>
</span> <span
<span class="spectrum-Link"
class="spectrum-Link" on:click={() => {
on:click={() => { builderStore.actions.highlightSetting(requiredSetting.key)
builderStore.actions.highlightSetting(requiredSetting.key) }}
}} >
> Show me
Show me </span>
</span>
</div>
</div> </div>
{/if} {/if}

View File

@ -1,7 +1,6 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { ProgressCircle, Pagination } from "@budibase/bbui" import { ProgressCircle, Pagination } from "@budibase/bbui"
import Placeholder from "./Placeholder.svelte"
import { fetchData, LuceneUtils } from "@budibase/frontend-core" import { fetchData, LuceneUtils } from "@budibase/frontend-core"
export let dataSource export let dataSource
@ -133,11 +132,7 @@
<ProgressCircle /> <ProgressCircle />
</div> </div>
{:else} {:else}
{#if $component.emptyState} <slot />
<Placeholder />
{:else}
<slot />
{/if}
{#if paginate && $fetch.supportsPagination} {#if paginate && $fetch.supportsPagination}
<div class="pagination"> <div class="pagination">
<Pagination <Pagination

View File

@ -19,7 +19,10 @@
label="Active screen" label="Active screen"
value={$screenStore.activeScreen?.routing.route} value={$screenStore.activeScreen?.routing.route}
/> />
<DevToolsStat label="Components" value={$componentStore.mountedComponents} /> <DevToolsStat
label="Components"
value={$componentStore.mountedComponentCount}
/>
<DevToolsStat label="User" value={$authStore.email} /> <DevToolsStat label="User" value={$authStore.email} />
<DevToolsStat label="Role" value={$authStore.roleId} /> <DevToolsStat label="Role" value={$authStore.roleId} />
</Layout> </Layout>

View File

@ -1,7 +1,20 @@
import ClientApp from "./components/ClientApp.svelte" import ClientApp from "./components/ClientApp.svelte"
import { builderStore, appStore, devToolsStore, blockStore } from "./stores" import {
builderStore,
appStore,
devToolsStore,
blockStore,
componentStore,
} from "./stores"
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js" import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
import { get } from "svelte/store" import { get } from "svelte/store"
import { initWebsocket } from "./websocket.js"
// Provide svelte and svelte/internal as globals for custom components
import * as svelte from "svelte"
import * as internal from "svelte/internal"
window.svelte_internal = internal
window.svelte = svelte
// Initialise spectrum icons // Initialise spectrum icons
loadSpectrumIcons() loadSpectrumIcons()
@ -21,6 +34,8 @@ const loadBudibase = () => {
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"], previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"], navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"], hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
location: window["##BUDIBASE_LOCATION##"],
}) })
// Set app ID - this window flag is set by both the preview and the real // Set app ID - this window flag is set by both the preview and the real
@ -43,6 +58,21 @@ const loadBudibase = () => {
} }
} }
// Register any custom components
if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
componentStore.actions.registerCustomComponent(component)
})
}
// Make a callback available for custom component bundles to register
// themselves at runtime
window.registerCustomComponent =
componentStore.actions.registerCustomComponent
// Initialise websocket
initWebsocket()
// Create app if one hasn't been created yet // Create app if one hasn't been created yet
if (!app) { if (!app) {
app = new ClientApp({ app = new ClientApp({

View File

@ -19,6 +19,7 @@ const createBuilderStore = () => {
isDragging: false, isDragging: false,
navigation: null, navigation: null,
hiddenComponentIds: [], hiddenComponentIds: [],
usedPlugins: null,
// Legacy - allow the builder to specify a layout // Legacy - allow the builder to specify a layout
layout: null, layout: null,
@ -87,6 +88,20 @@ const createBuilderStore = () => {
ejectBlock: (id, definition) => { ejectBlock: (id, definition) => {
dispatchEvent("eject-block", { id, definition }) dispatchEvent("eject-block", { id, definition })
}, },
updateUsedPlugin: (name, hash) => {
// Check if we used this plugin
const used = get(store)?.usedPlugins?.find(x => x.name === name)
if (used) {
store.update(state => {
state.usedPlugins = state.usedPlugins.filter(x => x.name !== name)
state.usedPlugins.push({
...used,
hash,
})
return state
})
}
},
} }
return { return {
...store, ...store,

View File

@ -4,9 +4,17 @@ import { findComponentById, findComponentPathById } from "../utils/components"
import { devToolsStore } from "./devTools" import { devToolsStore } from "./devTools"
import { screenStore } from "./screens" import { screenStore } from "./screens"
import { builderStore } from "./builder" import { builderStore } from "./builder"
import Router from "../components/Router.svelte"
import * as AppComponents from "../components/app/index.js"
const budibasePrefix = "@budibase/standard-components/"
const createComponentStore = () => { const createComponentStore = () => {
const store = writable({}) const store = writable({
customComponentManifest: {},
customComponentMap: {},
mountedComponents: {},
})
const derivedStore = derived( const derivedStore = derived(
[store, builderStore, devToolsStore, screenStore], [store, builderStore, devToolsStore, screenStore],
@ -25,41 +33,65 @@ const createComponentStore = () => {
asset = $screenState.activeScreen asset = $screenState.activeScreen
} }
const component = findComponentById(asset?.props, selectedComponentId) const component = findComponentById(asset?.props, selectedComponentId)
const prefix = "@budibase/standard-components/" const definition = getComponentDefinition(component?._component)
const type = component?._component?.replace(prefix, "")
const definition = type ? Manifest[type] : null
// Derive the selected component path // Derive the selected component path
const path = const path =
findComponentPathById(asset?.props, selectedComponentId) || [] findComponentPathById(asset?.props, selectedComponentId) || []
return { return {
selectedComponentInstance: $store[selectedComponentId], customComponentManifest: $store.customComponentManifest,
selectedComponentInstance:
$store.mountedComponents[selectedComponentId],
selectedComponent: component, selectedComponent: component,
selectedComponentDefinition: definition, selectedComponentDefinition: definition,
selectedComponentPath: path?.map(component => component._id), selectedComponentPath: path?.map(component => component._id),
mountedComponents: Object.keys($store).length, mountedComponentCount: Object.keys($store.mountedComponents).length,
currentAsset: asset, currentAsset: asset,
} }
} }
) )
const registerInstance = (id, instance) => { const registerInstance = (id, instance) => {
store.update(state => ({ store.update(state => {
...state, // If this is a custom component, flag it so we can reload this component
[id]: instance, // later if required
})) const component = instance.component
if (component?.startsWith("plugin")) {
if (!state.customComponentMap[component]) {
state.customComponentMap[component] = [id]
} else {
state.customComponentMap[component].push(id)
}
}
// Register to mounted components
state.mountedComponents[id] = instance
return state
})
} }
const unregisterInstance = id => { const unregisterInstance = id => {
store.update(state => { store.update(state => {
delete state[id] // Remove from custom component map if required
const component = state.mountedComponents[id]?.instance?.component
let customComponentMap = state.customComponentMap
if (component?.startsWith("plugin")) {
customComponentMap[component] = customComponentMap[component].filter(
x => {
return x !== id
}
)
}
// Remove from mounted components
delete state.mountedComponents[id]
return state return state
}) })
} }
const isComponentRegistered = id => { const isComponentRegistered = id => {
return get(store)[id] != null return get(store).mountedComponents[id] != null
} }
const getComponentById = id => { const getComponentById = id => {
@ -68,9 +100,66 @@ const createComponentStore = () => {
} }
const getComponentDefinition = type => { const getComponentDefinition = type => {
const prefix = "@budibase/standard-components/" if (!type) {
type = type?.replace(prefix, "") return null
return type ? Manifest[type] : null }
// Screenslot is an edge case
if (type === "screenslot") {
type = `${budibasePrefix}${type}`
}
// Handle built-in components
if (type.startsWith(budibasePrefix)) {
type = type.replace(budibasePrefix, "")
return type ? Manifest[type] : null
}
// Handle custom components
const { customComponentManifest } = get(store)
return customComponentManifest?.[type]?.schema?.schema
}
const getComponentConstructor = type => {
if (!type) {
return null
}
if (type === "screenslot") {
return Router
}
// Handle budibase components
if (type.startsWith(budibasePrefix)) {
const split = type.split("/")
const name = split[split.length - 1]
return AppComponents[name]
}
// Handle custom components
const { customComponentManifest } = get(store)
return customComponentManifest?.[type]?.Component
}
const registerCustomComponent = ({ Component, schema, version }) => {
if (!Component || !schema?.schema?.name || !version) {
return
}
const component = `plugin/${schema.schema.name}`
store.update(state => {
state.customComponentManifest[component] = {
Component,
schema,
}
return state
})
// Reload any mounted instances of this custom component
const state = get(store)
if (state.customComponentMap[component]?.length) {
state.customComponentMap[component].forEach(id => {
state.mountedComponents[id]?.reload()
})
}
} }
return { return {
@ -81,6 +170,8 @@ const createComponentStore = () => {
isComponentRegistered, isComponentRegistered,
getComponentById, getComponentById,
getComponentDefinition, getComponentDefinition,
getComponentConstructor,
registerCustomComponent,
}, },
} }
} }

View File

@ -0,0 +1,26 @@
import { builderStore } from "./stores/index.js"
import { get } from "svelte/store"
import { io } from "socket.io-client"
export const initWebsocket = () => {
const { inBuilder, location } = get(builderStore)
// Only connect when we're inside the builder preview, for now
if (!inBuilder || !location) {
return
}
// Initialise connection
const tls = location.protocol === "https:"
const proto = tls ? "wss:" : "ws:"
const host = location.hostname
const port = location.port || (tls ? 443 : 80)
const socket = io(`${proto}//${host}:${port}`, {
path: "/socket/client",
})
// Event handlers
socket.on("plugin-update", data => {
builderStore.actions.updateUsedPlugin(data.name, data.hash)
})
}

View File

@ -113,6 +113,11 @@
estree-walker "^1.0.1" estree-walker "^1.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@socket.io/component-emitter@~3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
"@spectrum-css/button@^3.0.3": "@spectrum-css/button@^3.0.3":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84" resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84"
@ -469,6 +474,13 @@ dayjs@^1.10.5:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.8.tgz#267df4bc6276fcb33c04a6735287e3f429abec41" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.8.tgz#267df4bc6276fcb33c04a6735287e3f429abec41"
integrity sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow== integrity sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow==
debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
deepmerge@^4.2.2: deepmerge@^4.2.2:
version "4.2.2" version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
@ -536,6 +548,22 @@ emojis-list@^3.0.0:
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
engine.io-client@~6.2.1:
version "6.2.2"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.2.tgz#c6c5243167f5943dcd9c4abee1bfc634aa2cbdd0"
integrity sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
engine.io-parser "~5.0.3"
ws "~8.2.3"
xmlhttprequest-ssl "~2.0.0"
engine.io-parser@~5.0.3:
version "5.0.4"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0"
integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
entities@^2.0.0: entities@^2.0.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
@ -824,6 +852,11 @@ minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanoid@^2.1.0: nanoid@^2.1.0:
version "2.1.11" version "2.1.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
@ -1389,6 +1422,24 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
socket.io-client@^4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.1.tgz#cab8da71976a300d3090414e28c2203a47884d84"
integrity sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.2"
engine.io-client "~6.2.1"
socket.io-parser "~4.2.0"
socket.io-parser@~4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5"
integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
source-map-js@^1.0.1, source-map-js@^1.0.2: source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@ -1598,6 +1649,16 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@~8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
xmlhttprequest-ssl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
y18n@^5.0.5: y18n@^5.0.5:
version "5.0.8" version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"

View File

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "1.3.4-alpha.2", "version": "1.3.15-alpha.3",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.4-alpha.2", "@budibase/bbui": "1.3.15-alpha.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View File

@ -11,10 +11,14 @@ export const buildDatasourceEndpoints = API => ({
/** /**
* Prompts the server to build the schema for a datasource. * Prompts the server to build the schema for a datasource.
* @param datasourceId the datasource ID to build the schema for * @param datasourceId the datasource ID to build the schema for
* @param tablesFilter list of specific table names to be build the schema
*/ */
buildDatasourceSchema: async datasourceId => { buildDatasourceSchema: async ({ datasourceId, tablesFilter }) => {
return await API.post({ return await API.post({
url: `/api/datasources/${datasourceId}/schema`, url: `/api/datasources/${datasourceId}/schema`,
body: {
tablesFilter,
},
}) })
}, },

View File

@ -24,6 +24,7 @@ import { buildSelfEndpoints } from "./self"
import { buildViewEndpoints } from "./views" import { buildViewEndpoints } from "./views"
import { buildLicensingEndpoints } from "./licensing" import { buildLicensingEndpoints } from "./licensing"
import { buildGroupsEndpoints } from "./groups" import { buildGroupsEndpoints } from "./groups"
import { buildPluginEndpoints } from "./plugins"
const defaultAPIClientConfig = { const defaultAPIClientConfig = {
/** /**
@ -243,5 +244,6 @@ export const createAPIClient = config => {
...buildSelfEndpoints(API), ...buildSelfEndpoints(API),
...buildLicensingEndpoints(API), ...buildLicensingEndpoints(API),
...buildGroupsEndpoints(API), ...buildGroupsEndpoints(API),
...buildPluginEndpoints(API),
} }
} }

View File

@ -0,0 +1,44 @@
export const buildPluginEndpoints = API => ({
/**
* Uploads a plugin tarball bundle
* @param data the plugin tarball bundle to upload
*/
uploadPlugin: async data => {
return await API.post({
url: `/api/plugin/upload`,
body: data,
json: false,
})
},
/**
* Creates a plugin from URL, Github or NPM
*/
createPlugin: async data => {
return await API.post({
url: `/api/plugin`,
body: data,
})
},
/**
* Gets a list of all plugins
*/
getPlugins: async () => {
return await API.get({
url: "/api/plugin",
})
},
/**
* Deletes a plugin.
* @param pluginId the ID of the plugin to delete
*
* * @param pluginId the revision of the plugin to delete
*/
deletePlugin: async pluginId => {
return await API.delete({
url: `/api/plugin/${pluginId}`,
})
},
})

View File

@ -80,6 +80,19 @@ const cleanupQuery = query => {
return query return query
} }
/**
* Removes a numeric prefix on field names designed to give fields uniqueness
*/
const removeKeyNumbering = key => {
if (typeof key === "string" && key.match(/\d[0-9]*:/g) != null) {
const parts = key.split(":")
parts.shift()
return parts.join(":")
} else {
return key
}
}
/** /**
* Builds a lucene JSON query from the filter structure generated in the builder * Builds a lucene JSON query from the filter structure generated in the builder
* @param filter the builder filter structure * @param filter the builder filter structure
@ -194,7 +207,7 @@ export const runLuceneQuery = (docs, query) => {
const filters = Object.entries(query[type] || {}) const filters = Object.entries(query[type] || {})
for (let i = 0; i < filters.length; i++) { for (let i = 0; i < filters.length; i++) {
const [key, testValue] = filters[i] const [key, testValue] = filters[i]
const docValue = Helpers.deepGet(doc, key) const docValue = Helpers.deepGet(doc, removeKeyNumbering(key))
if (failFn(docValue, testValue)) { if (failFn(docValue, testValue)) {
return false return false
} }

View File

@ -33,7 +33,7 @@ module MongoMock {
}) })
} }
mongodb.ObjectID = require("mongodb").ObjectID mongodb.ObjectID = jest.requireActual("mongodb").ObjectID
module.exports = mongodb module.exports = mongodb
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.3.4-alpha.2", "version": "1.3.15-alpha.3",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -77,11 +77,11 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.0.3", "@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "1.3.4-alpha.2", "@budibase/backend-core": "1.3.15-alpha.3",
"@budibase/client": "1.3.4-alpha.2", "@budibase/client": "1.3.15-alpha.3",
"@budibase/pro": "1.3.4-alpha.2", "@budibase/pro": "1.3.15-alpha.3",
"@budibase/string-templates": "1.3.4-alpha.2", "@budibase/string-templates": "1.3.15-alpha.3",
"@budibase/types": "1.3.4-alpha.2", "@budibase/types": "1.3.15-alpha.3",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",
@ -95,6 +95,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bull": "4.8.5", "bull": "4.8.5",
"chmodr": "1.2.0", "chmodr": "1.2.0",
"chokidar": "3.5.3",
"csvtojson": "2.0.10", "csvtojson": "2.0.10",
"curlconverter": "3.21.0", "curlconverter": "3.21.0",
"dotenv": "8.2.0", "dotenv": "8.2.0",
@ -138,8 +139,10 @@
"redis": "4", "redis": "4",
"server-destroy": "1.0.1", "server-destroy": "1.0.1",
"snowflake-promise": "^4.5.0", "snowflake-promise": "^4.5.0",
"socket.io": "^4.5.1",
"svelte": "3.49.0", "svelte": "3.49.0",
"swagger-parser": "10.0.3", "swagger-parser": "10.0.3",
"tar": "6.1.11",
"to-json-schema": "0.2.5", "to-json-schema": "0.2.5",
"uuid": "3.3.2", "uuid": "3.3.2",
"validate.js": "0.13.1", "validate.js": "0.13.1",

View File

@ -58,6 +58,7 @@ async function init() {
DEPLOYMENT_ENVIRONMENT: "development", DEPLOYMENT_ENVIRONMENT: "development",
BB_ADMIN_USER_EMAIL: "", BB_ADMIN_USER_EMAIL: "",
BB_ADMIN_USER_PASSWORD: "", BB_ADMIN_USER_PASSWORD: "",
PLUGINS_DIR: "",
} }
let envFile = "" let envFile = ""
Object.keys(envFileJson).forEach(key => { Object.keys(envFileJson).forEach(key => {

Some files were not shown because too many files have changed in this diff Show More