From afe525fb0f26d69e89726b1847b7dbf2026389cc Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 27 May 2021 11:11:44 +0100 Subject: [PATCH 01/33] execute bash commands in automations --- packages/server/src/automations/actions.js | 3 + packages/server/src/automations/steps/bash.js | 56 +++++++++++++++++++ .../src/automations/steps/executeScript.js | 2 +- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/automations/steps/bash.js diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index ad102e7b67..762ce677a7 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -4,6 +4,7 @@ const createRow = require("./steps/createRow") const updateRow = require("./steps/updateRow") const deleteRow = require("./steps/deleteRow") const executeScript = require("./steps/executeScript") +const bash = require("./steps/bash") const executeQuery = require("./steps/executeQuery") const outgoingWebhook = require("./steps/outgoingWebhook") const env = require("../environment") @@ -21,6 +22,7 @@ const BUILTIN_ACTIONS = { DELETE_ROW: deleteRow.run, OUTGOING_WEBHOOK: outgoingWebhook.run, EXECUTE_SCRIPT: executeScript.run, + EXECUTE_BASH: bash.run, EXECUTE_QUERY: executeQuery.run, } const BUILTIN_DEFINITIONS = { @@ -32,6 +34,7 @@ const BUILTIN_DEFINITIONS = { OUTGOING_WEBHOOK: outgoingWebhook.definition, EXECUTE_SCRIPT: executeScript.definition, EXECUTE_QUERY: executeQuery.definition, + EXECUTE_BASH: bash.definition, } let MANIFEST = null diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js new file mode 100644 index 0000000000..e71c70368b --- /dev/null +++ b/packages/server/src/automations/steps/bash.js @@ -0,0 +1,56 @@ +const scriptController = require("../../api/controllers/script") +const { execSync } = require("child_process") +const { processStringSync } = require("@budibase/string-templates") + +module.exports.definition = { + name: "Bash Scripting", + tagline: "Execute a bash command", + icon: "ri-terminal-box-line", + description: "Run a bash script", + type: "ACTION", + stepId: "EXECUTE_BASH", + inputs: {}, + schema: { + inputs: { + properties: { + code: { + type: "string", + customType: "code", + title: "Code", + }, + }, + required: ["code"], + }, + outputs: { + properties: { + stdout: { + type: "string", + description: "Standard output of your bash command or script.", + }, + }, + }, + required: ["stdout"], + }, +} + +module.exports.run = async function ({ inputs, context }) { + if (inputs.code == null) { + return { + stdout: "Budibase bash automation failed: Invalid inputs", + } + } + + try { + const command = processStringSync(inputs.code, context) + const stdout = execSync(command, { timeout: 1000 }) + return { + stdout, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/automations/steps/executeScript.js b/packages/server/src/automations/steps/executeScript.js index 5bea1ab151..33ffd3ee8e 100644 --- a/packages/server/src/automations/steps/executeScript.js +++ b/packages/server/src/automations/steps/executeScript.js @@ -1,7 +1,7 @@ const scriptController = require("../../api/controllers/script") module.exports.definition = { - name: "Scripting", + name: "JS Scripting", tagline: "Execute JavaScript Code", icon: "ri-terminal-box-line", description: "Run a piece of JavaScript code in your automation", From f5d829015a565cf36ede95bd9488efa0b83fe62f Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 27 May 2021 11:47:02 +0100 Subject: [PATCH 02/33] piping errors to stderr when timeout exceeded --- packages/server/src/automations/steps/bash.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js index e71c70368b..eb7ce605d2 100644 --- a/packages/server/src/automations/steps/bash.js +++ b/packages/server/src/automations/steps/bash.js @@ -42,7 +42,14 @@ module.exports.run = async function ({ inputs, context }) { try { const command = processStringSync(inputs.code, context) - const stdout = execSync(command, { timeout: 1000 }) + + let stdout + try { + stdout = execSync(command, { timeout: 500 }) + } catch (err) { + stdout = err.message + } + return { stdout, } From a367acfd74686a0eae3b9da120be1532d509e8e5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 27 May 2021 15:11:08 +0100 Subject: [PATCH 03/33] Allow data providers to inherit each other and add full client side lucene implementation --- .../builder/src/builderStore/dataBinding.js | 12 +- .../screenTemplates/utils/commonComponents.js | 2 +- .../PropertyControls/DataSourceSelect.svelte | 31 +++- .../EventsEditor/actions/SaveRow.svelte | 6 +- .../PropertyControls/FieldSelect.svelte | 2 +- .../FilterEditor/FilterEditor.svelte | 30 +--- .../PropertyControls/FormFieldSelect.svelte | 2 +- .../PropertyControls/MultiFieldSelect.svelte | 2 +- .../src/DataProvider.svelte | 106 +++++-------- packages/standard-components/src/lucene.js | 140 ++++++++++++++++++ 10 files changed, 222 insertions(+), 111 deletions(-) create mode 100644 packages/standard-components/src/lucene.js diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 89532e3bc3..5ce8e407c1 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -136,7 +136,7 @@ const getContextBindings = (asset, componentId) => { if (!datasource) { return } - const info = getSchemaForDatasource(datasource) + const info = getSchemaForDatasource(asset, datasource) schema = info.schema readablePrefix = info.table?.name } @@ -191,7 +191,7 @@ const getContextBindings = (asset, componentId) => { */ const getUserBindings = () => { let bindings = [] - const { schema } = getSchemaForDatasource({ + const { schema } = getSchemaForDatasource(null, { type: "table", tableId: TableNames.USERS, }) @@ -244,11 +244,15 @@ const getUrlBindings = asset => { /** * Gets a schema for a datasource object. */ -export const getSchemaForDatasource = (datasource, isForm = false) => { +export const getSchemaForDatasource = (asset, datasource, isForm = false) => { let schema, table if (datasource) { const { type } = datasource - if (type === "query") { + if (type === "provider") { + const component = findComponent(asset.props, datasource.providerId) + const source = getDatasourceForProvider(asset, component) + return getSchemaForDatasource(asset, source, isForm) + } else if (type === "query") { const queries = get(queriesStores).list table = queries.find(query => query._id === datasource._id) } else { diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index 02b3c20a2f..e234a1a770 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -174,7 +174,7 @@ const fieldTypeToComponentMap = { } export function makeDatasourceFormComponents(datasource) { - const { schema } = getSchemaForDatasource(datasource, true) + const { schema } = getSchemaForDatasource(null, datasource, true) let components = [] let fields = Object.keys(schema || {}) fields.forEach(field => { diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte index 1c9ab9767c..91578dd313 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte @@ -1,5 +1,8 @@ {#if $auth.isAdmin} @@ -130,10 +131,10 @@ +
+ +
-
- -
{/if} diff --git a/packages/builder/src/pages/builder/portal/settings/update.svelte b/packages/builder/src/pages/builder/portal/settings/update.svelte new file mode 100644 index 0000000000..a0eaa987cd --- /dev/null +++ b/packages/builder/src/pages/builder/portal/settings/update.svelte @@ -0,0 +1,70 @@ + + +{#if $auth.isAdmin} + + + Update + + Keep your budibase installation up to date to take advantage of the latest features, security updates and much more. + + + +
+
+ +
+
+
+{/if} + + diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 332b917a76..6c4188a5dc 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const { buildAuthMiddleware } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth const currentApp = require("../middleware/currentapp") const compress = require("koa-compress") const zlib = require("zlib") @@ -37,6 +37,7 @@ router }) ) .use(currentApp) + .use(auditLog) // error handling middleware router.use(async (ctx, next) => { diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 50df056b2a..5772eefad3 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -5,7 +5,7 @@ require("@budibase/auth").init(CouchDB) const Koa = require("koa") const destroyable = require("server-destroy") const koaBody = require("koa-body") -const logger = require("koa-pino-logger") +const pino = require("koa-pino-logger") const http = require("http") const api = require("./api") const eventEmitter = require("./events") @@ -28,14 +28,14 @@ app.use( }) ) -app.use( - logger({ - prettyPrint: { - levelFirst: true, - }, - level: env.LOG_LEVEL || "error", - }) -) +let logger = pino({ + prettyPrint: { + levelFirst: true, + }, + level: env.LOG_LEVEL || "error", +}) + +app.use(logger) if (!env.isTest()) { const bullApp = bullboard.init() diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js index eb7ce605d2..76d6713c5b 100644 --- a/packages/server/src/automations/steps/bash.js +++ b/packages/server/src/automations/steps/bash.js @@ -1,4 +1,3 @@ -const scriptController = require("../../api/controllers/script") const { execSync } = require("child_process") const { processStringSync } = require("@budibase/string-templates") diff --git a/packages/server/src/utilities/redis.js b/packages/server/src/utilities/redis.js index ae18b82e02..e1fa632003 100644 --- a/packages/server/src/utilities/redis.js +++ b/packages/server/src/utilities/redis.js @@ -12,8 +12,8 @@ exports.init = async () => { } exports.shutdown = async () => { - await devAppClient.finish() - await debounceClient.finish() + if (devAppClient) await devAppClient.finish() + if (debounceClient) await debounceClient.finish() } exports.doesUserHaveLock = async (devAppId, user) => { diff --git a/packages/worker/package.json b/packages/worker/package.json index 7a51c8ac49..b234990966 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -26,6 +26,7 @@ "@koa/router": "^8.0.0", "aws-sdk": "^2.811.0", "bcryptjs": "^2.4.3", + "dockerode": "^3.3.0", "dotenv": "^8.2.0", "got": "^11.8.1", "joi": "^17.4.0", diff --git a/packages/worker/src/api/controllers/admin/debug.js b/packages/worker/src/api/controllers/admin/debug.js new file mode 100644 index 0000000000..a96bf00c83 --- /dev/null +++ b/packages/worker/src/api/controllers/admin/debug.js @@ -0,0 +1,4 @@ +exports.fetchDebugLogs = async ctx => { + // read them from file + // serve them +} diff --git a/packages/worker/src/api/controllers/admin/updates.js b/packages/worker/src/api/controllers/admin/updates.js new file mode 100644 index 0000000000..a41beaf50c --- /dev/null +++ b/packages/worker/src/api/controllers/admin/updates.js @@ -0,0 +1 @@ +exports.updateSystem = async ctx => {} diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index 1b2586a6b6..d456778d54 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -2,7 +2,7 @@ const Router = require("@koa/router") const compress = require("koa-compress") const zlib = require("zlib") const { routes } = require("./routes") -const { buildAuthMiddleware } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth const PUBLIC_ENDPOINTS = [ { @@ -62,6 +62,7 @@ router } return next() }) + .use(auditLog) // error handling middleware router.use(async (ctx, next) => { diff --git a/packages/worker/src/api/routes/admin/debug.js b/packages/worker/src/api/routes/admin/debug.js new file mode 100644 index 0000000000..ee8e490d0e --- /dev/null +++ b/packages/worker/src/api/routes/admin/debug.js @@ -0,0 +1,9 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/admin/debug") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router.get("/api/admin/debug/logs", adminOnly, controller.fetchDebugLogs) + +module.exports = router diff --git a/packages/worker/src/api/routes/admin/updates.js b/packages/worker/src/api/routes/admin/updates.js new file mode 100644 index 0000000000..a89ce8b68a --- /dev/null +++ b/packages/worker/src/api/routes/admin/updates.js @@ -0,0 +1,9 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/admin/updates") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router.get("/api/admin/update", adminOnly, controller.updateSystem) + +module.exports = router diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js index 8b232f7b7c..ae83a714db 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -5,6 +5,7 @@ const templateRoutes = require("./admin/templates") const emailRoutes = require("./admin/email") const authRoutes = require("./admin/auth") const roleRoutes = require("./admin/roles") +const updatesRoutes = require("./admin/updates") const appRoutes = require("./app") exports.routes = [ @@ -16,4 +17,5 @@ exports.routes = [ templateRoutes, emailRoutes, roleRoutes, + updatesRoutes, ] diff --git a/packages/worker/src/utilities/redis.js b/packages/worker/src/utilities/redis.js index 28162a0c14..0701e500dd 100644 --- a/packages/worker/src/utilities/redis.js +++ b/packages/worker/src/utilities/redis.js @@ -51,8 +51,8 @@ exports.init = async () => { * make sure redis connection is closed. */ exports.shutdown = async () => { - await pwResetClient.finish() - await invitationClient.finish() + if (pwResetClient) await pwResetClient.finish() + if (invitationClient) await invitationClient.finish() } /** diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index ce1185f832..9f17e18bc9 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -910,7 +910,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -asn1@~0.2.3: +asn1@~0.2.0, asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== @@ -1076,7 +1076,7 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -bcrypt-pbkdf@^1.0.0: +bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= @@ -1093,6 +1093,15 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -1337,6 +1346,11 @@ chokidar@^3.2.2: optionalDependencies: fsevents "~2.3.1" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -1773,6 +1787,24 @@ diff-sequences@^26.6.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +docker-modem@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.0.tgz#cb912ad8daed42f858269fb3be6944df281ec12d" + integrity sha512-WwFajJ8I5geZ/dDZ5FDMDA6TBkWa76xWwGIGw8uzUjNUGCN0to83wJ8Oi1AxrJTC0JBn+7fvIxUctnawtlwXeg== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^0.8.7" + +dockerode@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.0.tgz#bedaf48ef9fa9124275a54a9881a92374c51008e" + integrity sha512-St08lfOjpYCOXEM8XA0VLu3B3hRjtddODphNW5GFoA0AS3JHgoPQKOz0Qmdzg3P+hUPxhb02g1o1Cu1G+U3lRg== + dependencies: + docker-modem "^3.0.0" + tar-fs "~2.0.1" + domexception@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" @@ -1857,7 +1889,7 @@ encoding-down@^6.3.0: level-codec "^9.0.0" level-errors "^2.0.0" -end-of-stream@^1.1.0: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -2238,6 +2270,11 @@ fresh@~0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3998,6 +4035,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -4829,7 +4871,7 @@ readable-stream@1.1.14: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -5326,6 +5368,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -5345,6 +5392,22 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +ssh2-streams@~0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" + integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ== + dependencies: + asn1 "~0.2.0" + bcrypt-pbkdf "^1.0.2" + streamsearch "~0.1.2" + +ssh2@^0.8.7: + version "0.8.9" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3" + integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw== + dependencies: + ssh2-streams "~0.4.10" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -5390,6 +5453,11 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +streamsearch@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -5529,6 +5597,27 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +tar-fs@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" From 6c2f0f5917fc466e2392eea094ffd95c395b1d36 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 28 May 2021 11:59:04 +0100 Subject: [PATCH 05/33] Cleansing input rows against the table schema. --- packages/server/src/utilities/rowProcessor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index 2267c9e986..ffcbf48d25 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -171,8 +171,9 @@ exports.inputProcessing = (user = {}, table, row) => { const copiedTable = cloneDeep(table) for (let [key, value] of Object.entries(clonedRow)) { const field = table.schema[key] + // cleanse fields that aren't in the schema if (!field) { - continue + delete clonedRow[key] } clonedRow[key] = exports.coerce(value, field.type) } From 4b6fd4149b86d7954660ce2a7c24df07fde30af1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 28 May 2021 12:04:15 +0100 Subject: [PATCH 06/33] Making sure certain fields aren't cleansed. --- packages/server/src/utilities/rowProcessor.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index ffcbf48d25..c93cc687a7 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -169,11 +169,15 @@ exports.inputProcessing = (user = {}, table, row) => { let clonedRow = cloneDeep(row) // need to copy the table so it can be differenced on way out const copiedTable = cloneDeep(table) + const dontCleanseKeys = ["type", "_id", "_rev", "tableId"] for (let [key, value] of Object.entries(clonedRow)) { const field = table.schema[key] // cleanse fields that aren't in the schema if (!field) { - delete clonedRow[key] + if (copiedTable.indexOf(key) === -1) { + delete clonedRow[key] + } + continue } clonedRow[key] = exports.coerce(value, field.type) } From 28099d64548f209dfb05ca40faa2dc27832093cb Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 28 May 2021 12:05:03 +0100 Subject: [PATCH 07/33] Fixing bug. --- packages/server/src/utilities/rowProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index c93cc687a7..807563d47e 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -174,7 +174,7 @@ exports.inputProcessing = (user = {}, table, row) => { const field = table.schema[key] // cleanse fields that aren't in the schema if (!field) { - if (copiedTable.indexOf(key) === -1) { + if (dontCleanseKeys.indexOf(key) === -1) { delete clonedRow[key] } continue From c6827e8a75500ac81ce922d1b3d421cf6106c8c3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 1 Jun 2021 14:59:42 +0100 Subject: [PATCH 08/33] Improve client performance and add action to control data provider queries --- .../design/AppPreview/componentStructure.json | 3 +- .../client/src/components/Component.svelte | 4 +- packages/client/src/constants.js | 1 + packages/client/src/utils/componentProps.js | 4 +- .../client/src/utils/enrichDataBinding.js | 6 +- packages/standard-components/manifest.json | 18 ++ packages/standard-components/package.json | 1 + .../src/DataProvider.svelte | 25 +- .../src/DateRangePicker.svelte | 81 ++++++ packages/standard-components/src/index.js | 1 + packages/standard-components/src/lucene.js | 14 +- packages/standard-components/yarn.lock | 255 ++++++++++++++++++ packages/string-templates/src/index.cjs | 4 +- 13 files changed, 400 insertions(+), 17 deletions(-) create mode 100644 packages/standard-components/src/DateRangePicker.svelte diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index 5bd2e01d25..53b6699fb4 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -18,7 +18,8 @@ "longformfield", "datetimefield", "attachmentfield", - "relationshipfield" + "relationshipfield", + "daterangepicker" ] }, { diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index b32222ce2f..4d2dddffcc 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -55,13 +55,13 @@ } // Enriches any string component props using handlebars - const updateComponentProps = async (definition, context) => { + const updateComponentProps = (definition, context) => { // Record the timestamp so we can reference it after enrichment latestUpdateTime = Date.now() const enrichmentTime = latestUpdateTime // Enrich props with context - const enrichedProps = await enrichProps(definition, context) + const enrichedProps = enrichProps(definition, context) // Abandon this update if a newer update has started if (enrichmentTime !== latestUpdateTime) { diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index 3aa302bec9..31ac4b285e 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -5,4 +5,5 @@ export const TableNames = { export const ActionTypes = { ValidateForm: "ValidateForm", RefreshDatasource: "RefreshDatasource", + SetDataProviderQuery: "SetDataProviderQuery", } diff --git a/packages/client/src/utils/componentProps.js b/packages/client/src/utils/componentProps.js index 559fc54486..14516fdb4c 100644 --- a/packages/client/src/utils/componentProps.js +++ b/packages/client/src/utils/componentProps.js @@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => { * Enriches component props. * Data bindings are enriched, and button actions are enriched. */ -export const enrichProps = async (props, context) => { +export const enrichProps = (props, context) => { // Exclude all private props that start with an underscore let validProps = {} Object.entries(props) @@ -41,7 +41,7 @@ export const enrichProps = async (props, context) => { } // Enrich all data bindings in top level props - let enrichedProps = await enrichDataBindings(validProps, totalContext) + let enrichedProps = enrichDataBindings(validProps, totalContext) // Enrich click actions if they exist if (enrichedProps.onClick) { diff --git a/packages/client/src/utils/enrichDataBinding.js b/packages/client/src/utils/enrichDataBinding.js index b8c5020c74..34bfb78539 100644 --- a/packages/client/src/utils/enrichDataBinding.js +++ b/packages/client/src/utils/enrichDataBinding.js @@ -1,5 +1,5 @@ import { cloneDeep } from "lodash/fp" -import { processString, processObject } from "@budibase/string-templates" +import { processString, processObjectSync } from "@budibase/string-templates" // Regex to test inputs with to see if they are likely candidates for template strings const looksLikeTemplate = /{{.*}}/ @@ -23,6 +23,6 @@ export const enrichDataBinding = async (input, context) => { * Recursively enriches all props in a props object and returns the new props. * Props are deeply cloned so that no mutation is done to the source object. */ -export const enrichDataBindings = async (props, context) => { - return await processObject(cloneDeep(props), context) +export const enrichDataBindings = (props, context) => { + return processObjectSync(cloneDeep(props), context) } diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index 9f67af6489..71a64cb2b1 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -1505,5 +1505,23 @@ "context": { "type": "schema" } + }, + "daterangepicker": { + "name": "Date Range", + "icon": "Date", + "styleable": true, + "hasChildren": false, + "settings": [ + { + "type": "dataProvider", + "label": "Provider", + "key": "dataProvider" + }, + { + "type": "field", + "label": "Date field", + "key": "field" + } + ] } } diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 610535d5b2..ba05de829f 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -37,6 +37,7 @@ "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "apexcharts": "^3.22.1", + "dayjs": "^1.10.5", "svelte-apexcharts": "^1.0.2", "svelte-flatpickr": "^3.1.0" } diff --git a/packages/standard-components/src/DataProvider.svelte b/packages/standard-components/src/DataProvider.svelte index ece8dfad81..cb1ae1c12c 100644 --- a/packages/standard-components/src/DataProvider.svelte +++ b/packages/standard-components/src/DataProvider.svelte @@ -17,6 +17,10 @@ const { API, styleable, Provider, ActionTypes } = getContext("sdk") const component = getContext("component") + const dataProviderApi = { + setLuceneQuery: newQuery => (query = newQuery), + foo: "bar", + } // Loading flag every time data is being fetched let loading = false @@ -31,9 +35,11 @@ let schema = {} let bookmarks = [null] let pageNumber = 0 + let query = null - $: internalTable = dataSource?.type === "table" $: query = buildLuceneQuery(filter) + $: internalTable = dataSource?.type === "table" + $: nestedProvider = dataSource?.type === "provider" $: hasNextPage = bookmarks[pageNumber + 1] != null $: hasPrevPage = pageNumber > 0 $: getSchema(dataSource) @@ -71,8 +77,21 @@ callback: () => refresh(), metadata: { dataSource }, }, + { + type: ActionTypes.SetDataProviderQuery, + callback: newQuery => (query = newQuery), + }, ] - $: dataContext = { rows, schema, rowsLength: rows.length } + $: dataContext = { + rows, + schema, + rowsLength: rows.length, + + // Undocumented properties. These aren't supposed to be used in builder + // bindings, but are used internally by other components + id: $component?.id, + state: { query }, + } const getSortType = (schema, sortColumn) => { if (!schema || !sortColumn || !schema[sortColumn]) { @@ -83,7 +102,7 @@ } const refresh = async () => { - if (schemaLoaded) { + if (schemaLoaded && !nestedProvider) { fetchData( dataSource, query, diff --git a/packages/standard-components/src/DateRangePicker.svelte b/packages/standard-components/src/DateRangePicker.svelte new file mode 100644 index 0000000000..ef5b4d1029 --- /dev/null +++ b/packages/standard-components/src/DateRangePicker.svelte @@ -0,0 +1,81 @@ + + +
+ + {:else if field.type === FORMULA_TYPE} Date: Fri, 4 Jun 2021 21:34:41 +0100 Subject: [PATCH 31/33] Managing the finding of old revision in older apps. --- packages/server/src/api/controllers/application.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 70b63c977a..fc09077edc 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -99,12 +99,18 @@ async function createInstance(template) { // replicate the template data to the instance DB // this is currently very hard to test, downloading and importing template files /* istanbul ignore next */ + let _rev if (template && template.useTemplate === "true") { const { ok } = await db.load(await getTemplateStream(template)) if (!ok) { throw "Error loading database dump from template." } - var { _rev } = await db.get(DocumentTypes.APP_METADATA) + try { + const response = await db.get(DocumentTypes.APP_METADATA) + _rev = response._rev + } catch (err) { + _rev = null + } } else { // create the users table await db.put(USERS_TABLE_SCHEMA) From d6808f48507dd54cb142b01f6d929c616bcaee31 Mon Sep 17 00:00:00 2001 From: Budibase Release Bot <> Date: Fri, 4 Jun 2021 20:56:30 +0000 Subject: [PATCH 32/33] v0.9.27 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 10 +++++----- packages/standard-components/package.json | 4 ++-- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index 0039de33e3..105efb7cd5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.26", + "version": "0.9.27", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 933a79c52f..d2704c8618 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.26", + "version": "0.9.27", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 72f435a656..834e9dcd60 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.26", + "version": "0.9.27", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 28bd2f1780..96a5ac7bf3 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.26", + "version": "0.9.27", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.26", - "@budibase/client": "^0.9.26", + "@budibase/bbui": "^0.9.27", + "@budibase/client": "^0.9.27", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.26", + "@budibase/string-templates": "^0.9.27", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index c87ae70a5d..519db352f3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.26", + "version": "0.9.27", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index bebcb36537..ee022f6558 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.26", + "version": "0.9.27", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -18,13 +18,13 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/string-templates": "^0.9.26", + "@budibase/string-templates": "^0.9.27", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.9.26", + "@budibase/standard-components": "^0.9.27", "@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-node-resolve": "^11.2.1", "fs-extra": "^8.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index 0c35405aa4..4d1691d37e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.26", + "version": "0.9.27", "description": "Budibase Web Server", "main": "src/electron.js", "repository": { @@ -55,9 +55,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.26", - "@budibase/client": "^0.9.26", - "@budibase/string-templates": "^0.9.26", + "@budibase/auth": "^0.9.27", + "@budibase/client": "^0.9.27", + "@budibase/string-templates": "^0.9.27", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", @@ -109,7 +109,7 @@ "devDependencies": { "@babel/core": "^7.14.3", "@babel/preset-env": "^7.14.4", - "@budibase/standard-components": "^0.9.26", + "@budibase/standard-components": "^0.9.27", "@jest/test-sequencer": "^24.8.0", "babel-jest": "^27.0.2", "docker-compose": "^0.23.6", diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 0495ba4369..b871b3949d 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -29,11 +29,11 @@ "keywords": [ "svelte" ], - "version": "0.9.26", + "version": "0.9.27", "license": "MIT", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "dependencies": { - "@budibase/bbui": "^0.9.26", + "@budibase/bbui": "^0.9.27", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "apexcharts": "^3.22.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 9f84df4bf8..fee42bf6f7 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.26", + "version": "0.9.27", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 783cb68d66..e4e5c218ad 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.26", + "version": "0.9.27", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -21,8 +21,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.26", - "@budibase/string-templates": "^0.9.26", + "@budibase/auth": "^0.9.27", + "@budibase/string-templates": "^0.9.27", "@koa/router": "^8.0.0", "aws-sdk": "^2.811.0", "bcryptjs": "^2.4.3", From a481f807dda73e6ef5f61a09328bd48636a88cba Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 7 Jun 2021 09:41:27 +0100 Subject: [PATCH 33/33] Fix radio group opacity on firefox on ubuntu --- packages/bbui/src/Form/Core/RadioGroup.svelte | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/bbui/src/Form/Core/RadioGroup.svelte b/packages/bbui/src/Form/Core/RadioGroup.svelte index 4ead9ed311..d7941b2518 100644 --- a/packages/bbui/src/Form/Core/RadioGroup.svelte +++ b/packages/bbui/src/Form/Core/RadioGroup.svelte @@ -37,3 +37,9 @@ {/each} {/if}
+ +