Merge branch 'develop' of github.com:Budibase/budibase into dnd-improvements
This commit is contained in:
commit
feaa8f9184
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -6,6 +6,7 @@ const {
|
||||||
updateAppId,
|
updateAppId,
|
||||||
doInAppContext,
|
doInAppContext,
|
||||||
doInTenant,
|
doInTenant,
|
||||||
|
doInContext,
|
||||||
} = require("./src/context")
|
} = require("./src/context")
|
||||||
|
|
||||||
const identity = require("./src/context/identity")
|
const identity = require("./src/context/identity")
|
||||||
|
@ -19,4 +20,5 @@ module.exports = {
|
||||||
doInAppContext,
|
doInAppContext,
|
||||||
doInTenant,
|
doInTenant,
|
||||||
identity,
|
identity,
|
||||||
|
doInContext,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"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,7 @@
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/types": "1.4.18-alpha.1",
|
"@budibase/types": "2.0.14-alpha.0",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@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",
|
||||||
|
|
|
@ -65,7 +65,16 @@ export const getTenantIDFromAppID = (appId: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// used for automations, API endpoints should always be in context already
|
export const doInContext = async (appId: string, task: any) => {
|
||||||
|
// gets the tenant ID from the app ID
|
||||||
|
const tenantId = getTenantIDFromAppID(appId)
|
||||||
|
return doInTenant(tenantId, async () => {
|
||||||
|
return doInAppContext(appId, async () => {
|
||||||
|
return task()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const doInTenant = (tenantId: string | null, task: any) => {
|
export const doInTenant = (tenantId: string | null, task: any) => {
|
||||||
// make sure default always selected in single tenancy
|
// make sure default always selected in single tenancy
|
||||||
if (!env.MULTI_TENANCY) {
|
if (!env.MULTI_TENANCY) {
|
||||||
|
|
|
@ -46,6 +46,9 @@ export enum DocumentType {
|
||||||
AUTOMATION_LOG = "log_au",
|
AUTOMATION_LOG = "log_au",
|
||||||
ACCOUNT_METADATA = "acc_metadata",
|
ACCOUNT_METADATA = "acc_metadata",
|
||||||
PLUGIN = "plg",
|
PLUGIN = "plg",
|
||||||
|
TABLE = "ta",
|
||||||
|
DATASOURCE = "datasource",
|
||||||
|
DATASOURCE_PLUS = "datasource_plus",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StaticDatabases = {
|
export const StaticDatabases = {
|
||||||
|
|
|
@ -36,6 +36,7 @@ exports.getDevelopmentAppID = appId => {
|
||||||
const rest = split.join(APP_PREFIX)
|
const rest = split.join(APP_PREFIX)
|
||||||
return `${APP_DEV_PREFIX}${rest}`
|
return `${APP_DEV_PREFIX}${rest}`
|
||||||
}
|
}
|
||||||
|
exports.getDevAppID = exports.getDevelopmentAppID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a development app ID to a deployed app ID.
|
* Convert a development app ID to a deployed app ID.
|
||||||
|
|
|
@ -64,6 +64,28 @@ export function getQueryIndex(viewName: ViewName) {
|
||||||
return `database/${viewName}`
|
return `database/${viewName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given ID is that of a table.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isTableId = (id: string) => {
|
||||||
|
// this includes datasource plus tables
|
||||||
|
return (
|
||||||
|
id &&
|
||||||
|
(id.startsWith(`${DocumentType.TABLE}${SEPARATOR}`) ||
|
||||||
|
id.startsWith(`${DocumentType.DATASOURCE_PLUS}${SEPARATOR}`))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given ID is that of a datasource or datasource plus.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isDatasourceId = (id: string) => {
|
||||||
|
// this covers both datasources and datasource plus
|
||||||
|
return id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new workspace ID.
|
* Generates a new workspace ID.
|
||||||
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const DEFINITIONS: MigrationDefinition[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: MigrationType.GLOBAL,
|
type: MigrationType.GLOBAL,
|
||||||
name: MigrationName.QUOTAS_1,
|
name: MigrationName.SYNC_QUOTAS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: MigrationType.APP,
|
type: MigrationType.APP,
|
||||||
|
@ -33,8 +33,4 @@ export const DEFINITIONS: MigrationDefinition[] = [
|
||||||
type: MigrationType.GLOBAL,
|
type: MigrationType.GLOBAL,
|
||||||
name: MigrationName.GLOBAL_INFO_SYNC_USERS,
|
name: MigrationName.GLOBAL_INFO_SYNC_USERS,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: MigrationType.GLOBAL,
|
|
||||||
name: MigrationName.PLUGIN_COUNT,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -182,6 +182,11 @@ export const streamUpload = async (
|
||||||
...extra,
|
...extra,
|
||||||
ContentType: "application/javascript",
|
ContentType: "application/javascript",
|
||||||
}
|
}
|
||||||
|
} else if (filename?.endsWith(".svg")) {
|
||||||
|
extra = {
|
||||||
|
...extra,
|
||||||
|
ContentType: "image",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
updateAppId,
|
updateAppId,
|
||||||
doInAppContext,
|
doInAppContext,
|
||||||
doInTenant,
|
doInTenant,
|
||||||
|
doInContext,
|
||||||
} from "../context"
|
} from "../context"
|
||||||
|
|
||||||
import * as identity from "../context/identity"
|
import * as identity from "../context/identity"
|
||||||
|
@ -20,5 +21,6 @@ export = {
|
||||||
updateAppId,
|
updateAppId,
|
||||||
doInAppContext,
|
doInAppContext,
|
||||||
doInTenant,
|
doInTenant,
|
||||||
|
doInContext,
|
||||||
identity,
|
identity,
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,12 +67,8 @@ function validateDatasource(schema) {
|
||||||
description: joi.string().required(),
|
description: joi.string().required(),
|
||||||
datasource: joi.object().pattern(joi.string(), fieldValidator).required(),
|
datasource: joi.object().pattern(joi.string(), fieldValidator).required(),
|
||||||
query: joi
|
query: joi
|
||||||
.object({
|
.object()
|
||||||
create: queryValidator,
|
.pattern(joi.string(), queryValidator)
|
||||||
read: queryValidator,
|
|
||||||
update: queryValidator,
|
|
||||||
delete: queryValidator,
|
|
||||||
})
|
|
||||||
.unknown(true)
|
.unknown(true)
|
||||||
.required(),
|
.required(),
|
||||||
extra: joi.object().pattern(
|
extra: joi.object().pattern(
|
||||||
|
|
|
@ -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.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"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.4.18-alpha.1",
|
"@budibase/string-templates": "2.0.14-alpha.0",
|
||||||
"@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",
|
||||||
|
|
|
@ -20,7 +20,9 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
|
cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
|
||||||
|
|
||||||
// User should not have app access
|
// User should not have app access
|
||||||
cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "No apps")
|
cy.get(".spectrum-Heading").contains("Apps").parent().within(() => {
|
||||||
|
cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "This user has access to no apps")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Cypress.env("TEST_ENV")) {
|
if (Cypress.env("TEST_ENV")) {
|
||||||
|
|
|
@ -54,6 +54,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.createDatasourceScreen([initialTable, secondTable])
|
cy.createDatasourceScreen([initialTable, secondTable])
|
||||||
// Confirm screens have been auto generated
|
// Confirm screens have been auto generated
|
||||||
// Previously generated tables are suffixed with numbers - as expected
|
// Previously generated tables are suffixed with numbers - as expected
|
||||||
|
cy.wait(1000)
|
||||||
cy.get(interact.BODY).should('contain', 'cypress-tests-2')
|
cy.get(interact.BODY).should('contain', 'cypress-tests-2')
|
||||||
.and('contain', 'cypress-tests-2/:id')
|
.and('contain', 'cypress-tests-2/:id')
|
||||||
.and('contain', 'cypress-tests-2/new/row')
|
.and('contain', 'cypress-tests-2/new/row')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "1.4.18-alpha.1",
|
"@budibase/bbui": "2.0.14-alpha.0",
|
||||||
"@budibase/client": "1.4.18-alpha.1",
|
"@budibase/client": "2.0.14-alpha.0",
|
||||||
"@budibase/frontend-core": "1.4.18-alpha.1",
|
"@budibase/frontend-core": "2.0.14-alpha.0",
|
||||||
"@budibase/string-templates": "1.4.18-alpha.1",
|
"@budibase/string-templates": "2.0.14-alpha.0",
|
||||||
"@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",
|
||||||
|
|
|
@ -143,7 +143,10 @@ export const getComponentSettings = componentType => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure whole component name is used
|
// Ensure whole component name is used
|
||||||
if (!componentType.startsWith("@budibase")) {
|
if (
|
||||||
|
!componentType.startsWith("plugin/") &&
|
||||||
|
!componentType.startsWith("@budibase")
|
||||||
|
) {
|
||||||
componentType = `@budibase/standard-components/${componentType}`
|
componentType = `@budibase/standard-components/${componentType}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -243,18 +243,18 @@ export const getDatasourceForProvider = (asset, component) => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are different types of setting which can be a datasource, for
|
// For legacy compatibility, we need to be able to handle datasources that are
|
||||||
// example an actual datasource object, or a table ID string.
|
// just strings. These are not generated any more, so could be removed in
|
||||||
// Convert the datasource setting into a proper datasource object so that
|
// future.
|
||||||
// we can use it properly
|
// TODO: remove at some point
|
||||||
if (datasourceSetting.type === "table") {
|
const datasource = component[datasourceSetting?.key]
|
||||||
|
if (typeof datasource === "string") {
|
||||||
return {
|
return {
|
||||||
tableId: component[datasourceSetting?.key],
|
tableId: datasource,
|
||||||
type: "table",
|
type: "table",
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return component[datasourceSetting?.key]
|
|
||||||
}
|
}
|
||||||
|
return datasource
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -88,27 +88,12 @@ export const getFrontendStore = () => {
|
||||||
initialise: async pkg => {
|
initialise: async pkg => {
|
||||||
const { layouts, screens, application, clientLibPath } = pkg
|
const { layouts, screens, application, clientLibPath } = pkg
|
||||||
|
|
||||||
// Fetch component definitions.
|
await store.actions.components.refreshDefinitions(application.appId)
|
||||||
// Allow errors to propagate.
|
|
||||||
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,
|
|
||||||
customComponents,
|
|
||||||
clientFeatures: {
|
|
||||||
...INITIAL_FRONTEND_STATE.clientFeatures,
|
|
||||||
...components.features,
|
|
||||||
},
|
|
||||||
name: application.name,
|
name: application.name,
|
||||||
description: application.description,
|
description: application.description,
|
||||||
appId: application.appId,
|
appId: application.appId,
|
||||||
|
@ -385,6 +370,29 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
refreshDefinitions: async appId => {
|
||||||
|
if (!appId) {
|
||||||
|
appId = get(store).appId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch definitions and filter out custom component definitions so we
|
||||||
|
// can flag them
|
||||||
|
const components = await API.fetchComponentLibDefinitions(appId)
|
||||||
|
const customComponents = Object.keys(components).filter(name =>
|
||||||
|
name.startsWith("plugin/")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update store
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
components,
|
||||||
|
customComponents,
|
||||||
|
clientFeatures: {
|
||||||
|
...INITIAL_FRONTEND_STATE.clientFeatures,
|
||||||
|
...components.features,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
},
|
||||||
getDefinition: componentName => {
|
getDefinition: componentName => {
|
||||||
if (!componentName) {
|
if (!componentName) {
|
||||||
return null
|
return null
|
||||||
|
@ -428,7 +436,7 @@ export const getFrontendStore = () => {
|
||||||
_id: Helpers.uuid(),
|
_id: Helpers.uuid(),
|
||||||
_component: definition.component,
|
_component: definition.component,
|
||||||
_styles: { normal: {}, hover: {}, active: {} },
|
_styles: { normal: {}, hover: {}, active: {} },
|
||||||
_instanceName: `New ${definition.name}`,
|
_instanceName: `New ${definition.friendlyName || definition.name}`,
|
||||||
...cloneDeep(props),
|
...cloneDeep(props),
|
||||||
...extras,
|
...extras,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
customQueryIconColor,
|
customQueryIconColor,
|
||||||
customQueryText,
|
customQueryText,
|
||||||
} from "helpers/data/utils"
|
} from "helpers/data/utils"
|
||||||
import { getIcon } from "./icons"
|
import IntegrationIcon from "./IntegrationIcon.svelte"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
let openDataSources = []
|
let openDataSources = []
|
||||||
|
@ -123,10 +123,10 @@
|
||||||
on:iconClick={() => toggleNode(datasource)}
|
on:iconClick={() => toggleNode(datasource)}
|
||||||
>
|
>
|
||||||
<div class="datasource-icon" slot="icon">
|
<div class="datasource-icon" slot="icon">
|
||||||
<svelte:component
|
<IntegrationIcon
|
||||||
this={getIcon(datasource.source, datasource.schema)}
|
integrationType={datasource.source}
|
||||||
height="18"
|
schema={datasource.schema}
|
||||||
width="18"
|
size="18"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if datasource._id !== BUDIBASE_INTERNAL_DB}
|
{#if datasource._id !== BUDIBASE_INTERNAL_DB}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script>
|
||||||
|
import { getIcon } from "./icons"
|
||||||
|
import CustomSVG from "components/common/CustomSVG.svelte"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
|
|
||||||
|
export let integrationType
|
||||||
|
export let schema
|
||||||
|
export let size = "18"
|
||||||
|
|
||||||
|
$: objectStoreUrl = $admin.cloud ? "https://cdn.budi.live" : ""
|
||||||
|
$: pluginsUrl = `${objectStoreUrl}/plugins`
|
||||||
|
$: iconInfo = getIcon(integrationType, schema)
|
||||||
|
|
||||||
|
async function getSvgFromUrl(info) {
|
||||||
|
const url = `${pluginsUrl}/${info.url}`
|
||||||
|
const resp = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
["pragma"]: "no-cache",
|
||||||
|
["cache-control"]: "no-cache",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return resp.text()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if iconInfo.icon}
|
||||||
|
<svelte:component this={iconInfo.icon} height={size} width={size} />
|
||||||
|
{:else if iconInfo.url}
|
||||||
|
{#await getSvgFromUrl(iconInfo) then retrievedSvg}
|
||||||
|
<CustomSVG {size} svgHtml={retrievedSvg} />
|
||||||
|
{/await}
|
||||||
|
{/if}
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { Heading, Detail } from "@budibase/bbui"
|
import { Heading, Detail } from "@budibase/bbui"
|
||||||
import { getIcon } from "../icons"
|
import IntegrationIcon from "../IntegrationIcon.svelte"
|
||||||
|
|
||||||
export let integration
|
export let integration
|
||||||
export let integrationType
|
export let integrationType
|
||||||
|
@ -16,11 +16,7 @@
|
||||||
class="item hoverable"
|
class="item hoverable"
|
||||||
>
|
>
|
||||||
<div class="item-body" class:with-type={!!schema.type}>
|
<div class="item-body" class:with-type={!!schema.type}>
|
||||||
<svelte:component
|
<IntegrationIcon {integrationType} {schema} size="25" />
|
||||||
this={getIcon(integrationType, schema)}
|
|
||||||
height="20"
|
|
||||||
width="20"
|
|
||||||
/>
|
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<Heading size="XXS">{schema.friendlyName}</Heading>
|
<Heading size="XXS">{schema.friendlyName}</Heading>
|
||||||
{#if schema.type}
|
{#if schema.type}
|
||||||
|
|
|
@ -16,6 +16,8 @@ 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"
|
import Custom from "./Custom.svelte"
|
||||||
|
import { integrations } from "stores/backend"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
BUDIBASE: Budibase,
|
BUDIBASE: Budibase,
|
||||||
|
@ -41,9 +43,12 @@ const ICONS = {
|
||||||
export default ICONS
|
export default ICONS
|
||||||
|
|
||||||
export function getIcon(integrationType, schema) {
|
export function getIcon(integrationType, schema) {
|
||||||
if (schema?.custom || !ICONS[integrationType]) {
|
const integrationList = get(integrations)
|
||||||
return ICONS.CUSTOM
|
if (integrationList[integrationType]?.iconUrl) {
|
||||||
|
return { url: integrationList[integrationType].iconUrl }
|
||||||
|
} else if (schema?.custom || !ICONS[integrationType]) {
|
||||||
|
return { icon: ICONS.CUSTOM }
|
||||||
} else {
|
} else {
|
||||||
return ICONS[integrationType]
|
return { icon: ICONS[integrationType] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
export let size
|
||||||
|
export let svgHtml
|
||||||
|
|
||||||
|
function substituteSize(svg) {
|
||||||
|
if (svg.includes("height=")) {
|
||||||
|
svg = svg.replace(/height="[^"]+"/, `height="${size}"`)
|
||||||
|
}
|
||||||
|
if (svg.includes("width=")) {
|
||||||
|
svg = svg.replace(/width="[^"]+"/, `width="${size}"`)
|
||||||
|
}
|
||||||
|
if (svg.includes("id=")) {
|
||||||
|
const matches = svg.match(/id="([^"]+)"/g)
|
||||||
|
for (let match of matches) {
|
||||||
|
svg = svg.replace(new RegExp(match, "g"), Helpers.uuid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return svg
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@html substituteSize(svgHtml)}
|
|
@ -43,7 +43,7 @@
|
||||||
let helpers = handlebarsCompletions()
|
let helpers = handlebarsCompletions()
|
||||||
let getCaretPosition
|
let getCaretPosition
|
||||||
let search = ""
|
let search = ""
|
||||||
let initialValueJS = value?.startsWith("{{ js ")
|
let initialValueJS = typeof value === "string" && value?.startsWith("{{ js ")
|
||||||
let mode = initialValueJS ? "JavaScript" : "Handlebars"
|
let mode = initialValueJS ? "JavaScript" : "Handlebars"
|
||||||
let jsValue = initialValueJS ? value : null
|
let jsValue = initialValueJS ? value : null
|
||||||
let hbsValue = initialValueJS ? null : value
|
let hbsValue = initialValueJS ? null : value
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
const { OperatorOptions } = Constants
|
||||||
|
const { getValidOperatorsForType } = LuceneUtils
|
||||||
|
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let filters = []
|
export let filters = []
|
||||||
|
@ -45,7 +47,7 @@
|
||||||
{
|
{
|
||||||
id: generate(),
|
id: generate(),
|
||||||
field: null,
|
field: null,
|
||||||
operator: Constants.OperatorOptions.Equals.value,
|
operator: OperatorOptions.Equals.value,
|
||||||
value: null,
|
value: null,
|
||||||
valueType: "Value",
|
valueType: "Value",
|
||||||
},
|
},
|
||||||
|
@ -66,49 +68,60 @@
|
||||||
return schemaFields.find(field => field.name === filter.field)
|
return schemaFields.find(field => field.name === filter.field)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFieldChange = (expression, field) => {
|
const santizeTypes = filter => {
|
||||||
// Update the field types
|
// Update type based on field
|
||||||
expression.type = enrichedSchemaFields.find(x => x.name === field)?.type
|
const fieldSchema = enrichedSchemaFields.find(x => x.name === filter.field)
|
||||||
expression.externalType = getSchema(expression)?.externalType
|
filter.type = fieldSchema?.type
|
||||||
|
|
||||||
// Ensure a valid operator is set
|
// Update external type based on field
|
||||||
const validOperators = LuceneUtils.getValidOperatorsForType(
|
filter.externalType = getSchema(filter)?.externalType
|
||||||
expression.type
|
}
|
||||||
).map(x => x.value)
|
|
||||||
if (!validOperators.includes(expression.operator)) {
|
const santizeOperator = filter => {
|
||||||
expression.operator =
|
// Ensure a valid operator is selected
|
||||||
validOperators[0] ?? Constants.OperatorOptions.Equals.value
|
const operators = getValidOperatorsForType(filter.type).map(x => x.value)
|
||||||
onOperatorChange(expression, expression.operator)
|
if (!operators.includes(filter.operator)) {
|
||||||
|
filter.operator = operators[0] ?? OperatorOptions.Equals.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// if changed to an array, change default value to empty array
|
// Update the noValue flag if the operator does not take a value
|
||||||
const idx = filters.findIndex(x => x.id === expression.id)
|
const noValueOptions = [
|
||||||
if (expression.type === "array") {
|
OperatorOptions.Empty.value,
|
||||||
filters[idx].value = []
|
OperatorOptions.NotEmpty.value,
|
||||||
} else {
|
]
|
||||||
filters[idx].value = null
|
filter.noValue = noValueOptions.includes(filter.operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
const santizeValue = filter => {
|
||||||
|
// Check if the operator allows a value at all
|
||||||
|
if (filter.noValue) {
|
||||||
|
filter.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure array values are properly set and cleared
|
||||||
|
if (Array.isArray(filter.value)) {
|
||||||
|
if (filter.valueType !== "Value" || filter.type !== "array") {
|
||||||
|
filter.value = null
|
||||||
|
}
|
||||||
|
} else if (filter.type === "array" && filter.valueType === "Value") {
|
||||||
|
filter.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onOperatorChange = (expression, operator) => {
|
const onFieldChange = filter => {
|
||||||
const noValueOptions = [
|
santizeTypes(filter)
|
||||||
Constants.OperatorOptions.Empty.value,
|
santizeOperator(filter)
|
||||||
Constants.OperatorOptions.NotEmpty.value,
|
santizeValue(filter)
|
||||||
]
|
}
|
||||||
expression.noValue = noValueOptions.includes(operator)
|
|
||||||
if (expression.noValue) {
|
const onOperatorChange = filter => {
|
||||||
expression.value = null
|
santizeOperator(filter)
|
||||||
}
|
santizeValue(filter)
|
||||||
if (
|
}
|
||||||
operator === Constants.OperatorOptions.In.value &&
|
|
||||||
!Array.isArray(expression.value)
|
const onValueTypeChange = filter => {
|
||||||
) {
|
santizeValue(filter)
|
||||||
if (expression.value) {
|
|
||||||
expression.value = [expression.value]
|
|
||||||
} else {
|
|
||||||
expression.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldOptions = field => {
|
const getFieldOptions = field => {
|
||||||
|
@ -153,23 +166,24 @@
|
||||||
<Select
|
<Select
|
||||||
bind:value={filter.field}
|
bind:value={filter.field}
|
||||||
options={fieldOptions}
|
options={fieldOptions}
|
||||||
on:change={e => onFieldChange(filter, e.detail)}
|
on:change={() => onFieldChange(filter)}
|
||||||
placeholder="Column"
|
placeholder="Column"
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
disabled={!filter.field}
|
disabled={!filter.field}
|
||||||
options={LuceneUtils.getValidOperatorsForType(filter.type)}
|
options={getValidOperatorsForType(filter.type)}
|
||||||
bind:value={filter.operator}
|
bind:value={filter.operator}
|
||||||
on:change={e => onOperatorChange(filter, e.detail)}
|
on:change={() => onOperatorChange(filter)}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
disabled={filter.noValue || !filter.field}
|
disabled={filter.noValue || !filter.field}
|
||||||
options={valueTypeOptions}
|
options={valueTypeOptions}
|
||||||
bind:value={filter.valueType}
|
bind:value={filter.valueType}
|
||||||
|
on:change={() => onValueTypeChange(filter)}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
/>
|
/>
|
||||||
{#if filter.valueType === "Binding"}
|
{#if filter.field && filter.valueType === "Binding"}
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
disabled={filter.noValue}
|
disabled={filter.noValue}
|
||||||
title={`Value for "${filter.field}"`}
|
title={`Value for "${filter.field}"`}
|
||||||
|
@ -250,7 +264,7 @@
|
||||||
column-gap: var(--spacing-l);
|
column-gap: var(--spacing-l);
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-s);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-template-columns: 1fr 120px 120px 1fr auto auto;
|
grid-template-columns: 1fr 150px 120px 1fr 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-label {
|
.filter-label {
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select } from "@budibase/bbui"
|
||||||
import { tables } from "stores/backend"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { tables as tablesStore } from "stores/backend"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: tables = $tablesStore.list.map(m => ({
|
||||||
|
label: m.name,
|
||||||
|
tableId: m._id,
|
||||||
|
type: "table",
|
||||||
|
}))
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
const dataSource = tables?.find(x => x.tableId === e.detail)
|
||||||
|
dispatch("change", dataSource)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<Select
|
||||||
<Select extraThin secondary wide on:change {value}>
|
on:change={onChange}
|
||||||
<option value="">Choose a table</option>
|
value={value?.tableId}
|
||||||
{#each $tables.list as table}
|
options={tables}
|
||||||
<option value={table._id}>{table.name}</option>
|
getOptionValue={x => x.tableId}
|
||||||
{/each}
|
getOptionLabel={x => x.label}
|
||||||
</Select>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
div :global(> *) {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -198,6 +198,8 @@
|
||||||
block: "center",
|
block: "center",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else if (type === "reload-plugin") {
|
||||||
|
await store.actions.components.refreshDefinitions()
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Client sent unknown event type: ${type}`)
|
console.warn(`Client sent unknown event type: ${type}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
let closedNodes = {}
|
let closedNodes = {}
|
||||||
|
|
||||||
|
$: currentScreen = get(selectedScreen)
|
||||||
|
|
||||||
$: filteredComponents = components?.filter(component => {
|
$: filteredComponents = components?.filter(component => {
|
||||||
return (
|
return (
|
||||||
!$store.componentToPaste?.isCut ||
|
!$store.componentToPaste?.isCut ||
|
||||||
|
@ -68,9 +70,27 @@
|
||||||
closedNodes = closedNodes
|
closedNodes = closedNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDrop = async e => {
|
const onDrop = async (e, component) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
try {
|
try {
|
||||||
|
const compDef = store.actions.components.getDefinition(
|
||||||
|
$dndStore.source?._component
|
||||||
|
)
|
||||||
|
const compTypeName = compDef.name.toLowerCase()
|
||||||
|
const path = findComponentPath(currentScreen.props, component._id)
|
||||||
|
|
||||||
|
for (let pathComp of path) {
|
||||||
|
const pathCompDef = store.actions.components.getDefinition(
|
||||||
|
pathComp?._component
|
||||||
|
)
|
||||||
|
if (pathCompDef?.illegalChildren?.indexOf(compTypeName) > -1) {
|
||||||
|
notifications.warning(
|
||||||
|
`${compDef.name} cannot be a child of ${pathCompDef.name} (${pathComp._instanceName})`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await dndStore.actions.drop()
|
await dndStore.actions.drop()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -114,7 +134,9 @@
|
||||||
on:dragstart={() => dndStore.actions.dragstart(component)}
|
on:dragstart={() => dndStore.actions.dragstart(component)}
|
||||||
on:dragover={dragover(component, index)}
|
on:dragover={dragover(component, index)}
|
||||||
on:iconClick={() => toggleNodeOpen(component._id)}
|
on:iconClick={() => toggleNodeOpen(component._id)}
|
||||||
on:drop={onDrop}
|
on:drop={e => {
|
||||||
|
onDrop(e, component)
|
||||||
|
}}
|
||||||
text={getComponentText(component)}
|
text={getComponentText(component)}
|
||||||
icon={getComponentIcon(component)}
|
icon={getComponentIcon(component)}
|
||||||
withArrow={componentHasChildren(component)}
|
withArrow={componentHasChildren(component)}
|
||||||
|
|
|
@ -133,7 +133,7 @@
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider />
|
<Divider />
|
||||||
{#if $licensing.usageMetrics.dayPasses >= 100}
|
{#if $licensing.usageMetrics?.dayPasses >= 100}
|
||||||
<div>
|
<div>
|
||||||
<Layout gap="S" justifyItems="center">
|
<Layout gap="S" justifyItems="center">
|
||||||
<img class="spaceman" alt="spaceman" src={Spaceman} />
|
<img class="spaceman" alt="spaceman" src={Spaceman} />
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
{
|
{
|
||||||
title: "Plugins",
|
title: "Plugins",
|
||||||
href: "/builder/portal/manage/plugins",
|
href: "/builder/portal/manage/plugins",
|
||||||
badge: "Beta",
|
badge: "New",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
||||||
import GroupIcon from "./_components/GroupIcon.svelte"
|
import GroupIcon from "./_components/GroupIcon.svelte"
|
||||||
|
import AppAddModal from "./_components/AppAddModal.svelte"
|
||||||
|
|
||||||
export let groupId
|
export let groupId
|
||||||
|
|
||||||
|
@ -34,15 +35,14 @@
|
||||||
let prevSearch = undefined
|
let prevSearch = undefined
|
||||||
let pageInfo = createPaginationStore()
|
let pageInfo = createPaginationStore()
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let editModal
|
let editModal, deleteModal, appAddModal
|
||||||
let deleteModal
|
|
||||||
|
|
||||||
$: page = $pageInfo.page
|
$: page = $pageInfo.page
|
||||||
$: fetchUsers(page, searchTerm)
|
$: fetchUsers(page, searchTerm)
|
||||||
$: group = $groups.find(x => x._id === groupId)
|
$: group = $groups.find(x => x._id === groupId)
|
||||||
$: filtered = $users.data
|
$: filtered = $users.data
|
||||||
$: groupApps = $apps.filter(app =>
|
$: groupApps = $apps.filter(app =>
|
||||||
groups.actions.getGroupAppIds(group).includes(apps.getProdAppID(app.appId))
|
groups.actions.getGroupAppIds(group).includes(apps.getProdAppID(app.devId))
|
||||||
)
|
)
|
||||||
$: {
|
$: {
|
||||||
if (loaded && !group?._id) {
|
if (loaded && !group?._id) {
|
||||||
|
@ -182,7 +182,14 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<Heading size="S">Apps</Heading>
|
<div class="header">
|
||||||
|
<Heading size="S">Apps</Heading>
|
||||||
|
<div>
|
||||||
|
<Button on:click={appAddModal.show()} icon="ExperienceAdd" cta>
|
||||||
|
Add app
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<List>
|
<List>
|
||||||
{#if groupApps.length}
|
{#if groupApps.length}
|
||||||
{#each groupApps as app}
|
{#each groupApps as app}
|
||||||
|
@ -197,12 +204,24 @@
|
||||||
<StatusLight
|
<StatusLight
|
||||||
square
|
square
|
||||||
color={RoleUtils.getRoleColour(
|
color={RoleUtils.getRoleColour(
|
||||||
group.roles[apps.getProdAppID(app.appId)]
|
group.roles[apps.getProdAppID(app.devId)]
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getRoleLabel(app.appId)}
|
{getRoleLabel(app.devId)}
|
||||||
</StatusLight>
|
</StatusLight>
|
||||||
</div>
|
</div>
|
||||||
|
<Icon
|
||||||
|
on:click={e => {
|
||||||
|
groups.actions.removeApp(
|
||||||
|
groupId,
|
||||||
|
apps.getProdAppID(app.devId)
|
||||||
|
)
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
name="Close"
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -216,6 +235,11 @@
|
||||||
<Modal bind:this={editModal}>
|
<Modal bind:this={editModal}>
|
||||||
<CreateEditGroupModal {group} {saveGroup} />
|
<CreateEditGroupModal {group} {saveGroup} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={appAddModal}>
|
||||||
|
<AppAddModal {group} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={deleteModal}
|
bind:this={deleteModal}
|
||||||
title="Delete user group"
|
title="Delete user group"
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<script>
|
||||||
|
import { Body, ModalContent, Select } from "@budibase/bbui"
|
||||||
|
import { apps, groups } from "stores/portal"
|
||||||
|
import { roles } from "stores/backend"
|
||||||
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
|
|
||||||
|
export let group
|
||||||
|
|
||||||
|
$: appOptions = $apps.map(app => ({
|
||||||
|
label: app.name,
|
||||||
|
value: app,
|
||||||
|
}))
|
||||||
|
$: confirmDisabled =
|
||||||
|
(!selectingRole && !selectedApp) || (selectingRole && !selectedRoleId)
|
||||||
|
let selectedApp, selectedRoleId
|
||||||
|
let selectingRole = false
|
||||||
|
|
||||||
|
async function appSelected() {
|
||||||
|
const prodAppId = apps.getProdAppID(selectedApp.devId)
|
||||||
|
if (!selectingRole) {
|
||||||
|
selectingRole = true
|
||||||
|
await roles.fetchByAppId(prodAppId)
|
||||||
|
// return false to stop closing modal
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
await groups.actions.addApp(group._id, prodAppId, selectedRoleId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
onConfirm={appSelected}
|
||||||
|
size="M"
|
||||||
|
title="Add app to group"
|
||||||
|
confirmText={selectingRole ? "Confirm" : "Next"}
|
||||||
|
showSecondaryButton={selectingRole}
|
||||||
|
secondaryButtonText="Back"
|
||||||
|
secondaryAction={() => (selectingRole = false)}
|
||||||
|
disabled={confirmDisabled}
|
||||||
|
>
|
||||||
|
{#if !selectingRole}
|
||||||
|
<Body
|
||||||
|
>Select an app to assign roles for members of <i>"{group.name}"</i></Body
|
||||||
|
>
|
||||||
|
<Select bind:value={selectedApp} options={appOptions} />
|
||||||
|
{:else}
|
||||||
|
<Body
|
||||||
|
>Select the role that all members of "<i>{group.name}</i>" will have for
|
||||||
|
<i>"{selectedApp.name}"</i></Body
|
||||||
|
>
|
||||||
|
<RoleSelect allowPublic={false} bind:value={selectedRoleId} />
|
||||||
|
{/if}
|
||||||
|
</ModalContent>
|
|
@ -27,7 +27,6 @@
|
||||||
icon: "UserGroup",
|
icon: "UserGroup",
|
||||||
color: "var(--spectrum-global-color-blue-600)",
|
color: "var(--spectrum-global-color-blue-600)",
|
||||||
users: [],
|
users: [],
|
||||||
apps: [],
|
|
||||||
roles: {},
|
roles: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,16 +90,14 @@
|
||||||
|
|
||||||
<Layout noPadding gap="M">
|
<Layout noPadding gap="M">
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="M">User groups</Heading>
|
<div class="title">
|
||||||
{#if !$licensing.groupsEnabled}
|
<Heading size="M">User groups</Heading>
|
||||||
<Tags>
|
{#if !$licensing.groupsEnabled}
|
||||||
<div class="tags">
|
<Tags>
|
||||||
<div class="tag">
|
<Tag icon="LockClosed">Pro plan</Tag>
|
||||||
<Tag icon="LockClosed">Pro plan</Tag>
|
</Tags>
|
||||||
</div>
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Tags>
|
|
||||||
{/if}
|
|
||||||
<Body>
|
<Body>
|
||||||
Easily assign and manage your users' access with user groups.
|
Easily assign and manage your users' access with user groups.
|
||||||
{#if !$auth.accountPortalAccess && !$licensing.groupsEnabled && $admin.cloud}
|
{#if !$auth.accountPortalAccess && !$licensing.groupsEnabled && $admin.cloud}
|
||||||
|
@ -124,6 +121,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<Button
|
<Button
|
||||||
newStyles
|
newStyles
|
||||||
|
primary
|
||||||
disabled={!$auth.accountPortalAccess && $admin.cloud}
|
disabled={!$auth.accountPortalAccess && $admin.cloud}
|
||||||
on:click={$licensing.goToUpgradePage()}
|
on:click={$licensing.goToUpgradePage()}
|
||||||
>
|
>
|
||||||
|
@ -141,18 +139,22 @@
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<div class="controls-right">
|
{#if $licensing.groupsEnabled}
|
||||||
<Search bind:value={searchString} placeholder="Search" />
|
<div class="controls-right">
|
||||||
</div>
|
<Search bind:value={searchString} placeholder="Search" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Table
|
{#if $licensing.groupsEnabled}
|
||||||
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
<Table
|
||||||
{schema}
|
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
||||||
data={filteredGroups}
|
{schema}
|
||||||
allowEditColumns={false}
|
data={filteredGroups}
|
||||||
allowEditRows={false}
|
allowEditColumns={false}
|
||||||
{customRenderers}
|
allowEditRows={false}
|
||||||
/>
|
{customRenderers}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
@ -176,8 +178,11 @@
|
||||||
.controls-right :global(.spectrum-Search) {
|
.controls-right :global(.spectrum-Search) {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
.tag {
|
.title {
|
||||||
margin-top: var(--spacing-xs);
|
display: flex;
|
||||||
margin-left: var(--spacing-m);
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -55,18 +55,20 @@
|
||||||
Add plugin
|
Add plugin
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="filters">
|
{#if filteredPlugins?.length}
|
||||||
<div class="select">
|
<div class="filters">
|
||||||
<Select
|
<div class="select">
|
||||||
bind:value={filter}
|
<Select
|
||||||
placeholder={null}
|
bind:value={filter}
|
||||||
options={filterOptions}
|
placeholder={null}
|
||||||
autoWidth
|
options={filterOptions}
|
||||||
quiet
|
autoWidth
|
||||||
/>
|
quiet
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Search bind:value={searchTerm} placeholder="Search plugins" />
|
||||||
</div>
|
</div>
|
||||||
<Search bind:value={searchTerm} placeholder="Search plugins" />
|
{/if}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{#if filteredPlugins?.length}
|
{#if filteredPlugins?.length}
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
|
|
|
@ -5,16 +5,24 @@ import { RoleUtils } from "@budibase/frontend-core"
|
||||||
export function createRolesStore() {
|
export function createRolesStore() {
|
||||||
const { subscribe, update, set } = writable([])
|
const { subscribe, update, set } = writable([])
|
||||||
|
|
||||||
|
function setRoles(roles) {
|
||||||
|
set(
|
||||||
|
roles.sort((a, b) => {
|
||||||
|
const priorityA = RoleUtils.getRolePriority(a._id)
|
||||||
|
const priorityB = RoleUtils.getRolePriority(b._id)
|
||||||
|
return priorityA > priorityB ? -1 : 1
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
const roles = await API.getRoles()
|
const roles = await API.getRoles()
|
||||||
set(
|
setRoles(roles)
|
||||||
roles.sort((a, b) => {
|
},
|
||||||
const priorityA = RoleUtils.getRolePriority(a._id)
|
fetchByAppId: async appId => {
|
||||||
const priorityB = RoleUtils.getRolePriority(b._id)
|
const { roles } = await API.getRolesForApp(appId)
|
||||||
return priorityA > priorityB ? -1 : 1
|
setRoles(roles)
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
delete: async role => {
|
delete: async role => {
|
||||||
await API.deleteRole({
|
await API.deleteRole({
|
||||||
|
|
|
@ -21,6 +21,8 @@ const getProdAppID = appId => {
|
||||||
} else if (!appId.startsWith("app")) {
|
} else if (!appId.startsWith("app")) {
|
||||||
rest = appId
|
rest = appId
|
||||||
separator = "_"
|
separator = "_"
|
||||||
|
} else {
|
||||||
|
return appId
|
||||||
}
|
}
|
||||||
return `app${separator}${rest}`
|
return `app${separator}${rest}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"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,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "1.4.18-alpha.1",
|
"@budibase/backend-core": "2.0.14-alpha.0",
|
||||||
"@budibase/string-templates": "1.4.18-alpha.1",
|
"@budibase/string-templates": "2.0.14-alpha.0",
|
||||||
"@budibase/types": "1.4.18-alpha.1",
|
"@budibase/types": "2.0.14-alpha.0",
|
||||||
"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",
|
||||||
|
@ -36,6 +36,7 @@
|
||||||
"docker-compose": "0.23.6",
|
"docker-compose": "0.23.6",
|
||||||
"dotenv": "16.0.1",
|
"dotenv": "16.0.1",
|
||||||
"download": "8.0.0",
|
"download": "8.0.0",
|
||||||
|
"find-free-port": "^2.0.0",
|
||||||
"inquirer": "8.0.0",
|
"inquirer": "8.0.0",
|
||||||
"joi": "17.6.0",
|
"joi": "17.6.0",
|
||||||
"lookpath": "1.1.0",
|
"lookpath": "1.1.0",
|
||||||
|
@ -45,7 +46,8 @@
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
"pouchdb-replication-stream": "1.2.9",
|
"pouchdb-replication-stream": "1.2.9",
|
||||||
"randomstring": "1.1.5",
|
"randomstring": "1.1.5",
|
||||||
"tar": "6.1.11"
|
"tar": "6.1.11",
|
||||||
|
"yaml": "^2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
|
|
|
@ -21,3 +21,5 @@ exports.AnalyticsEvents = {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.POSTHOG_TOKEN = "phc_yGOn4i7jWKaCTapdGR6lfA4AvmuEQ2ijn5zAVSFYPlS"
|
exports.POSTHOG_TOKEN = "phc_yGOn4i7jWKaCTapdGR6lfA4AvmuEQ2ijn5zAVSFYPlS"
|
||||||
|
|
||||||
|
exports.GENERATED_USER_EMAIL = "admin@admin.com"
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
const { success } = require("../utils")
|
||||||
|
const { updateDockerComposeService } = require("./utils")
|
||||||
|
const randomString = require("randomstring")
|
||||||
|
const { GENERATED_USER_EMAIL } = require("../constants")
|
||||||
|
|
||||||
|
exports.generateUser = async (password, silent) => {
|
||||||
|
const email = GENERATED_USER_EMAIL
|
||||||
|
if (!password) {
|
||||||
|
password = randomString.generate({ length: 6 })
|
||||||
|
}
|
||||||
|
updateDockerComposeService(service => {
|
||||||
|
service.environment["BB_ADMIN_USER_EMAIL"] = email
|
||||||
|
service.environment["BB_ADMIN_USER_PASSWORD"] = password
|
||||||
|
})
|
||||||
|
if (!silent) {
|
||||||
|
console.log(
|
||||||
|
success(
|
||||||
|
`User admin credentials configured, access with email: ${email} - password: ${password}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,164 +1,18 @@
|
||||||
const Command = require("../structures/Command")
|
const Command = require("../structures/Command")
|
||||||
const { CommandWords, InitTypes, AnalyticsEvents } = require("../constants")
|
const { CommandWords } = require("../constants")
|
||||||
const { lookpath } = require("lookpath")
|
const { init } = require("./init")
|
||||||
const {
|
const { start } = require("./start")
|
||||||
downloadFile,
|
const { stop } = require("./stop")
|
||||||
logErrorToFile,
|
const { status } = require("./status")
|
||||||
success,
|
const { update } = require("./update")
|
||||||
info,
|
const { generateUser } = require("./genUser")
|
||||||
parseEnv,
|
const { watchPlugins } = require("./watch")
|
||||||
} = require("../utils")
|
|
||||||
const { confirmation } = require("../questions")
|
|
||||||
const fs = require("fs")
|
|
||||||
const compose = require("docker-compose")
|
|
||||||
const makeEnv = require("./makeEnv")
|
|
||||||
const axios = require("axios")
|
|
||||||
const { captureEvent } = require("../events")
|
|
||||||
|
|
||||||
const BUDIBASE_SERVICES = ["app-service", "worker-service", "proxy-service"]
|
|
||||||
const ERROR_FILE = "docker-error.log"
|
|
||||||
const FILE_URLS = [
|
|
||||||
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml",
|
|
||||||
]
|
|
||||||
const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"
|
|
||||||
|
|
||||||
async function downloadFiles() {
|
|
||||||
const promises = []
|
|
||||||
for (let url of FILE_URLS) {
|
|
||||||
const fileName = url.split("/").slice(-1)[0]
|
|
||||||
promises.push(downloadFile(url, `./${fileName}`))
|
|
||||||
}
|
|
||||||
await Promise.all(promises)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkDockerConfigured() {
|
|
||||||
const error =
|
|
||||||
"docker/docker-compose has not been installed, please follow instructions at: https://docs.budibase.com/docs/docker-compose"
|
|
||||||
const docker = await lookpath("docker")
|
|
||||||
const compose = await lookpath("docker-compose")
|
|
||||||
if (!docker || !compose) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkInitComplete() {
|
|
||||||
if (!fs.existsSync(makeEnv.filePath)) {
|
|
||||||
throw "Please run the hosting --init command before any other hosting command."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleError(func) {
|
|
||||||
try {
|
|
||||||
await func()
|
|
||||||
} catch (err) {
|
|
||||||
if (err && err.err) {
|
|
||||||
logErrorToFile(ERROR_FILE, err.err)
|
|
||||||
}
|
|
||||||
throw `Failed to start - logs written to file: ${ERROR_FILE}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init(type) {
|
|
||||||
const isQuick = type === InitTypes.QUICK || type === InitTypes.DIGITAL_OCEAN
|
|
||||||
await checkDockerConfigured()
|
|
||||||
if (!isQuick) {
|
|
||||||
const shouldContinue = await confirmation(
|
|
||||||
"This will create multiple files in current directory, should continue?"
|
|
||||||
)
|
|
||||||
if (!shouldContinue) {
|
|
||||||
console.log("Stopping.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
captureEvent(AnalyticsEvents.SelfHostInit, {
|
|
||||||
type,
|
|
||||||
})
|
|
||||||
await downloadFiles()
|
|
||||||
const config = isQuick ? makeEnv.QUICK_CONFIG : {}
|
|
||||||
if (type === InitTypes.DIGITAL_OCEAN) {
|
|
||||||
try {
|
|
||||||
const output = await axios.get(DO_USER_DATA_URL)
|
|
||||||
const response = parseEnv(output.data)
|
|
||||||
for (let [key, value] of Object.entries(makeEnv.ConfigMap)) {
|
|
||||||
if (response[key]) {
|
|
||||||
config[value] = response[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// don't need to handle error, just don't do anything
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await makeEnv.make(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function start() {
|
|
||||||
await checkDockerConfigured()
|
|
||||||
checkInitComplete()
|
|
||||||
console.log(
|
|
||||||
info(
|
|
||||||
"Starting services, this may take a moment - first time this may take a few minutes to download images."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
const port = makeEnv.get("MAIN_PORT")
|
|
||||||
await handleError(async () => {
|
|
||||||
// need to log as it makes it more clear
|
|
||||||
await compose.upAll({ cwd: "./", log: true })
|
|
||||||
})
|
|
||||||
console.log(
|
|
||||||
success(
|
|
||||||
`Services started, please go to http://localhost:${port} for next steps.`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function status() {
|
|
||||||
await checkDockerConfigured()
|
|
||||||
checkInitComplete()
|
|
||||||
console.log(info("Budibase status"))
|
|
||||||
await handleError(async () => {
|
|
||||||
const response = await compose.ps()
|
|
||||||
console.log(response.out)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stop() {
|
|
||||||
await checkDockerConfigured()
|
|
||||||
checkInitComplete()
|
|
||||||
console.log(info("Stopping services, this may take a moment."))
|
|
||||||
await handleError(async () => {
|
|
||||||
await compose.stop()
|
|
||||||
})
|
|
||||||
console.log(success("Services have been stopped successfully."))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function update() {
|
|
||||||
await checkDockerConfigured()
|
|
||||||
checkInitComplete()
|
|
||||||
if (await confirmation("Do you wish to update you docker-compose.yaml?")) {
|
|
||||||
await downloadFiles()
|
|
||||||
}
|
|
||||||
await handleError(async () => {
|
|
||||||
const status = await compose.ps()
|
|
||||||
const parts = status.out.split("\n")
|
|
||||||
const isUp = parts[2] && parts[2].indexOf("Up") !== -1
|
|
||||||
if (isUp) {
|
|
||||||
console.log(info("Stopping services, this may take a moment."))
|
|
||||||
await compose.stop()
|
|
||||||
}
|
|
||||||
console.log(info("Beginning update, this may take a few minutes."))
|
|
||||||
await compose.pullMany(BUDIBASE_SERVICES, { log: true })
|
|
||||||
if (isUp) {
|
|
||||||
console.log(success("Update complete, restarting services..."))
|
|
||||||
await start()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const command = new Command(`${CommandWords.HOSTING}`)
|
const command = new Command(`${CommandWords.HOSTING}`)
|
||||||
.addHelp("Controls self hosting on the Budibase platform.")
|
.addHelp("Controls self hosting on the Budibase platform.")
|
||||||
.addSubOption(
|
.addSubOption(
|
||||||
"--init [type]",
|
"--init [type]",
|
||||||
"Configure a self hosted platform in current directory, type can be unspecified or 'quick'.",
|
"Configure a self hosted platform in current directory, type can be unspecified, 'quick' or 'single'.",
|
||||||
init
|
init
|
||||||
)
|
)
|
||||||
.addSubOption(
|
.addSubOption(
|
||||||
|
@ -181,5 +35,16 @@ const command = new Command(`${CommandWords.HOSTING}`)
|
||||||
"Update the Budibase images to the latest version.",
|
"Update the Budibase images to the latest version.",
|
||||||
update
|
update
|
||||||
)
|
)
|
||||||
|
.addSubOption(
|
||||||
|
"--watch-plugin-dir [directory]",
|
||||||
|
"Add plugin directory watching to a Budibase install.",
|
||||||
|
watchPlugins
|
||||||
|
)
|
||||||
|
.addSubOption(
|
||||||
|
"--gen-user",
|
||||||
|
"Create an admin user automatically as part of first start.",
|
||||||
|
generateUser
|
||||||
|
)
|
||||||
|
.addSubOption("--single", "Specify this with init to use the single image.")
|
||||||
|
|
||||||
exports.command = command
|
exports.command = command
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
const { InitTypes, AnalyticsEvents } = require("../constants")
|
||||||
|
const { confirmation } = require("../questions")
|
||||||
|
const { captureEvent } = require("../events")
|
||||||
|
const makeFiles = require("./makeFiles")
|
||||||
|
const axios = require("axios")
|
||||||
|
const { parseEnv } = require("../utils")
|
||||||
|
const { checkDockerConfigured, downloadFiles } = require("./utils")
|
||||||
|
const { watchPlugins } = require("./watch")
|
||||||
|
const { generateUser } = require("./genUser")
|
||||||
|
|
||||||
|
const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"
|
||||||
|
|
||||||
|
async function getInitConfig(type, isQuick, port) {
|
||||||
|
const config = isQuick ? makeFiles.QUICK_CONFIG : {}
|
||||||
|
if (type === InitTypes.DIGITAL_OCEAN) {
|
||||||
|
try {
|
||||||
|
const output = await axios.get(DO_USER_DATA_URL)
|
||||||
|
const response = parseEnv(output.data)
|
||||||
|
for (let [key, value] of Object.entries(makeFiles.ConfigMap)) {
|
||||||
|
if (response[key]) {
|
||||||
|
config[value] = response[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// don't need to handle error, just don't do anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// override port
|
||||||
|
if (port) {
|
||||||
|
config[makeFiles.ConfigMap.MAIN_PORT] = port
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.init = async opts => {
|
||||||
|
let type, isSingle, watchDir, genUser, port, silent
|
||||||
|
if (typeof opts === "string") {
|
||||||
|
type = opts
|
||||||
|
} else {
|
||||||
|
type = opts["init"]
|
||||||
|
isSingle = opts["single"]
|
||||||
|
watchDir = opts["watchPluginDir"]
|
||||||
|
genUser = opts["genUser"]
|
||||||
|
port = opts["port"]
|
||||||
|
silent = opts["silent"]
|
||||||
|
}
|
||||||
|
const isQuick = type === InitTypes.QUICK || type === InitTypes.DIGITAL_OCEAN
|
||||||
|
await checkDockerConfigured()
|
||||||
|
if (!isQuick) {
|
||||||
|
const shouldContinue = await confirmation(
|
||||||
|
"This will create multiple files in current directory, should continue?"
|
||||||
|
)
|
||||||
|
if (!shouldContinue) {
|
||||||
|
console.log("Stopping.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
captureEvent(AnalyticsEvents.SelfHostInit, {
|
||||||
|
type,
|
||||||
|
})
|
||||||
|
const config = await getInitConfig(type, isQuick, port)
|
||||||
|
if (!isSingle) {
|
||||||
|
await downloadFiles()
|
||||||
|
await makeFiles.makeEnv(config, silent)
|
||||||
|
} else {
|
||||||
|
await makeFiles.makeSingleCompose(config, silent)
|
||||||
|
}
|
||||||
|
if (watchDir) {
|
||||||
|
await watchPlugins(watchDir, silent)
|
||||||
|
}
|
||||||
|
if (genUser) {
|
||||||
|
const inputPassword = typeof genUser === "string" ? genUser : null
|
||||||
|
await generateUser(inputPassword, silent)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
const { number } = require("../questions")
|
|
||||||
const { success } = require("../utils")
|
|
||||||
const fs = require("fs")
|
|
||||||
const path = require("path")
|
|
||||||
const randomString = require("randomstring")
|
|
||||||
|
|
||||||
const FILE_PATH = path.resolve("./.env")
|
|
||||||
|
|
||||||
function getContents(port) {
|
|
||||||
return `
|
|
||||||
# Use the main port in the builder for your self hosting URL, e.g. localhost:10000
|
|
||||||
MAIN_PORT=${port}
|
|
||||||
|
|
||||||
# This section contains all secrets pertaining to the system
|
|
||||||
JWT_SECRET=${randomString.generate()}
|
|
||||||
MINIO_ACCESS_KEY=${randomString.generate()}
|
|
||||||
MINIO_SECRET_KEY=${randomString.generate()}
|
|
||||||
COUCH_DB_PASSWORD=${randomString.generate()}
|
|
||||||
COUCH_DB_USER=${randomString.generate()}
|
|
||||||
REDIS_PASSWORD=${randomString.generate()}
|
|
||||||
INTERNAL_API_KEY=${randomString.generate()}
|
|
||||||
|
|
||||||
# This section contains variables that do not need to be altered under normal circumstances
|
|
||||||
APP_PORT=4002
|
|
||||||
WORKER_PORT=4003
|
|
||||||
MINIO_PORT=4004
|
|
||||||
COUCH_DB_PORT=4005
|
|
||||||
REDIS_PORT=6379
|
|
||||||
WATCHTOWER_PORT=6161
|
|
||||||
BUDIBASE_ENVIRONMENT=PRODUCTION`
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.filePath = FILE_PATH
|
|
||||||
module.exports.ConfigMap = {
|
|
||||||
MAIN_PORT: "port",
|
|
||||||
}
|
|
||||||
module.exports.QUICK_CONFIG = {
|
|
||||||
key: "budibase",
|
|
||||||
port: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.make = async (inputs = {}) => {
|
|
||||||
const hostingPort =
|
|
||||||
inputs.port ||
|
|
||||||
(await number(
|
|
||||||
"Please enter the port on which you want your installation to run: ",
|
|
||||||
10000
|
|
||||||
))
|
|
||||||
const fileContents = getContents(hostingPort)
|
|
||||||
fs.writeFileSync(FILE_PATH, fileContents)
|
|
||||||
console.log(
|
|
||||||
success(
|
|
||||||
"Configuration has been written successfully - please check .env file for more details."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.get = property => {
|
|
||||||
const props = fs.readFileSync(FILE_PATH, "utf8").split(property)
|
|
||||||
if (props[0].charAt(0) === "=") {
|
|
||||||
property = props[0]
|
|
||||||
} else {
|
|
||||||
property = props[1]
|
|
||||||
}
|
|
||||||
return property.split("=")[1].split("\n")[0]
|
|
||||||
}
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
const { number } = require("../questions")
|
||||||
|
const { success, stringifyToDotEnv } = require("../utils")
|
||||||
|
const fs = require("fs")
|
||||||
|
const path = require("path")
|
||||||
|
const randomString = require("randomstring")
|
||||||
|
const yaml = require("yaml")
|
||||||
|
const { getAppService } = require("./utils")
|
||||||
|
|
||||||
|
const SINGLE_IMAGE = "budibase/budibase:latest"
|
||||||
|
const VOL_NAME = "budibase_data"
|
||||||
|
const COMPOSE_PATH = path.resolve("./docker-compose.yaml")
|
||||||
|
const ENV_PATH = path.resolve("./.env")
|
||||||
|
|
||||||
|
function getSecrets(opts = { single: false }) {
|
||||||
|
const secrets = [
|
||||||
|
"JWT_SECRET",
|
||||||
|
"MINIO_ACCESS_KEY",
|
||||||
|
"MINIO_SECRET_KEY",
|
||||||
|
"REDIS_PASSWORD",
|
||||||
|
"INTERNAL_API_KEY",
|
||||||
|
]
|
||||||
|
const obj = {}
|
||||||
|
secrets.forEach(secret => (obj[secret] = randomString.generate()))
|
||||||
|
// setup couch creds separately
|
||||||
|
if (opts && opts.single) {
|
||||||
|
obj["COUCHDB_USER"] = "admin"
|
||||||
|
obj["COUCHDB_PASSWORD"] = randomString.generate()
|
||||||
|
} else {
|
||||||
|
obj["COUCH_DB_USER"] = "admin"
|
||||||
|
obj["COUCH_DB_PASSWORD"] = randomString.generate()
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSingleCompose(port) {
|
||||||
|
const singleComposeObj = {
|
||||||
|
version: "3",
|
||||||
|
services: {
|
||||||
|
budibase: {
|
||||||
|
restart: "unless-stopped",
|
||||||
|
image: SINGLE_IMAGE,
|
||||||
|
ports: [`${port}:80`],
|
||||||
|
environment: getSecrets({ single: true }),
|
||||||
|
volumes: [`${VOL_NAME}:/data`],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
volumes: {
|
||||||
|
[VOL_NAME]: {
|
||||||
|
driver: "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return yaml.stringify(singleComposeObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnv(port) {
|
||||||
|
const partOne = stringifyToDotEnv({
|
||||||
|
MAIN_PORT: port,
|
||||||
|
})
|
||||||
|
const partTwo = stringifyToDotEnv(getSecrets())
|
||||||
|
const partThree = stringifyToDotEnv({
|
||||||
|
APP_PORT: 4002,
|
||||||
|
WORKER_PORT: 4003,
|
||||||
|
MINIO_PORT: 4004,
|
||||||
|
COUCH_DB_PORT: 4005,
|
||||||
|
REDIS_PORT: 6379,
|
||||||
|
WATCHTOWER_PORT: 6161,
|
||||||
|
BUDIBASE_ENVIRONMENT: "PRODUCTION",
|
||||||
|
})
|
||||||
|
return [
|
||||||
|
"# Use the main port in the builder for your self hosting URL, e.g. localhost:10000",
|
||||||
|
partOne,
|
||||||
|
"# This section contains all secrets pertaining to the system",
|
||||||
|
partTwo,
|
||||||
|
"# This section contains variables that do not need to be altered under normal circumstances",
|
||||||
|
partThree,
|
||||||
|
].join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.ENV_PATH = ENV_PATH
|
||||||
|
exports.COMPOSE_PATH = COMPOSE_PATH
|
||||||
|
|
||||||
|
module.exports.ConfigMap = {
|
||||||
|
MAIN_PORT: "port",
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.QUICK_CONFIG = {
|
||||||
|
key: "budibase",
|
||||||
|
port: 10000,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function make(path, contentsFn, inputs = {}, silent) {
|
||||||
|
const port =
|
||||||
|
inputs.port ||
|
||||||
|
(await number(
|
||||||
|
"Please enter the port on which you want your installation to run: ",
|
||||||
|
10000
|
||||||
|
))
|
||||||
|
const fileContents = contentsFn(port)
|
||||||
|
fs.writeFileSync(path, fileContents)
|
||||||
|
if (!silent) {
|
||||||
|
console.log(
|
||||||
|
success(
|
||||||
|
`Configuration has been written successfully - please check ${path} for more details.`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.makeEnv = async (inputs = {}, silent) => {
|
||||||
|
return make(ENV_PATH, getEnv, inputs, silent)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.makeSingleCompose = async (inputs = {}, silent) => {
|
||||||
|
return make(COMPOSE_PATH, getSingleCompose, inputs, silent)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getEnvProperty = property => {
|
||||||
|
const props = fs.readFileSync(ENV_PATH, "utf8").split(property)
|
||||||
|
if (props[0].charAt(0) === "=") {
|
||||||
|
property = props[0]
|
||||||
|
} else {
|
||||||
|
property = props[1]
|
||||||
|
}
|
||||||
|
return property.split("=")[1].split("\n")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getComposeProperty = property => {
|
||||||
|
const { service } = getAppService(COMPOSE_PATH)
|
||||||
|
if (property === "port" && Array.isArray(service.ports)) {
|
||||||
|
const port = service.ports[0]
|
||||||
|
return port.split(":")[0]
|
||||||
|
} else if (service.environment) {
|
||||||
|
return service.environment[property]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
const {
|
||||||
|
checkDockerConfigured,
|
||||||
|
checkInitComplete,
|
||||||
|
handleError,
|
||||||
|
} = require("./utils")
|
||||||
|
const { info, success } = require("../utils")
|
||||||
|
const makeFiles = require("./makeFiles")
|
||||||
|
const compose = require("docker-compose")
|
||||||
|
const fs = require("fs")
|
||||||
|
|
||||||
|
exports.start = async () => {
|
||||||
|
await checkDockerConfigured()
|
||||||
|
checkInitComplete()
|
||||||
|
console.log(
|
||||||
|
info(
|
||||||
|
"Starting services, this may take a moment - first time this may take a few minutes to download images."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
let port
|
||||||
|
if (fs.existsSync(makeFiles.ENV_PATH)) {
|
||||||
|
port = makeFiles.getEnvProperty("MAIN_PORT")
|
||||||
|
} else {
|
||||||
|
port = makeFiles.getComposeProperty("port")
|
||||||
|
}
|
||||||
|
await handleError(async () => {
|
||||||
|
// need to log as it makes it more clear
|
||||||
|
await compose.upAll({ cwd: "./", log: true })
|
||||||
|
})
|
||||||
|
console.log(
|
||||||
|
success(
|
||||||
|
`Services started, please go to http://localhost:${port} for next steps.`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
const {
|
||||||
|
checkDockerConfigured,
|
||||||
|
checkInitComplete,
|
||||||
|
handleError,
|
||||||
|
} = require("./utils")
|
||||||
|
const { info } = require("../utils")
|
||||||
|
const compose = require("docker-compose")
|
||||||
|
|
||||||
|
exports.status = async () => {
|
||||||
|
await checkDockerConfigured()
|
||||||
|
checkInitComplete()
|
||||||
|
console.log(info("Budibase status"))
|
||||||
|
await handleError(async () => {
|
||||||
|
const response = await compose.ps()
|
||||||
|
console.log(response.out)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
const {
|
||||||
|
checkDockerConfigured,
|
||||||
|
checkInitComplete,
|
||||||
|
handleError,
|
||||||
|
} = require("./utils")
|
||||||
|
const { info, success } = require("../utils")
|
||||||
|
const compose = require("docker-compose")
|
||||||
|
|
||||||
|
exports.stop = async () => {
|
||||||
|
await checkDockerConfigured()
|
||||||
|
checkInitComplete()
|
||||||
|
console.log(info("Stopping services, this may take a moment."))
|
||||||
|
await handleError(async () => {
|
||||||
|
await compose.stop()
|
||||||
|
})
|
||||||
|
console.log(success("Services have been stopped successfully."))
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
const {
|
||||||
|
checkDockerConfigured,
|
||||||
|
checkInitComplete,
|
||||||
|
downloadFiles,
|
||||||
|
handleError,
|
||||||
|
getServices,
|
||||||
|
} = require("./utils")
|
||||||
|
const { confirmation } = require("../questions")
|
||||||
|
const compose = require("docker-compose")
|
||||||
|
const { COMPOSE_PATH } = require("./makeFiles")
|
||||||
|
const { info, success } = require("../utils")
|
||||||
|
const { start } = require("./start")
|
||||||
|
|
||||||
|
const BB_COMPOSE_SERVICES = ["app-service", "worker-service", "proxy-service"]
|
||||||
|
const BB_SINGLE_SERVICE = ["budibase"]
|
||||||
|
|
||||||
|
exports.update = async () => {
|
||||||
|
const { services } = getServices(COMPOSE_PATH)
|
||||||
|
const isSingle = Object.keys(services).length === 1
|
||||||
|
await checkDockerConfigured()
|
||||||
|
checkInitComplete()
|
||||||
|
if (
|
||||||
|
!isSingle &&
|
||||||
|
(await confirmation("Do you wish to update you docker-compose.yaml?"))
|
||||||
|
) {
|
||||||
|
await downloadFiles()
|
||||||
|
}
|
||||||
|
await handleError(async () => {
|
||||||
|
const status = await compose.ps()
|
||||||
|
const parts = status.out.split("\n")
|
||||||
|
const isUp = parts[2] && parts[2].indexOf("Up") !== -1
|
||||||
|
if (isUp) {
|
||||||
|
console.log(info("Stopping services, this may take a moment."))
|
||||||
|
await compose.stop()
|
||||||
|
}
|
||||||
|
console.log(info("Beginning update, this may take a few minutes."))
|
||||||
|
let services
|
||||||
|
if (isSingle) {
|
||||||
|
services = BB_SINGLE_SERVICE
|
||||||
|
} else {
|
||||||
|
services = BB_COMPOSE_SERVICES
|
||||||
|
}
|
||||||
|
await compose.pullMany(services, { log: true })
|
||||||
|
if (isUp) {
|
||||||
|
console.log(success("Update complete, restarting services..."))
|
||||||
|
await start()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
const { lookpath } = require("lookpath")
|
||||||
|
const fs = require("fs")
|
||||||
|
const makeFiles = require("./makeFiles")
|
||||||
|
const { logErrorToFile, downloadFile, error } = require("../utils")
|
||||||
|
const yaml = require("yaml")
|
||||||
|
|
||||||
|
const ERROR_FILE = "docker-error.log"
|
||||||
|
const FILE_URLS = [
|
||||||
|
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
exports.downloadFiles = async () => {
|
||||||
|
const promises = []
|
||||||
|
for (let url of FILE_URLS) {
|
||||||
|
const fileName = url.split("/").slice(-1)[0]
|
||||||
|
promises.push(downloadFile(url, `./${fileName}`))
|
||||||
|
}
|
||||||
|
await Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.checkDockerConfigured = async () => {
|
||||||
|
const error =
|
||||||
|
"docker/docker-compose has not been installed, please follow instructions at: https://docs.budibase.com/docs/docker-compose"
|
||||||
|
const docker = await lookpath("docker")
|
||||||
|
const compose = await lookpath("docker-compose")
|
||||||
|
if (!docker || !compose) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.checkInitComplete = () => {
|
||||||
|
if (
|
||||||
|
!fs.existsSync(makeFiles.ENV_PATH) &&
|
||||||
|
!fs.existsSync(makeFiles.COMPOSE_PATH)
|
||||||
|
) {
|
||||||
|
throw "Please run the hosting --init command before any other hosting command."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.handleError = async func => {
|
||||||
|
try {
|
||||||
|
await func()
|
||||||
|
} catch (err) {
|
||||||
|
if (err && err.err) {
|
||||||
|
logErrorToFile(ERROR_FILE, err.err)
|
||||||
|
}
|
||||||
|
throw `Failed to start - logs written to file: ${ERROR_FILE}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getServices = path => {
|
||||||
|
const dockerYaml = fs.readFileSync(path, "utf8")
|
||||||
|
const parsedYaml = yaml.parse(dockerYaml)
|
||||||
|
return { yaml: parsedYaml, services: parsedYaml.services }
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getAppService = path => {
|
||||||
|
const { yaml, services } = exports.getServices(path),
|
||||||
|
serviceList = Object.keys(services)
|
||||||
|
let service
|
||||||
|
if (services["app-service"]) {
|
||||||
|
service = services["app-service"]
|
||||||
|
} else if (serviceList.length === 1) {
|
||||||
|
service = services[serviceList[0]]
|
||||||
|
}
|
||||||
|
return { yaml, service }
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateDockerComposeService = updateFn => {
|
||||||
|
const opts = ["docker-compose.yaml", "docker-compose.yml"]
|
||||||
|
const dockerFilePath = opts.find(name => fs.existsSync(name))
|
||||||
|
if (!dockerFilePath) {
|
||||||
|
console.log(error("Unable to locate docker-compose YAML."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { yaml: parsedYaml, service } = exports.getAppService(dockerFilePath)
|
||||||
|
if (!service) {
|
||||||
|
console.log(
|
||||||
|
error(
|
||||||
|
"Unable to locate service within compose file, is it a valid Budibase configuration?"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateFn(service)
|
||||||
|
fs.writeFileSync(dockerFilePath, yaml.stringify(parsedYaml))
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
const { resolve } = require("path")
|
||||||
|
const fs = require("fs")
|
||||||
|
const { error, success } = require("../utils")
|
||||||
|
const { updateDockerComposeService } = require("./utils")
|
||||||
|
|
||||||
|
exports.watchPlugins = async (pluginPath, silent) => {
|
||||||
|
const PLUGIN_PATH = "/plugins"
|
||||||
|
// get absolute path
|
||||||
|
pluginPath = resolve(pluginPath)
|
||||||
|
if (!fs.existsSync(pluginPath)) {
|
||||||
|
console.log(
|
||||||
|
error(
|
||||||
|
`The directory "${pluginPath}" does not exist, please create and then try again.`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateDockerComposeService(service => {
|
||||||
|
// set environment variable
|
||||||
|
service.environment["PLUGINS_DIR"] = PLUGIN_PATH
|
||||||
|
// add volumes to parsed yaml
|
||||||
|
if (!service.volumes) {
|
||||||
|
service.volumes = []
|
||||||
|
}
|
||||||
|
const found = service.volumes.find(vol => vol.includes(PLUGIN_PATH))
|
||||||
|
if (found) {
|
||||||
|
service.volumes.splice(service.volumes.indexOf(found), 1)
|
||||||
|
}
|
||||||
|
service.volumes.push(`${pluginPath}:${PLUGIN_PATH}`)
|
||||||
|
})
|
||||||
|
if (!silent) {
|
||||||
|
console.log(
|
||||||
|
success(`Docker compose configured to watch directory: ${pluginPath}`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
const Command = require("../structures/Command")
|
const Command = require("../structures/Command")
|
||||||
const { CommandWords, AnalyticsEvents } = require("../constants")
|
const { CommandWords, AnalyticsEvents, InitTypes } = require("../constants")
|
||||||
const { getSkeleton, fleshOutSkeleton } = require("./skeleton")
|
const { getSkeleton, fleshOutSkeleton } = require("./skeleton")
|
||||||
const questions = require("../questions")
|
const questions = require("../questions")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
|
@ -9,6 +9,10 @@ const { runPkgCommand } = require("../exec")
|
||||||
const { join } = require("path")
|
const { join } = require("path")
|
||||||
const { success, error, info, moveDirectory } = require("../utils")
|
const { success, error, info, moveDirectory } = require("../utils")
|
||||||
const { captureEvent } = require("../events")
|
const { captureEvent } = require("../events")
|
||||||
|
const fp = require("find-free-port")
|
||||||
|
const { GENERATED_USER_EMAIL } = require("../constants")
|
||||||
|
const { init: hostingInit } = require("../hosting/init")
|
||||||
|
const { start: hostingStart } = require("../hosting/start")
|
||||||
|
|
||||||
function checkInPlugin() {
|
function checkInPlugin() {
|
||||||
if (!fs.existsSync("package.json")) {
|
if (!fs.existsSync("package.json")) {
|
||||||
|
@ -141,6 +145,29 @@ async function watch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function dev() {
|
||||||
|
const pluginDir = await questions.string("Directory to watch", "./")
|
||||||
|
const [port] = await fp(10000)
|
||||||
|
const password = "admin"
|
||||||
|
await hostingInit({
|
||||||
|
init: InitTypes.QUICK,
|
||||||
|
single: true,
|
||||||
|
watchPluginDir: pluginDir,
|
||||||
|
genUser: password,
|
||||||
|
port,
|
||||||
|
silent: true,
|
||||||
|
})
|
||||||
|
await hostingStart()
|
||||||
|
console.log(success(`Configuration has been written to docker-compose.yaml`))
|
||||||
|
console.log(
|
||||||
|
success("Development environment started successfully - connect at: ") +
|
||||||
|
info(`http://localhost:${port}`)
|
||||||
|
)
|
||||||
|
console.log(success("Use the following credentials to login:"))
|
||||||
|
console.log(success("Email: ") + info(GENERATED_USER_EMAIL))
|
||||||
|
console.log(success("Password: ") + info(password))
|
||||||
|
}
|
||||||
|
|
||||||
const command = new Command(`${CommandWords.PLUGIN}`)
|
const command = new Command(`${CommandWords.PLUGIN}`)
|
||||||
.addHelp(
|
.addHelp(
|
||||||
"Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
|
"Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
|
||||||
|
@ -160,5 +187,10 @@ const command = new Command(`${CommandWords.PLUGIN}`)
|
||||||
"Automatically build any changes to your plugin.",
|
"Automatically build any changes to your plugin.",
|
||||||
watch
|
watch
|
||||||
)
|
)
|
||||||
|
.addSubOption(
|
||||||
|
"--dev",
|
||||||
|
"Run a development environment which automatically watches the current directory.",
|
||||||
|
dev
|
||||||
|
)
|
||||||
|
|
||||||
exports.command = command
|
exports.command = command
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
const { getSubHelpDescription, getHelpDescription, error } = require("../utils")
|
const {
|
||||||
|
getSubHelpDescription,
|
||||||
|
getHelpDescription,
|
||||||
|
error,
|
||||||
|
capitaliseFirstLetter,
|
||||||
|
} = require("../utils")
|
||||||
|
|
||||||
class Command {
|
class Command {
|
||||||
constructor(command, func = null) {
|
constructor(command, func = null) {
|
||||||
|
@ -8,6 +13,15 @@ class Command {
|
||||||
this.func = func
|
this.func = func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertToCommander(lookup) {
|
||||||
|
const parts = lookup.toLowerCase().split("-")
|
||||||
|
// camel case, separate out first
|
||||||
|
const first = parts.shift()
|
||||||
|
return [first]
|
||||||
|
.concat(parts.map(part => capitaliseFirstLetter(part)))
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
|
|
||||||
addHelp(help) {
|
addHelp(help) {
|
||||||
this.help = help
|
this.help = help
|
||||||
return this
|
return this
|
||||||
|
@ -25,10 +39,7 @@ class Command {
|
||||||
command = command.description(getHelpDescription(thisCmd.help))
|
command = command.description(getHelpDescription(thisCmd.help))
|
||||||
}
|
}
|
||||||
for (let opt of thisCmd.opts) {
|
for (let opt of thisCmd.opts) {
|
||||||
command = command.option(
|
command = command.option(opt.command, getSubHelpDescription(opt.help))
|
||||||
`${opt.command}`,
|
|
||||||
getSubHelpDescription(opt.help)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
command.helpOption(
|
command.helpOption(
|
||||||
"--help",
|
"--help",
|
||||||
|
@ -36,17 +47,25 @@ class Command {
|
||||||
)
|
)
|
||||||
command.action(async options => {
|
command.action(async options => {
|
||||||
try {
|
try {
|
||||||
let executed = false
|
let executed = false,
|
||||||
|
found = false
|
||||||
for (let opt of thisCmd.opts) {
|
for (let opt of thisCmd.opts) {
|
||||||
const lookup = opt.command.split(" ")[0].replace("--", "")
|
let lookup = opt.command.split(" ")[0].replace("--", "")
|
||||||
if (!executed && options[lookup]) {
|
// need to handle how commander converts watch-plugin-dir to watchPluginDir
|
||||||
|
lookup = this.convertToCommander(lookup)
|
||||||
|
found = !executed && options[lookup]
|
||||||
|
if (found && opt.func) {
|
||||||
const input =
|
const input =
|
||||||
Object.keys(options).length > 1 ? options : options[lookup]
|
Object.keys(options).length > 1 ? options : options[lookup]
|
||||||
await opt.func(input)
|
await opt.func(input)
|
||||||
executed = true
|
executed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!executed) {
|
if (found && !executed) {
|
||||||
|
console.log(
|
||||||
|
error(`${Object.keys(options)[0]} is an option, not an operation.`)
|
||||||
|
)
|
||||||
|
} else if (!executed) {
|
||||||
console.log(error(`Unknown ${this.command} option.`))
|
console.log(error(`Unknown ${this.command} option.`))
|
||||||
command.help()
|
command.help()
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,10 @@ class ConfigManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(key, value) {
|
setValue(key, value) {
|
||||||
const updated = {
|
this.config = {
|
||||||
...this.config,
|
...this.config,
|
||||||
[key]: value,
|
[key]: value,
|
||||||
}
|
}
|
||||||
this.config = updated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeKey(key) {
|
removeKey(key) {
|
||||||
|
|
|
@ -84,3 +84,15 @@ exports.moveDirectory = (oldPath, newPath) => {
|
||||||
}
|
}
|
||||||
fs.rmdirSync(oldPath)
|
fs.rmdirSync(oldPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.capitaliseFirstLetter = str => {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.stringifyToDotEnv = json => {
|
||||||
|
let str = ""
|
||||||
|
for (let [key, value] of Object.entries(json)) {
|
||||||
|
str += `${key}=${value}\n`
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
|
@ -1113,6 +1113,11 @@ fill-range@^7.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range "^5.0.1"
|
to-regex-range "^5.0.1"
|
||||||
|
|
||||||
|
find-free-port@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-free-port/-/find-free-port-2.0.0.tgz#4b22e5f6579eb1a38c41ac6bcb3efed1b6da9b1b"
|
||||||
|
integrity sha512-J1j8gfEVf5FN4PR5w5wrZZ7NYs2IvqsHcd03cAeQx3Ec/mo+lKceaVNhpsRKoZpZKbId88o8qh+dwUwzBV6WCg==
|
||||||
|
|
||||||
find-replace@^3.0.0:
|
find-replace@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
|
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
|
||||||
|
@ -3080,6 +3085,11 @@ yallist@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
|
yaml@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec"
|
||||||
|
integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==
|
||||||
|
|
||||||
yargs-parser@^20.2.2:
|
yargs-parser@^20.2.2:
|
||||||
version "20.2.9"
|
version "20.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"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.4.18-alpha.1",
|
"@budibase/bbui": "2.0.14-alpha.0",
|
||||||
"@budibase/frontend-core": "1.4.18-alpha.1",
|
"@budibase/frontend-core": "2.0.14-alpha.0",
|
||||||
"@budibase/string-templates": "1.4.18-alpha.1",
|
"@budibase/string-templates": "2.0.14-alpha.0",
|
||||||
"@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",
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
themeStore,
|
themeStore,
|
||||||
appStore,
|
appStore,
|
||||||
devToolsStore,
|
devToolsStore,
|
||||||
|
environmentStore,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
|
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
|
||||||
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
|
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
|
||||||
|
@ -47,6 +48,8 @@
|
||||||
!$builderStore.inBuilder &&
|
!$builderStore.inBuilder &&
|
||||||
$devToolsStore.enabled &&
|
$devToolsStore.enabled &&
|
||||||
!$routeStore.queryParams?.peek
|
!$routeStore.queryParams?.peek
|
||||||
|
$: objectStoreUrl = $environmentStore.cloud ? "https://cdn.budi.live" : ""
|
||||||
|
$: pluginsUrl = `${objectStoreUrl}/plugins`
|
||||||
|
|
||||||
// Handle no matching route
|
// Handle no matching route
|
||||||
$: {
|
$: {
|
||||||
|
@ -92,7 +95,8 @@
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
{#if $builderStore.usedPlugins?.length}
|
{#if $builderStore.usedPlugins?.length}
|
||||||
{#each $builderStore.usedPlugins as plugin (plugin.hash)}
|
{#each $builderStore.usedPlugins as plugin (plugin.hash)}
|
||||||
<script src={`/plugins/${plugin.jsUrl}?r=${plugin.hash || ""}`}></script>
|
<script
|
||||||
|
src={`${pluginsUrl}/${plugin.jsUrl}?r=${plugin.hash || ""}`}></script>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import ClientApp from "./components/ClientApp.svelte"
|
import ClientApp from "./components/ClientApp.svelte"
|
||||||
import { componentStore, builderStore, appStore, devToolsStore } from "./stores"
|
import {
|
||||||
|
componentStore,
|
||||||
|
builderStore,
|
||||||
|
appStore,
|
||||||
|
devToolsStore,
|
||||||
|
environmentStore,
|
||||||
|
} 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"
|
import { initWebsocket } from "./websocket.js"
|
||||||
|
@ -15,7 +21,7 @@ loadSpectrumIcons()
|
||||||
|
|
||||||
let app
|
let app
|
||||||
|
|
||||||
const loadBudibase = () => {
|
const loadBudibase = async () => {
|
||||||
// Update builder store with any builder flags
|
// Update builder store with any builder flags
|
||||||
builderStore.set({
|
builderStore.set({
|
||||||
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
||||||
|
@ -36,6 +42,9 @@ const loadBudibase = () => {
|
||||||
// server rendered app HTML
|
// server rendered app HTML
|
||||||
appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
|
appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
|
||||||
|
|
||||||
|
// Fetch environment info
|
||||||
|
await environmentStore.actions.fetchEnvironment()
|
||||||
|
|
||||||
// Enable dev tools or not. We need to be using a dev app and not inside
|
// Enable dev tools or not. We need to be using a dev app and not inside
|
||||||
// the builder preview to enable them.
|
// the builder preview to enable them.
|
||||||
const enableDevTools = !get(builderStore).inBuilder && get(appStore).isDevApp
|
const enableDevTools = !get(builderStore).inBuilder && get(appStore).isDevApp
|
||||||
|
|
|
@ -98,6 +98,9 @@ const createBuilderStore = () => {
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify the builder so we can reload component definitions
|
||||||
|
dispatchEvent("reload-plugin")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { routeStore } from "./routes"
|
import { routeStore } from "./routes"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
import { environmentStore } from "./environment"
|
|
||||||
|
|
||||||
export async function initialise() {
|
export async function initialise() {
|
||||||
await routeStore.actions.fetchRoutes()
|
await routeStore.actions.fetchRoutes()
|
||||||
await appStore.actions.fetchAppDefinition()
|
await appStore.actions.fetchAppDefinition()
|
||||||
await environmentStore.actions.fetchEnvironment()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
import { builderStore, environmentStore } from "./stores/index.js"
|
import {
|
||||||
|
builderStore,
|
||||||
|
environmentStore,
|
||||||
|
notificationStore,
|
||||||
|
} from "./stores/index.js"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { io } from "socket.io-client"
|
import { io } from "socket.io-client"
|
||||||
|
|
||||||
|
let socket
|
||||||
|
|
||||||
export const initWebsocket = () => {
|
export const initWebsocket = () => {
|
||||||
const { inBuilder, location } = get(builderStore)
|
const { inBuilder, location } = get(builderStore)
|
||||||
const { cloud } = get(environmentStore)
|
const { cloud } = get(environmentStore)
|
||||||
|
|
||||||
// Only connect when we're inside the builder preview, for now
|
// Only connect when we're inside the builder preview, for now
|
||||||
if (!inBuilder || !location || cloud) {
|
if (!inBuilder || !location || cloud || socket) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,20 +22,20 @@ export const initWebsocket = () => {
|
||||||
const proto = tls ? "wss:" : "ws:"
|
const proto = tls ? "wss:" : "ws:"
|
||||||
const host = location.hostname
|
const host = location.hostname
|
||||||
const port = location.port || (tls ? 443 : 80)
|
const port = location.port || (tls ? 443 : 80)
|
||||||
const socket = io(`${proto}//${host}:${port}`, {
|
socket = io(`${proto}//${host}:${port}`, {
|
||||||
path: "/socket/client",
|
path: "/socket/client",
|
||||||
// Cap reconnection attempts to 10 (total of 95 seconds before giving up)
|
// Cap reconnection attempts to 3 (total of 15 seconds before giving up)
|
||||||
reconnectionAttempts: 10,
|
reconnectionAttempts: 3,
|
||||||
// Delay initial reconnection attempt by 5 seconds
|
// Delay reconnection attempt by 5 seconds
|
||||||
reconnectionDelay: 5000,
|
reconnectionDelay: 5000,
|
||||||
// Then decrease to 10 second intervals
|
reconnectionDelayMax: 5000,
|
||||||
reconnectionDelayMax: 10000,
|
// Timeout after 4 seconds so we never stack requests
|
||||||
// Timeout after 5 seconds so we never stack requests
|
timeout: 4000,
|
||||||
timeout: 5000,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
socket.on("plugin-update", data => {
|
socket.on("plugin-update", data => {
|
||||||
builderStore.actions.updateUsedPlugin(data.name, data.hash)
|
builderStore.actions.updateUsedPlugin(data.name, data.hash)
|
||||||
|
notificationStore.actions.info(`"${data.name}" plugin reloaded`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"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.4.18-alpha.1",
|
"@budibase/bbui": "2.0.14-alpha.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,4 +29,13 @@ export const buildRoleEndpoints = API => ({
|
||||||
url: "/api/roles",
|
url: "/api/roles",
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of roles within a specified app.
|
||||||
|
*/
|
||||||
|
getRolesForApp: async appId => {
|
||||||
|
return await API.get({
|
||||||
|
url: `/api/global/roles/${appId}`,
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,10 +8,9 @@ export const buildRowEndpoints = API => ({
|
||||||
if (!tableId || !rowId) {
|
if (!tableId || !rowId) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const row = await API.get({
|
return await API.get({
|
||||||
url: `/api/${tableId}/rows/${rowId}`,
|
url: `/api/${tableId}/rows/${rowId}`,
|
||||||
})
|
})
|
||||||
return (await API.enrichRows([row], tableId))[0]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,7 +40,7 @@ export const OperatorOptions = {
|
||||||
},
|
},
|
||||||
NotContains: {
|
NotContains: {
|
||||||
value: "notContains",
|
value: "notContains",
|
||||||
label: "Does Not Contain",
|
label: "Does not contain",
|
||||||
},
|
},
|
||||||
In: {
|
In: {
|
||||||
value: "oneOf",
|
value: "oneOf",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"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.4.18-alpha.1",
|
"@budibase/backend-core": "2.0.14-alpha.0",
|
||||||
"@budibase/client": "1.4.18-alpha.1",
|
"@budibase/client": "2.0.14-alpha.0",
|
||||||
"@budibase/pro": "1.4.18-alpha.1",
|
"@budibase/pro": "2.0.14-alpha.0",
|
||||||
"@budibase/string-templates": "1.4.18-alpha.1",
|
"@budibase/string-templates": "2.0.14-alpha.0",
|
||||||
"@budibase/types": "1.4.18-alpha.1",
|
"@budibase/types": "2.0.14-alpha.0",
|
||||||
"@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",
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
"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",
|
||||||
"vm2": "3.9.6",
|
"vm2": "3.9.11",
|
||||||
"worker-farm": "1.7.0",
|
"worker-farm": "1.7.0",
|
||||||
"xml2js": "0.4.23",
|
"xml2js": "0.4.23",
|
||||||
"yargs": "13.2.4",
|
"yargs": "13.2.4",
|
||||||
|
|
|
@ -356,7 +356,7 @@ const appPostCreate = async (ctx: any, app: App) => {
|
||||||
await creationEvents(ctx.request, app)
|
await creationEvents(ctx.request, app)
|
||||||
// app import & template creation
|
// app import & template creation
|
||||||
if (ctx.request.body.useTemplate === "true") {
|
if (ctx.request.body.useTemplate === "true") {
|
||||||
const rows = await getUniqueRows([app.appId])
|
const { rows } = await getUniqueRows([app.appId])
|
||||||
const rowCount = rows ? rows.length : 0
|
const rowCount = rows ? rows.length : 0
|
||||||
if (rowCount) {
|
if (rowCount) {
|
||||||
try {
|
try {
|
||||||
|
@ -490,7 +490,7 @@ const destroyApp = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const preDestroyApp = async (ctx: any) => {
|
const preDestroyApp = async (ctx: any) => {
|
||||||
const rows = await getUniqueRows([ctx.params.appId])
|
const { rows } = await getUniqueRows([ctx.params.appId])
|
||||||
ctx.rowCount = rows.length
|
ctx.rowCount = rows.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,10 @@ export async function preview(ctx: any) {
|
||||||
auth: { ...authConfigCtx },
|
auth: { ...authConfigCtx },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { rows, keys, info, extra } = await quotas.addQuery(runFn)
|
|
||||||
|
const { rows, keys, info, extra } = await quotas.addQuery(runFn, {
|
||||||
|
datasourceId: datasource._id,
|
||||||
|
})
|
||||||
const schemaFields: any = {}
|
const schemaFields: any = {}
|
||||||
if (rows?.length > 0) {
|
if (rows?.length > 0) {
|
||||||
for (let key of [...new Set(keys)] as string[]) {
|
for (let key of [...new Set(keys)] as string[]) {
|
||||||
|
@ -234,7 +237,9 @@ async function execute(
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { rows, pagination, extra } = await quotas.addQuery(runFn)
|
const { rows, pagination, extra } = await quotas.addQuery(runFn, {
|
||||||
|
datasourceId: datasource._id,
|
||||||
|
})
|
||||||
if (opts && opts.rowsOnly) {
|
if (opts && opts.rowsOnly) {
|
||||||
ctx.body = rows
|
ctx.body = rows
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -31,8 +31,11 @@ export async function patch(ctx: any): Promise<any> {
|
||||||
return save(ctx)
|
return save(ctx)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { row, table } = await quotas.addQuery(() =>
|
const { row, table } = await quotas.addQuery(
|
||||||
pickApi(tableId).patch(ctx)
|
() => pickApi(tableId).patch(ctx),
|
||||||
|
{
|
||||||
|
datasourceId: tableId,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
|
@ -54,7 +57,9 @@ export const save = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { row, table } = await quotas.addRow(() =>
|
const { row, table } = await quotas.addRow(() =>
|
||||||
quotas.addQuery(() => pickApi(tableId).save(ctx))
|
quotas.addQuery(() => pickApi(tableId).save(ctx), {
|
||||||
|
datasourceId: tableId,
|
||||||
|
})
|
||||||
)
|
)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
|
||||||
|
@ -68,7 +73,9 @@ export const save = async (ctx: any) => {
|
||||||
export async function fetchView(ctx: any) {
|
export async function fetchView(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await quotas.addQuery(() => pickApi(tableId).fetchView(ctx))
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).fetchView(ctx), {
|
||||||
|
datasourceId: tableId,
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +84,9 @@ export async function fetchView(ctx: any) {
|
||||||
export async function fetch(ctx: any) {
|
export async function fetch(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await quotas.addQuery(() => pickApi(tableId).fetch(ctx))
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).fetch(ctx), {
|
||||||
|
datasourceId: tableId,
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -86,7 +95,9 @@ export async function fetch(ctx: any) {
|
||||||
export async function find(ctx: any) {
|
export async function find(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await quotas.addQuery(() => pickApi(tableId).find(ctx))
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).find(ctx), {
|
||||||
|
datasourceId: tableId,
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -98,8 +109,11 @@ export async function destroy(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
let response, row
|
let response, row
|
||||||
if (inputs.rows) {
|
if (inputs.rows) {
|
||||||
let { rows } = await quotas.addQuery(() =>
|
let { rows } = await quotas.addQuery(
|
||||||
pickApi(tableId).bulkDestroy(ctx)
|
() => pickApi(tableId).bulkDestroy(ctx),
|
||||||
|
{
|
||||||
|
datasourceId: tableId,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
await quotas.removeRows(rows.length)
|
await quotas.removeRows(rows.length)
|
||||||
response = rows
|
response = rows
|
||||||
|
@ -107,7 +121,9 @@ export async function destroy(ctx: any) {
|
||||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let resp = await quotas.addQuery(() => pickApi(tableId).destroy(ctx))
|
let resp = await quotas.addQuery(() => pickApi(tableId).destroy(ctx), {
|
||||||
|
datasourceId: tableId,
|
||||||
|
})
|
||||||
await quotas.removeRow()
|
await quotas.removeRow()
|
||||||
response = resp.response
|
response = resp.response
|
||||||
row = resp.row
|
row = resp.row
|
||||||
|
@ -123,7 +139,9 @@ export async function search(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = await quotas.addQuery(() => pickApi(tableId).search(ctx))
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).search(ctx), {
|
||||||
|
datasourceId: tableId,
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -141,8 +159,11 @@ export async function validate(ctx: any) {
|
||||||
export async function fetchEnrichedRow(ctx: any) {
|
export async function fetchEnrichedRow(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await quotas.addQuery(() =>
|
ctx.body = await quotas.addQuery(
|
||||||
pickApi(tableId).fetchEnrichedRow(ctx)
|
() => pickApi(tableId).fetchEnrichedRow(ctx),
|
||||||
|
{
|
||||||
|
datasourceId: tableId,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
|
@ -152,7 +173,9 @@ export async function fetchEnrichedRow(ctx: any) {
|
||||||
export const exportRows = async (ctx: any) => {
|
export const exportRows = async (ctx: any) => {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await quotas.addQuery(() => pickApi(tableId).exportRows(ctx))
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).exportRows(ctx), {
|
||||||
|
datasourceId: tableId,
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,9 @@ export async function destroy(ctx: any) {
|
||||||
await db.bulkDocs(
|
await db.bulkDocs(
|
||||||
rows.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
|
rows.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
|
||||||
)
|
)
|
||||||
await quotas.removeRows(rows.rows.length)
|
await quotas.removeRows(rows.rows.length, {
|
||||||
|
tableId: ctx.params.tableId,
|
||||||
|
})
|
||||||
|
|
||||||
// update linked rows
|
// update linked rows
|
||||||
await updateLinks({
|
await updateLinks({
|
||||||
|
|
|
@ -148,7 +148,9 @@ export async function handleDataImport(user: any, table: any, dataImport: any) {
|
||||||
finalData.push(row)
|
finalData.push(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
await quotas.addRows(finalData.length, () => db.bulkDocs(finalData))
|
await quotas.addRows(finalData.length, () => db.bulkDocs(finalData), {
|
||||||
|
tableId: table._id,
|
||||||
|
})
|
||||||
await events.rows.imported(table, "csv", finalData.length)
|
await events.rows.imported(table, "csv", finalData.length)
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,18 +34,13 @@ describe("/rows", () => {
|
||||||
.expect(status)
|
.expect(status)
|
||||||
|
|
||||||
const getRowUsage = async () => {
|
const getRowUsage = async () => {
|
||||||
return config.doInContext(null, () =>
|
const { total } = await config.doInContext(null, () => quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS))
|
||||||
quotas.getCurrentUsageValue(QuotaUsageType.STATIC, StaticQuotaName.ROWS)
|
return total
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getQueryUsage = async () => {
|
const getQueryUsage = async () => {
|
||||||
return config.doInContext(null, () =>
|
const { total } = await config.doInContext(null, () => quotas.getCurrentUsageValues(QuotaUsageType.MONTHLY, MonthlyQuotaName.QUERIES))
|
||||||
quotas.getCurrentUsageValue(
|
return total
|
||||||
QuotaUsageType.MONTHLY,
|
|
||||||
MonthlyQuotaName.QUERIES
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const assertRowUsage = async expected => {
|
const assertRowUsage = async expected => {
|
||||||
|
@ -60,26 +55,26 @@ describe("/rows", () => {
|
||||||
|
|
||||||
describe("save, load, update", () => {
|
describe("save, load, update", () => {
|
||||||
it("returns a success message when the row is created", async () => {
|
it("returns a success message when the row is created", async () => {
|
||||||
// const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
// const queryUsage = await getQueryUsage()
|
const queryUsage = await getQueryUsage()
|
||||||
//
|
|
||||||
// const res = await request
|
const res = await request
|
||||||
// .post(`/api/${row.tableId}/rows`)
|
.post(`/api/${row.tableId}/rows`)
|
||||||
// .send(row)
|
.send(row)
|
||||||
// .set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
// .expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
// .expect(200)
|
.expect(200)
|
||||||
// expect(res.res.statusMessage).toEqual(`${table.name} saved successfully`)
|
expect(res.res.statusMessage).toEqual(`${table.name} saved successfully`)
|
||||||
// expect(res.body.name).toEqual("Test Contact")
|
expect(res.body.name).toEqual("Test Contact")
|
||||||
// expect(res.body._rev).toBeDefined()
|
expect(res.body._rev).toBeDefined()
|
||||||
// await assertRowUsage(rowUsage + 1)
|
await assertRowUsage(rowUsage + 1)
|
||||||
// await assertQueryUsage(queryUsage + 1)
|
await assertQueryUsage(queryUsage + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates a row successfully", async () => {
|
it("updates a row successfully", async () => {
|
||||||
const existing = await config.createRow()
|
const existing = await config.createRow()
|
||||||
// const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
// const queryUsage = await getQueryUsage()
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/${table._id}/rows`)
|
.post(`/api/${table._id}/rows`)
|
||||||
|
@ -97,8 +92,8 @@ describe("/rows", () => {
|
||||||
`${table.name} updated successfully.`
|
`${table.name} updated successfully.`
|
||||||
)
|
)
|
||||||
expect(res.body.name).toEqual("Updated Name")
|
expect(res.body.name).toEqual("Updated Name")
|
||||||
// await assertRowUsage(rowUsage)
|
await assertRowUsage(rowUsage)
|
||||||
// await assertQueryUsage(queryUsage + 1)
|
await assertQueryUsage(queryUsage + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should load a row", async () => {
|
it("should load a row", async () => {
|
||||||
|
|
|
@ -29,16 +29,11 @@ describe("Run through some parts of the automations system", () => {
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
it("should be able to init in builder", async () => {
|
it("should be able to init in builder", async () => {
|
||||||
await triggers.externalTrigger(basicAutomation(), { a: 1 })
|
await triggers.externalTrigger(basicAutomation(), { a: 1, appId: "app_123" })
|
||||||
await wait(100)
|
await wait(100)
|
||||||
expect(thread.execute).toHaveBeenCalled()
|
expect(thread.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to init in prod", async () => {
|
|
||||||
await triggers.externalTrigger(basicAutomation(), { a: 1 })
|
|
||||||
await wait(100)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should check coercion", async () => {
|
it("should check coercion", async () => {
|
||||||
const table = await config.createTable()
|
const table = await config.createTable()
|
||||||
const automation = basicAutomation()
|
const automation = basicAutomation()
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
getAppId,
|
getAppId,
|
||||||
getProdAppDB,
|
getProdAppDB,
|
||||||
} from "@budibase/backend-core/context"
|
} from "@budibase/backend-core/context"
|
||||||
import { tenancy } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { Automation } from "@budibase/types"
|
import { Automation } from "@budibase/types"
|
||||||
|
|
||||||
|
@ -28,12 +28,14 @@ const jobMessage = (job: any, message: string) => {
|
||||||
|
|
||||||
export async function processEvent(job: any) {
|
export async function processEvent(job: any) {
|
||||||
try {
|
try {
|
||||||
|
const automationId = job.data.automation._id
|
||||||
console.log(jobMessage(job, "running"))
|
console.log(jobMessage(job, "running"))
|
||||||
// need to actually await these so that an error can be captured properly
|
// need to actually await these so that an error can be captured properly
|
||||||
const tenantId = tenancy.getTenantIDFromAppID(job.data.event.appId)
|
return await context.doInContext(job.data.event.appId, async () => {
|
||||||
return await tenancy.doInTenant(tenantId, async () => {
|
|
||||||
const runFn = () => Runner.run(job)
|
const runFn = () => Runner.run(job)
|
||||||
return quotas.addAutomation(runFn)
|
return quotas.addAutomation(runFn, {
|
||||||
|
automationId,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errJson = JSON.stringify(err)
|
const errJson = JSON.stringify(err)
|
||||||
|
|
|
@ -34,8 +34,6 @@ const DocumentType = {
|
||||||
INSTANCE: "inst",
|
INSTANCE: "inst",
|
||||||
LAYOUT: "layout",
|
LAYOUT: "layout",
|
||||||
SCREEN: "screen",
|
SCREEN: "screen",
|
||||||
DATASOURCE: "datasource",
|
|
||||||
DATASOURCE_PLUS: "datasource_plus",
|
|
||||||
QUERY: "query",
|
QUERY: "query",
|
||||||
DEPLOYMENTS: "deployments",
|
DEPLOYMENTS: "deployments",
|
||||||
METADATA: "metadata",
|
METADATA: "metadata",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import googlesheets from "./googlesheets"
|
||||||
import firebase from "./firebase"
|
import firebase from "./firebase"
|
||||||
import redis from "./redis"
|
import redis from "./redis"
|
||||||
import snowflake from "./snowflake"
|
import snowflake from "./snowflake"
|
||||||
|
import oracle from "./oracle"
|
||||||
import { getPlugins } from "../api/controllers/plugin"
|
import { getPlugins } from "../api/controllers/plugin"
|
||||||
import { SourceName, Integration, PluginType } from "@budibase/types"
|
import { SourceName, Integration, PluginType } from "@budibase/types"
|
||||||
import { getDatasourcePlugin } from "../utilities/fileSystem"
|
import { getDatasourcePlugin } from "../utilities/fileSystem"
|
||||||
|
@ -56,8 +57,11 @@ const INTEGRATIONS: { [key: string]: any } = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// optionally add oracle integration if the oracle binary can be installed
|
// optionally add oracle integration if the oracle binary can be installed
|
||||||
if (process.arch && !process.arch.startsWith("arm")) {
|
if (
|
||||||
const oracle = require("./oracle")
|
process.arch &&
|
||||||
|
!process.arch.startsWith("arm") &&
|
||||||
|
oracle.integration.isInstalled()
|
||||||
|
) {
|
||||||
DEFINITIONS[SourceName.ORACLE] = oracle.schema
|
DEFINITIONS[SourceName.ORACLE] = oracle.schema
|
||||||
INTEGRATIONS[SourceName.ORACLE] = oracle.integration
|
INTEGRATIONS[SourceName.ORACLE] = oracle.integration
|
||||||
}
|
}
|
||||||
|
@ -78,6 +82,9 @@ module.exports = {
|
||||||
...plugin.schema["schema"],
|
...plugin.schema["schema"],
|
||||||
custom: true,
|
custom: true,
|
||||||
}
|
}
|
||||||
|
if (plugin.iconUrl) {
|
||||||
|
pluginSchemas[sourceId].iconUrl = plugin.iconUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -15,17 +15,22 @@ import {
|
||||||
getSqlQuery,
|
getSqlQuery,
|
||||||
SqlClient,
|
SqlClient,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
import oracledb, {
|
import Sql from "./base/sql"
|
||||||
|
import { FieldTypes } from "../constants"
|
||||||
|
import {
|
||||||
BindParameters,
|
BindParameters,
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionAttributes,
|
ConnectionAttributes,
|
||||||
ExecuteOptions,
|
ExecuteOptions,
|
||||||
Result,
|
Result,
|
||||||
} from "oracledb"
|
} from "oracledb"
|
||||||
import Sql from "./base/sql"
|
let oracledb: any
|
||||||
import { FieldTypes } from "../constants"
|
try {
|
||||||
|
oracledb = require("oracledb")
|
||||||
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT
|
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT
|
||||||
|
} catch (err) {
|
||||||
|
console.log("ORACLEDB is not installed")
|
||||||
|
}
|
||||||
|
|
||||||
interface OracleConfig {
|
interface OracleConfig {
|
||||||
host: string
|
host: string
|
||||||
|
@ -183,6 +188,10 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
return parts.join(" || ")
|
return parts.join(" || ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isInstalled() {
|
||||||
|
return oracledb != null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map the flat tabular columns and constraints data into a nested object
|
* Map the flat tabular columns and constraints data into a nested object
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
accounts,
|
accounts,
|
||||||
db as dbUtils,
|
db as dbUtils,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { QuotaUsage } from "@budibase/pro"
|
import { QuotaUsage } from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
CloudAccount,
|
CloudAccount,
|
||||||
App,
|
App,
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { tenancy, logging } from "@budibase/backend-core"
|
|
||||||
import { plugins } from "@budibase/pro"
|
|
||||||
|
|
||||||
export const run = async () => {
|
|
||||||
try {
|
|
||||||
await tenancy.doInTenant(tenancy.DEFAULT_TENANT_ID, async () => {
|
|
||||||
await plugins.checkPluginQuotas()
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
logging.logAlert("Failed to update plugin quotas", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,15 @@
|
||||||
import { runQuotaMigration } from "./usageQuotas"
|
import { runQuotaMigration } from "./usageQuotas"
|
||||||
import * as syncApps from "./usageQuotas/syncApps"
|
import * as syncApps from "./usageQuotas/syncApps"
|
||||||
import * as syncRows from "./usageQuotas/syncRows"
|
import * as syncRows from "./usageQuotas/syncRows"
|
||||||
|
import * as syncPlugins from "./usageQuotas/syncPlugins"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date:
|
* Synchronise quotas to the state of the db.
|
||||||
* January 2022
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Synchronise the app and row quotas to the state of the db after it was
|
|
||||||
* discovered that the quota resets were still in place and the row quotas
|
|
||||||
* weren't being decremented correctly.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const run = async () => {
|
export const run = async () => {
|
||||||
await runQuotaMigration(async () => {
|
await runQuotaMigration(async () => {
|
||||||
await syncApps.run()
|
await syncApps.run()
|
||||||
await syncRows.run()
|
await syncRows.run()
|
||||||
|
await syncPlugins.run()
|
||||||
})
|
})
|
||||||
}
|
}
|
|
@ -2,11 +2,13 @@ const TestConfig = require("../../../tests/utilities/TestConfiguration")
|
||||||
|
|
||||||
const syncApps = jest.fn()
|
const syncApps = jest.fn()
|
||||||
const syncRows = jest.fn()
|
const syncRows = jest.fn()
|
||||||
|
const syncPlugins = jest.fn()
|
||||||
|
|
||||||
jest.mock("../usageQuotas/syncApps", () => ({ run: syncApps }) )
|
jest.mock("../usageQuotas/syncApps", () => ({ run: syncApps }) )
|
||||||
jest.mock("../usageQuotas/syncRows", () => ({ run: syncRows }) )
|
jest.mock("../usageQuotas/syncRows", () => ({ run: syncRows }) )
|
||||||
|
jest.mock("../usageQuotas/syncPlugins", () => ({ run: syncPlugins }) )
|
||||||
|
|
||||||
const migration = require("../quotas1")
|
const migration = require("../syncQuotas")
|
||||||
|
|
||||||
describe("run", () => {
|
describe("run", () => {
|
||||||
let config = new TestConfig(false)
|
let config = new TestConfig(false)
|
||||||
|
@ -17,9 +19,10 @@ describe("run", () => {
|
||||||
|
|
||||||
afterAll(config.end)
|
afterAll(config.end)
|
||||||
|
|
||||||
it("runs ", async () => {
|
it("run", async () => {
|
||||||
await migration.run()
|
await migration.run()
|
||||||
expect(syncApps).toHaveBeenCalledTimes(1)
|
expect(syncApps).toHaveBeenCalledTimes(1)
|
||||||
expect(syncRows).toHaveBeenCalledTimes(1)
|
expect(syncRows).toHaveBeenCalledTimes(1)
|
||||||
|
expect(syncPlugins).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -5,7 +5,6 @@ import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
|
||||||
|
|
||||||
export const run = async () => {
|
export const run = async () => {
|
||||||
// get app count
|
// get app count
|
||||||
// @ts-ignore
|
|
||||||
const devApps = await getAllApps({ dev: true })
|
const devApps = await getAllApps({ dev: true })
|
||||||
const appCount = devApps ? devApps.length : 0
|
const appCount = devApps ? devApps.length : 0
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { logging } from "@budibase/backend-core"
|
||||||
|
import { plugins } from "@budibase/pro"
|
||||||
|
|
||||||
|
export const run = async () => {
|
||||||
|
try {
|
||||||
|
await plugins.checkPluginQuotas()
|
||||||
|
} catch (err) {
|
||||||
|
logging.logAlert("Failed to update plugin quotas", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,19 +2,28 @@ import { getTenantId } from "@budibase/backend-core/tenancy"
|
||||||
import { getAllApps } from "@budibase/backend-core/db"
|
import { getAllApps } from "@budibase/backend-core/db"
|
||||||
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
|
import { StaticQuotaName, QuotaUsageType } from "@budibase/types"
|
||||||
|
|
||||||
export const run = async () => {
|
export const run = async () => {
|
||||||
// get all rows in all apps
|
// get all rows in all apps
|
||||||
// @ts-ignore
|
|
||||||
const allApps = await getAllApps({ all: true })
|
const allApps = await getAllApps({ all: true })
|
||||||
// @ts-ignore
|
|
||||||
const appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : []
|
const appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : []
|
||||||
const rows = await getUniqueRows(appIds)
|
const { appRows } = await getUniqueRows(appIds)
|
||||||
const rowCount = rows ? rows.length : 0
|
|
||||||
|
// get the counts per app
|
||||||
|
const counts: { [key: string]: number } = {}
|
||||||
|
let rowCount = 0
|
||||||
|
Object.entries(appRows).forEach(([appId, rows]) => {
|
||||||
|
counts[appId] = rows.length
|
||||||
|
rowCount += rows.length
|
||||||
|
})
|
||||||
|
|
||||||
// sync row count
|
// sync row count
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
console.log(`[Tenant: ${tenantId}] Syncing row count: ${rowCount}`)
|
console.log(`[Tenant: ${tenantId}] Syncing row count: ${rowCount}`)
|
||||||
await quotas.setUsage(rowCount, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
|
await quotas.setUsagePerApp(
|
||||||
|
counts,
|
||||||
|
StaticQuotaName.ROWS,
|
||||||
|
QuotaUsageType.STATIC
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import TestConfig from "../../../../tests/utilities/TestConfiguration"
|
||||||
import * as syncRows from "../syncRows"
|
import * as syncRows from "../syncRows"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
|
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
|
||||||
|
const { getProdAppID } = require("@budibase/backend-core/db")
|
||||||
|
|
||||||
describe("syncRows", () => {
|
describe("syncRows", () => {
|
||||||
let config = new TestConfig(false)
|
let config = new TestConfig(false)
|
||||||
|
@ -22,10 +23,11 @@ describe("syncRows", () => {
|
||||||
expect(usageDoc.usageQuota.rows).toEqual(300)
|
expect(usageDoc.usageQuota.rows).toEqual(300)
|
||||||
|
|
||||||
// app 1
|
// app 1
|
||||||
|
const app1 = config.app
|
||||||
await config.createTable()
|
await config.createTable()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
// app 2
|
// app 2
|
||||||
await config.createApp("second-app")
|
const app2 = await config.createApp("second-app")
|
||||||
await config.createTable()
|
await config.createTable()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
|
@ -36,6 +38,12 @@ describe("syncRows", () => {
|
||||||
// assert the migration worked
|
// assert the migration worked
|
||||||
usageDoc = await quotas.getQuotaUsage()
|
usageDoc = await quotas.getQuotaUsage()
|
||||||
expect(usageDoc.usageQuota.rows).toEqual(3)
|
expect(usageDoc.usageQuota.rows).toEqual(3)
|
||||||
|
expect(usageDoc.apps?.[getProdAppID(app1.appId)].usageQuota.rows).toEqual(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
expect(usageDoc.apps?.[getProdAppID(app2.appId)].usageQuota.rows).toEqual(
|
||||||
|
2
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,11 +4,9 @@ import env from "../environment"
|
||||||
|
|
||||||
// migration functions
|
// migration functions
|
||||||
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
||||||
import * as quota1 from "./functions/quotas1"
|
import * as syncQuotas from "./functions/syncQuotas"
|
||||||
import * as appUrls from "./functions/appUrls"
|
import * as appUrls from "./functions/appUrls"
|
||||||
import * as backfill from "./functions/backfill"
|
import * as backfill from "./functions/backfill"
|
||||||
import * as pluginCount from "./functions/pluginCount"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate the migration function and additional configuration from
|
* Populate the migration function and additional configuration from
|
||||||
* the static migration definitions.
|
* the static migration definitions.
|
||||||
|
@ -26,10 +24,10 @@ export const buildMigrations = () => {
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case MigrationName.QUOTAS_1: {
|
case MigrationName.SYNC_QUOTAS: {
|
||||||
serverMigrations.push({
|
serverMigrations.push({
|
||||||
...definition,
|
...definition,
|
||||||
fn: quota1.run,
|
fn: syncQuotas.run,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -69,16 +67,6 @@ export const buildMigrations = () => {
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case MigrationName.PLUGIN_COUNT: {
|
|
||||||
if (env.SELF_HOSTED) {
|
|
||||||
serverMigrations.push({
|
|
||||||
...definition,
|
|
||||||
fn: pluginCount.run,
|
|
||||||
silent: !!env.SELF_HOSTED,
|
|
||||||
preventRetry: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
tenancy,
|
tenancy,
|
||||||
DocumentType,
|
DocumentType,
|
||||||
context,
|
context,
|
||||||
db,
|
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import TestConfig from "../../tests/utilities/TestConfiguration"
|
import TestConfig from "../../tests/utilities/TestConfiguration"
|
||||||
import structures from "../../tests/utilities/structures"
|
import structures from "../../tests/utilities/structures"
|
||||||
|
|
|
@ -2,6 +2,7 @@ const { getRowParams, USER_METDATA_PREFIX } = require("../../db/utils")
|
||||||
const {
|
const {
|
||||||
isDevAppID,
|
isDevAppID,
|
||||||
getDevelopmentAppID,
|
getDevelopmentAppID,
|
||||||
|
getProdAppID,
|
||||||
doWithDB,
|
doWithDB,
|
||||||
} = require("@budibase/backend-core/db")
|
} = require("@budibase/backend-core/db")
|
||||||
|
|
||||||
|
@ -52,7 +53,8 @@ const getAppRows = async appId => {
|
||||||
* Rows duplicates may exist across apps due to data import so they are not filtered out.
|
* Rows duplicates may exist across apps due to data import so they are not filtered out.
|
||||||
*/
|
*/
|
||||||
exports.getUniqueRows = async appIds => {
|
exports.getUniqueRows = async appIds => {
|
||||||
let uniqueRows = []
|
let uniqueRows = [],
|
||||||
|
rowsByApp = {}
|
||||||
const pairs = getAppPairs(appIds)
|
const pairs = getAppPairs(appIds)
|
||||||
|
|
||||||
for (let pair of Object.values(pairs)) {
|
for (let pair of Object.values(pairs)) {
|
||||||
|
@ -73,8 +75,10 @@ exports.getUniqueRows = async appIds => {
|
||||||
// this can't be done on all rows because app import results in
|
// this can't be done on all rows because app import results in
|
||||||
// duplicate row ids across apps
|
// duplicate row ids across apps
|
||||||
// the array pre-concat is important to avoid stack overflow
|
// the array pre-concat is important to avoid stack overflow
|
||||||
uniqueRows = uniqueRows.concat([...new Set(appRows)])
|
const prodId = getProdAppID(pair.devId || pair.prodId)
|
||||||
|
rowsByApp[prodId] = [...new Set(appRows)]
|
||||||
|
uniqueRows = uniqueRows.concat(rowsByApp[prodId])
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniqueRows
|
return { rows: uniqueRows, appRows: rowsByApp }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1094,12 +1094,12 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@1.4.18-alpha.1":
|
"@budibase/backend-core@2.0.14-alpha.0":
|
||||||
version "1.4.18-alpha.1"
|
version "2.0.14-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.4.18-alpha.1.tgz#4222d3927d2a37bf7e505e17533c15fb0f134525"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.14-alpha.0.tgz#e4115967c9e37147216376bbabd622a9a13403d4"
|
||||||
integrity sha512-lYySQUPt8wuIXh3LRiUTlr81KHFLiJWa0piP1UDa5viqzxPRZ/UhvsrPFw+avJ7dZl2FR314PN2enO3q9/Az/A==
|
integrity sha512-igWtifz/AFZx3kbQi7yO+dRDQX7mbY/ZCA2aRhmDIlhm3zky94XgFyG/7iPFmxh9jK+gpg1Sg3axY7vNDSX6+Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/types" "1.4.18-alpha.1"
|
"@budibase/types" "2.0.14-alpha.0"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@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"
|
||||||
|
@ -1180,13 +1180,13 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/pro@1.4.18-alpha.1":
|
"@budibase/pro@2.0.14-alpha.0":
|
||||||
version "1.4.18-alpha.1"
|
version "2.0.14-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.4.18-alpha.1.tgz#a00471e34d352f57e5bf56f2ae596ff6c1311021"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.14-alpha.0.tgz#e6cb571a0a757871e9ab65555470e2c9f4fc4403"
|
||||||
integrity sha512-UkE1hgMTzWOBa67czStcGyegZAlmf/TgyUjNB8OxeyzQ7jCjUHdVAijnMUMVCfAnq0gdewg+vAu2UFck0dNDGw==
|
integrity sha512-Qh0U89AfnIpBA9fE4xH8hXYp4HexYSoc6WDjlVuNI46IvGRlHaeBAsRkI8XYG8mx830fQqVeEIY1WuRUso7bOg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "1.4.18-alpha.1"
|
"@budibase/backend-core" "2.0.14-alpha.0"
|
||||||
"@budibase/types" "1.4.18-alpha.1"
|
"@budibase/types" "2.0.14-alpha.0"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
@ -1209,10 +1209,10 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/types@1.4.18-alpha.1":
|
"@budibase/types@2.0.14-alpha.0":
|
||||||
version "1.4.18-alpha.1"
|
version "2.0.14-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.4.18-alpha.1.tgz#aad43686afe861838f89e69ecf16ccee9e45f462"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.14-alpha.0.tgz#419ceefde9698b1918c1b41f90fc3010927acde7"
|
||||||
integrity sha512-5fWImsg5cZomUSNs+DK0ZWH5pd4pzHk6x0mgHBcaL+CpzO+ROs/W1JWSVJ6uWYUi9ohy6G1FKB0+4/F5yUaOcw==
|
integrity sha512-20+VfYR9oIui3PDExL+3Ld0XWkrbD74CfWHS8+dYiRmW/PqUkhAT0suwpNui5OsVUn1I+9Jw0wvbitpgT5u2VQ==
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
|
@ -14362,10 +14362,10 @@ verror@1.10.0:
|
||||||
core-util-is "1.0.2"
|
core-util-is "1.0.2"
|
||||||
extsprintf "^1.2.0"
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
vm2@3.9.6:
|
vm2@3.9.11:
|
||||||
version "3.9.6"
|
version "3.9.11"
|
||||||
resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.6.tgz#2f9b2fd0d82802dcd872e1011869ba8ae6b74778"
|
resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.11.tgz#a880f510a606481719ec3f9803b940c5805a06fe"
|
||||||
integrity sha512-BF7euUjgO+ezsz2UKex9kO9M/PtDNOf+KEpiqNepZsgf1MT7JYfJEIvG8BoYhZMLAVjqevFJ0UmXNuETe8m5dQ==
|
integrity sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^8.7.0"
|
acorn "^8.7.0"
|
||||||
acorn-walk "^8.2.0"
|
acorn-walk "^8.2.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -4413,9 +4413,9 @@ vlq@^0.2.2:
|
||||||
integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
|
integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
|
||||||
|
|
||||||
vm2@^3.9.4:
|
vm2@^3.9.4:
|
||||||
version "3.9.6"
|
version "3.9.11"
|
||||||
resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.6.tgz#2f9b2fd0d82802dcd872e1011869ba8ae6b74778"
|
resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.11.tgz#a880f510a606481719ec3f9803b940c5805a06fe"
|
||||||
integrity sha512-BF7euUjgO+ezsz2UKex9kO9M/PtDNOf+KEpiqNepZsgf1MT7JYfJEIvG8BoYhZMLAVjqevFJ0UmXNuETe8m5dQ==
|
integrity sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^8.7.0"
|
acorn "^8.7.0"
|
||||||
acorn-walk "^8.2.0"
|
acorn-walk "^8.2.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/types",
|
"name": "@budibase/types",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
@ -21,6 +21,7 @@ export interface Plugin extends Document {
|
||||||
name: string
|
name: string
|
||||||
version: string
|
version: string
|
||||||
jsUrl?: string
|
jsUrl?: string
|
||||||
|
iconUrl?: string
|
||||||
source: PluginSource
|
source: PluginSource
|
||||||
package: { [key: string]: any }
|
package: { [key: string]: any }
|
||||||
hash: string
|
hash: string
|
||||||
|
|
|
@ -1,15 +1,58 @@
|
||||||
import { MonthlyQuotaName, StaticQuotaName } from "../../sdk"
|
import { MonthlyQuotaName, StaticQuotaName } from "../../sdk"
|
||||||
|
|
||||||
export interface QuotaUsage {
|
export enum BreakdownQuotaName {
|
||||||
_id: string
|
ROW_QUERIES = "rowQueries",
|
||||||
_rev?: string
|
DATASOURCE_QUERIES = "datasourceQueries",
|
||||||
quotaReset: string
|
AUTOMATIONS = "automations",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const APP_QUOTA_NAMES = [
|
||||||
|
StaticQuotaName.ROWS,
|
||||||
|
MonthlyQuotaName.QUERIES,
|
||||||
|
MonthlyQuotaName.AUTOMATIONS,
|
||||||
|
]
|
||||||
|
|
||||||
|
export const BREAKDOWN_QUOTA_NAMES = [
|
||||||
|
MonthlyQuotaName.QUERIES,
|
||||||
|
MonthlyQuotaName.AUTOMATIONS,
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface UsageBreakdown {
|
||||||
|
parent: MonthlyQuotaName
|
||||||
|
values: {
|
||||||
|
[key: string]: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonthlyUsage = {
|
||||||
|
[MonthlyQuotaName.QUERIES]: number
|
||||||
|
[MonthlyQuotaName.AUTOMATIONS]: number
|
||||||
|
[MonthlyQuotaName.DAY_PASSES]: number
|
||||||
|
breakdown?: {
|
||||||
|
[key in BreakdownQuotaName]?: UsageBreakdown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseQuotaUsage {
|
||||||
usageQuota: {
|
usageQuota: {
|
||||||
[key in StaticQuotaName]: number
|
[key in StaticQuotaName]: number
|
||||||
}
|
}
|
||||||
monthly: {
|
monthly: {
|
||||||
[key: string]: {
|
[key: string]: MonthlyUsage
|
||||||
[key in MonthlyQuotaName]: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface QuotaUsage extends BaseQuotaUsage {
|
||||||
|
_id: string
|
||||||
|
_rev?: string
|
||||||
|
quotaReset: string
|
||||||
|
apps?: {
|
||||||
|
[key: string]: BaseQuotaUsage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UsageValues = {
|
||||||
|
total: number
|
||||||
|
app?: number
|
||||||
|
breakdown?: number
|
||||||
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ export interface Integration {
|
||||||
description: string
|
description: string
|
||||||
friendlyName: string
|
friendlyName: string
|
||||||
type?: string
|
type?: string
|
||||||
|
iconUrl?: string
|
||||||
datasource: {}
|
datasource: {}
|
||||||
query: {
|
query: {
|
||||||
[key: string]: QueryDefinition
|
[key: string]: QueryDefinition
|
||||||
|
|
|
@ -27,6 +27,7 @@ export enum ConstantQuotaName {
|
||||||
AUTOMATION_LOG_RETENTION_DAYS = "automationLogRetentionDays",
|
AUTOMATION_LOG_RETENTION_DAYS = "automationLogRetentionDays",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MeteredQuotaName = StaticQuotaName | MonthlyQuotaName
|
||||||
export type QuotaName = StaticQuotaName | MonthlyQuotaName | ConstantQuotaName
|
export type QuotaName = StaticQuotaName | MonthlyQuotaName | ConstantQuotaName
|
||||||
|
|
||||||
export const isStaticQuota = (
|
export const isStaticQuota = (
|
||||||
|
|
|
@ -39,14 +39,13 @@ export interface MigrationOptions {
|
||||||
|
|
||||||
export enum MigrationName {
|
export enum MigrationName {
|
||||||
USER_EMAIL_VIEW_CASING = "user_email_view_casing",
|
USER_EMAIL_VIEW_CASING = "user_email_view_casing",
|
||||||
QUOTAS_1 = "quotas_1",
|
|
||||||
APP_URLS = "app_urls",
|
APP_URLS = "app_urls",
|
||||||
EVENT_APP_BACKFILL = "event_app_backfill",
|
EVENT_APP_BACKFILL = "event_app_backfill",
|
||||||
EVENT_GLOBAL_BACKFILL = "event_global_backfill",
|
EVENT_GLOBAL_BACKFILL = "event_global_backfill",
|
||||||
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
|
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
|
||||||
GLOBAL_INFO_SYNC_USERS = "global_info_sync_users",
|
GLOBAL_INFO_SYNC_USERS = "global_info_sync_users",
|
||||||
PLATFORM_USERS_EMAIL_CASING = "platform_users_email_casing",
|
// increment this number to re-activate this migration
|
||||||
PLUGIN_COUNT = "plugin_count",
|
SYNC_QUOTAS = "sync_quotas_1",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MigrationDefinition {
|
export interface MigrationDefinition {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.4.18-alpha.1",
|
"version": "2.0.14-alpha.0",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "1.4.18-alpha.1",
|
"@budibase/backend-core": "2.0.14-alpha.0",
|
||||||
"@budibase/pro": "1.4.18-alpha.1",
|
"@budibase/pro": "2.0.14-alpha.0",
|
||||||
"@budibase/string-templates": "1.4.18-alpha.1",
|
"@budibase/string-templates": "2.0.14-alpha.0",
|
||||||
"@budibase/types": "1.4.18-alpha.1",
|
"@budibase/types": "2.0.14-alpha.0",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
"@sentry/node": "6.17.7",
|
"@sentry/node": "6.17.7",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
|
|
|
@ -2,6 +2,7 @@ const { getAllRoles } = require("@budibase/backend-core/roles")
|
||||||
const {
|
const {
|
||||||
getAllApps,
|
getAllApps,
|
||||||
getProdAppID,
|
getProdAppID,
|
||||||
|
getDevAppID,
|
||||||
DocumentType,
|
DocumentType,
|
||||||
} = require("@budibase/backend-core/db")
|
} = require("@budibase/backend-core/db")
|
||||||
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
@ -34,7 +35,7 @@ exports.fetch = async ctx => {
|
||||||
|
|
||||||
exports.find = async ctx => {
|
exports.find = async ctx => {
|
||||||
const appId = ctx.params.appId
|
const appId = ctx.params.appId
|
||||||
await doInAppContext(appId, async () => {
|
await doInAppContext(getDevAppID(appId), async () => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const app = await db.get(DocumentType.APP_METADATA)
|
const app = await db.get(DocumentType.APP_METADATA)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
|
|
@ -291,12 +291,12 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@1.4.18-alpha.1":
|
"@budibase/backend-core@2.0.14-alpha.0":
|
||||||
version "1.4.18-alpha.1"
|
version "2.0.14-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.4.18-alpha.1.tgz#4222d3927d2a37bf7e505e17533c15fb0f134525"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.14-alpha.0.tgz#e4115967c9e37147216376bbabd622a9a13403d4"
|
||||||
integrity sha512-lYySQUPt8wuIXh3LRiUTlr81KHFLiJWa0piP1UDa5viqzxPRZ/UhvsrPFw+avJ7dZl2FR314PN2enO3q9/Az/A==
|
integrity sha512-igWtifz/AFZx3kbQi7yO+dRDQX7mbY/ZCA2aRhmDIlhm3zky94XgFyG/7iPFmxh9jK+gpg1Sg3axY7vNDSX6+Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/types" "1.4.18-alpha.1"
|
"@budibase/types" "2.0.14-alpha.0"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@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"
|
||||||
|
@ -327,21 +327,21 @@
|
||||||
uuid "8.3.2"
|
uuid "8.3.2"
|
||||||
zlib "1.0.5"
|
zlib "1.0.5"
|
||||||
|
|
||||||
"@budibase/pro@1.4.18-alpha.1":
|
"@budibase/pro@2.0.14-alpha.0":
|
||||||
version "1.4.18-alpha.1"
|
version "2.0.14-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.4.18-alpha.1.tgz#a00471e34d352f57e5bf56f2ae596ff6c1311021"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.14-alpha.0.tgz#e6cb571a0a757871e9ab65555470e2c9f4fc4403"
|
||||||
integrity sha512-UkE1hgMTzWOBa67czStcGyegZAlmf/TgyUjNB8OxeyzQ7jCjUHdVAijnMUMVCfAnq0gdewg+vAu2UFck0dNDGw==
|
integrity sha512-Qh0U89AfnIpBA9fE4xH8hXYp4HexYSoc6WDjlVuNI46IvGRlHaeBAsRkI8XYG8mx830fQqVeEIY1WuRUso7bOg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "1.4.18-alpha.1"
|
"@budibase/backend-core" "2.0.14-alpha.0"
|
||||||
"@budibase/types" "1.4.18-alpha.1"
|
"@budibase/types" "2.0.14-alpha.0"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
|
||||||
"@budibase/types@1.4.18-alpha.1":
|
"@budibase/types@2.0.14-alpha.0":
|
||||||
version "1.4.18-alpha.1"
|
version "2.0.14-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.4.18-alpha.1.tgz#aad43686afe861838f89e69ecf16ccee9e45f462"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.14-alpha.0.tgz#419ceefde9698b1918c1b41f90fc3010927acde7"
|
||||||
integrity sha512-5fWImsg5cZomUSNs+DK0ZWH5pd4pzHk6x0mgHBcaL+CpzO+ROs/W1JWSVJ6uWYUi9ohy6G1FKB0+4/F5yUaOcw==
|
integrity sha512-20+VfYR9oIui3PDExL+3Ld0XWkrbD74CfWHS8+dYiRmW/PqUkhAT0suwpNui5OsVUn1I+9Jw0wvbitpgT5u2VQ==
|
||||||
|
|
||||||
"@cspotcode/source-map-consumer@0.8.0":
|
"@cspotcode/source-map-consumer@0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue