1
0
Fork 0
mirror of synced 2024-09-11 06:56:23 +12:00

Merge branch 'master' of github.com:budibase/budibase into budi-7754-make-our-helm-chart-work-out-of-the-box

This commit is contained in:
Sam Rose 2023-11-22 14:45:54 +00:00
commit 2fb9748979
No known key found for this signature in database
23 changed files with 357 additions and 270 deletions

View file

@ -1,24 +0,0 @@
#!/bin/bash
echo ${TARGETBUILD} > /buildtarget.txt
if [[ "${TARGETBUILD}" = "aas" ]]; then
# Azure AppService uses /home for persistent data & SSH on port 2222
DATA_DIR="${DATA_DIR:-/home}"
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
mkdir -p $DATA_DIR/{search,minio,couch}
mkdir -p $DATA_DIR/couch/{dbs,views}
chown -R couchdb:couchdb $DATA_DIR/couch/
apt update
apt-get install -y openssh-server
echo "root:Docker!" | chpasswd
mkdir -p /tmp
chmod +x /tmp/ssh_setup.sh \
&& (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)
cp /etc/sshd_config /etc/ssh/sshd_config
/etc/init.d/ssh restart
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
else
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
fi

View file

@ -25,7 +25,7 @@ if [[ $(curl -s -w "%{http_code}\n" http://localhost:4002/health -o /dev/null) -
healthy=false
fi
if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/ -o /dev/null) -ne 200 ]]; then
if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/_up -o /dev/null) -ne 200 ]]; then
echo 'ERROR: CouchDB is not running';
healthy=false
fi

View file

@ -2,7 +2,7 @@ import env from "../environment"
import * as eventHelpers from "./events"
import * as accountSdk from "../accounts"
import * as cache from "../cache"
import { getGlobalDB, getIdentity, getTenantId } from "../context"
import { doInTenant, getGlobalDB, getIdentity, getTenantId } from "../context"
import * as dbUtils from "../db"
import { EmailUnavailableError, HTTPError } from "../errors"
import * as platform from "../platform"
@ -10,12 +10,10 @@ import * as sessions from "../security/sessions"
import * as usersCore from "./users"
import {
Account,
AllDocsResponse,
BulkUserCreated,
BulkUserDeleted,
isSSOAccount,
isSSOUser,
RowResponse,
SaveUserOpts,
User,
UserStatus,
@ -487,6 +485,37 @@ export class UserDB {
await sessions.invalidateSessions(userId, { reason: "deletion" })
}
static async createAdminUser(
email: string,
password: string,
tenantId: string,
opts?: { ssoId?: string; hashPassword?: boolean; requirePassword?: boolean }
) {
const user: User = {
email: email,
password: password,
createdAt: Date.now(),
roles: {},
builder: {
global: true,
},
admin: {
global: true,
},
tenantId,
}
if (opts?.ssoId) {
user.ssoId = opts.ssoId
}
// always bust checklist beforehand, if an error occurs but can proceed, don't get
// stuck in a cycle
await cache.bustCache(cache.CacheKey.CHECKLIST)
return await UserDB.save(user, {
hashPassword: opts?.hashPassword,
requirePassword: opts?.requirePassword,
})
}
static async getGroups(groupIds: string[]) {
return await this.groups.getBulk(groupIds)
}

View file

@ -43,7 +43,7 @@ function removeUserPassword(users: User | User[]) {
return users
}
export const isSupportedUserSearch = (query: SearchQuery) => {
export function isSupportedUserSearch(query: SearchQuery) {
const allowed = [
{ op: SearchQueryOperators.STRING, key: "email" },
{ op: SearchQueryOperators.EQUAL, key: "_id" },
@ -68,10 +68,10 @@ export const isSupportedUserSearch = (query: SearchQuery) => {
return true
}
export const bulkGetGlobalUsersById = async (
export async function bulkGetGlobalUsersById(
userIds: string[],
opts?: GetOpts
) => {
) {
const db = getGlobalDB()
let users = (
await db.allDocs({
@ -85,7 +85,7 @@ export const bulkGetGlobalUsersById = async (
return users
}
export const getAllUserIds = async () => {
export async function getAllUserIds() {
const db = getGlobalDB()
const startKey = `${DocumentType.USER}${SEPARATOR}`
const response = await db.allDocs({
@ -95,7 +95,7 @@ export const getAllUserIds = async () => {
return response.rows.map(row => row.id)
}
export const bulkUpdateGlobalUsers = async (users: User[]) => {
export async function bulkUpdateGlobalUsers(users: User[]) {
const db = getGlobalDB()
return (await db.bulkDocs(users)) as BulkDocsResponse
}
@ -113,10 +113,10 @@ export async function getById(id: string, opts?: GetOpts): Promise<User> {
* Given an email address this will use a view to search through
* all the users to find one with this email address.
*/
export const getGlobalUserByEmail = async (
export async function getGlobalUserByEmail(
email: String,
opts?: GetOpts
): Promise<User | undefined> => {
): Promise<User | undefined> {
if (email == null) {
throw "Must supply an email address to view"
}
@ -139,11 +139,23 @@ export const getGlobalUserByEmail = async (
return user
}
export const searchGlobalUsersByApp = async (
export async function doesUserExist(email: string) {
try {
const user = await getGlobalUserByEmail(email)
if (Array.isArray(user) || user != null) {
return true
}
} catch (err) {
return false
}
return false
}
export async function searchGlobalUsersByApp(
appId: any,
opts: DatabaseQueryOpts,
getOpts?: GetOpts
) => {
) {
if (typeof appId !== "string") {
throw new Error("Must provide a string based app ID")
}
@ -167,10 +179,10 @@ export const searchGlobalUsersByApp = async (
Return any user who potentially has access to the application
Admins, developers and app users with the explicitly role.
*/
export const searchGlobalUsersByAppAccess = async (
export async function searchGlobalUsersByAppAccess(
appId: any,
opts?: { limit?: number }
) => {
) {
const roleSelector = `roles.${appId}`
let orQuery: any[] = [
@ -205,7 +217,7 @@ export const searchGlobalUsersByAppAccess = async (
return resp.rows
}
export const getGlobalUserByAppPage = (appId: string, user: User) => {
export function getGlobalUserByAppPage(appId: string, user: User) {
if (!user) {
return
}
@ -215,11 +227,11 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
/**
* Performs a starts with search on the global email view.
*/
export const searchGlobalUsersByEmail = async (
export async function searchGlobalUsersByEmail(
email: string | unknown,
opts: any,
getOpts?: GetOpts
) => {
) {
if (typeof email !== "string") {
throw new Error("Must provide a string to search by")
}
@ -242,12 +254,12 @@ export const searchGlobalUsersByEmail = async (
}
const PAGE_LIMIT = 8
export const paginatedUsers = async ({
export async function paginatedUsers({
bookmark,
query,
appId,
limit,
}: SearchUsersRequest = {}) => {
}: SearchUsersRequest = {}) {
const db = getGlobalDB()
const pageSize = limit ?? PAGE_LIMIT
const pageLimit = pageSize + 1

View file

@ -228,7 +228,12 @@ export const getContextProviderComponents = (
/**
* Gets all data provider components above a component.
*/
export const getActionProviderComponents = (asset, componentId, actionType) => {
export const getActionProviders = (
asset,
componentId,
actionType,
options = { includeSelf: false }
) => {
if (!asset || !componentId) {
return []
}
@ -236,13 +241,30 @@ export const getActionProviderComponents = (asset, componentId, actionType) => {
// Get the component tree leading up to this component, ignoring the component
// itself
const path = findComponentPath(asset.props, componentId)
path.pop()
if (!options?.includeSelf) {
path.pop()
}
// Filter by only data provider components
return path.filter(component => {
// Find matching contexts and generate bindings
let providers = []
path.forEach(component => {
const def = store.actions.components.getDefinition(component._component)
return def?.actions?.includes(actionType)
const actions = (def?.actions || []).map(action => {
return typeof action === "string" ? { type: action } : action
})
const action = actions.find(x => x.type === actionType)
if (action) {
let runtimeBinding = component._id
if (action.suffix) {
runtimeBinding += `-${action.suffix}`
}
providers.push({
readableBinding: component._instanceName,
runtimeBinding,
})
}
})
return providers
}
/**

View file

@ -1,17 +1,19 @@
<script>
import { Select, Label } from "@budibase/bbui"
import { currentAsset, store } from "builderStore"
import { getActionProviderComponents } from "builderStore/dataBinding"
import { getActionProviders } from "builderStore/dataBinding"
import { onMount } from "svelte"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
export let parameters
export let bindings = []
export let nested
$: actionProviders = getActionProviderComponents(
$: actionProviders = getActionProviders(
$currentAsset,
$store.selectedComponentId,
"ChangeFormStep"
"ChangeFormStep",
{ includeSelf: nested }
)
const typeOptions = [
@ -46,8 +48,8 @@
placeholder={null}
bind:value={parameters.componentId}
options={actionProviders}
getOptionLabel={x => x._instanceName}
getOptionValue={x => x._id}
getOptionLabel={x => x.readableBinding}
getOptionValue={x => x.runtimeBinding}
/>
<Label small>Step</Label>
<Select bind:value={parameters.type} options={typeOptions} />

View file

@ -1,14 +1,16 @@
<script>
import { Select, Label } from "@budibase/bbui"
import { currentAsset, store } from "builderStore"
import { getActionProviderComponents } from "builderStore/dataBinding"
import { getActionProviders } from "builderStore/dataBinding"
export let parameters
export let nested
$: actionProviders = getActionProviderComponents(
$: actionProviders = getActionProviders(
$currentAsset,
$store.selectedComponentId,
"ClearForm"
"ClearForm",
{ includeSelf: nested }
)
</script>
@ -17,8 +19,8 @@
<Select
bind:value={parameters.componentId}
options={actionProviders}
getOptionLabel={x => x._instanceName}
getOptionValue={x => x._id}
getOptionLabel={x => x.readableBinding}
getOptionValue={x => x.runtimeBinding}
/>
</div>

View file

@ -2,27 +2,20 @@
import { Select, Label, Body, Checkbox, Input } from "@budibase/bbui"
import { store, currentAsset } from "builderStore"
import { tables, viewsV2 } from "stores/backend"
import {
getContextProviderComponents,
getSchemaForDatasourcePlus,
} from "builderStore/dataBinding"
import { getSchemaForDatasourcePlus } from "builderStore/dataBinding"
import SaveFields from "./SaveFields.svelte"
import { getDatasourceLikeProviders } from "components/design/settings/controls/ButtonActionEditor/actions/utils"
export let parameters
export let bindings = []
export let nested
$: formComponents = getContextProviderComponents(
$currentAsset,
$store.selectedComponentId,
"form"
)
$: schemaComponents = getContextProviderComponents(
$currentAsset,
$store.selectedComponentId,
"schema"
)
$: providerOptions = getProviderOptions(formComponents, schemaComponents)
$: schemaFields = getSchemaFields($currentAsset, parameters?.tableId)
$: providerOptions = getDatasourceLikeProviders({
asset: $currentAsset,
componentId: $store.selectedComponentId,
nested,
})
$: schemaFields = getSchemaFields(parameters?.tableId)
$: tableOptions = $tables.list.map(table => ({
label: table.name,
resourceId: table._id,
@ -33,44 +26,8 @@
}))
$: options = [...(tableOptions || []), ...(viewOptions || [])]
// Gets a context definition of a certain type from a component definition
const extractComponentContext = (component, contextType) => {
const def = store.actions.components.getDefinition(component?._component)
if (!def) {
return null
}
const contexts = Array.isArray(def.context) ? def.context : [def.context]
return contexts.find(context => context?.type === contextType)
}
// Gets options for valid context keys which provide valid data to submit
const getProviderOptions = (formComponents, schemaComponents) => {
const formContexts = formComponents.map(component => ({
component,
context: extractComponentContext(component, "form"),
}))
const schemaContexts = schemaComponents.map(component => ({
component,
context: extractComponentContext(component, "schema"),
}))
const allContexts = formContexts.concat(schemaContexts)
return allContexts.map(({ component, context }) => {
let runtimeBinding = component._id
if (context.suffix) {
runtimeBinding += `-${context.suffix}`
}
return {
label: component._instanceName,
value: runtimeBinding,
}
})
}
const getSchemaFields = (asset, tableId) => {
const { schema } = getSchemaForDatasourcePlus(tableId)
delete schema._id
delete schema._rev
const getSchemaFields = resourceId => {
const { schema } = getSchemaForDatasourcePlus(resourceId)
return Object.values(schema || {})
}

View file

@ -1,14 +1,16 @@
<script>
import { Select, Label } from "@budibase/bbui"
import { currentAsset, store } from "builderStore"
import { getActionProviderComponents } from "builderStore/dataBinding"
import { getActionProviders } from "builderStore/dataBinding"
export let parameters
export let nested
$: actionProviders = getActionProviderComponents(
$: actionProviders = getActionProviders(
$currentAsset,
$store.selectedComponentId,
"RefreshDatasource"
"RefreshDatasource",
{ includeSelf: nested }
)
</script>
@ -17,8 +19,8 @@
<Select
bind:value={parameters.componentId}
options={actionProviders}
getOptionLabel={x => x._instanceName}
getOptionValue={x => x._id}
getOptionLabel={x => x.readableBinding}
getOptionValue={x => x.runtimeBinding}
/>
</div>

View file

@ -2,29 +2,19 @@
import { Select, Label, Body, Checkbox, Input } from "@budibase/bbui"
import { store, currentAsset } from "builderStore"
import { tables, viewsV2 } from "stores/backend"
import {
getContextProviderComponents,
getSchemaForDatasourcePlus,
} from "builderStore/dataBinding"
import { getSchemaForDatasourcePlus } from "builderStore/dataBinding"
import SaveFields from "./SaveFields.svelte"
import { getDatasourceLikeProviders } from "components/design/settings/controls/ButtonActionEditor/actions/utils"
export let parameters
export let bindings = []
export let nested
$: formComponents = getContextProviderComponents(
$currentAsset,
$store.selectedComponentId,
"form",
{ includeSelf: nested }
)
$: schemaComponents = getContextProviderComponents(
$currentAsset,
$store.selectedComponentId,
"schema",
{ includeSelf: nested }
)
$: providerOptions = getProviderOptions(formComponents, schemaComponents)
$: providerOptions = getDatasourceLikeProviders({
asset: $currentAsset,
componentId: $store.selectedComponentId,
nested,
})
$: schemaFields = getSchemaFields(parameters?.tableId)
$: tableOptions = $tables.list.map(table => ({
label: table.name,
@ -36,40 +26,6 @@
}))
$: options = [...(tableOptions || []), ...(viewOptions || [])]
// Gets a context definition of a certain type from a component definition
const extractComponentContext = (component, contextType) => {
const def = store.actions.components.getDefinition(component?._component)
if (!def) {
return null
}
const contexts = Array.isArray(def.context) ? def.context : [def.context]
return contexts.find(context => context?.type === contextType)
}
// Gets options for valid context keys which provide valid data to submit
const getProviderOptions = (formComponents, schemaComponents) => {
const formContexts = formComponents.map(component => ({
component,
context: extractComponentContext(component, "form"),
}))
const schemaContexts = schemaComponents.map(component => ({
component,
context: extractComponentContext(component, "schema"),
}))
const allContexts = formContexts.concat(schemaContexts)
return allContexts.map(({ component, context }) => {
let runtimeBinding = component._id
if (context.suffix) {
runtimeBinding += `-${context.suffix}`
}
return {
label: component._instanceName,
value: runtimeBinding,
}
})
}
const getSchemaFields = resourceId => {
const { schema } = getSchemaForDatasourcePlus(resourceId)
return Object.values(schema || {})

View file

@ -1,22 +1,36 @@
<script>
import { currentAsset, store } from "builderStore"
import { Label, Combobox, Select } from "@budibase/bbui"
import {
getActionProviderComponents,
buildFormSchema,
} from "builderStore/dataBinding"
import { getActionProviders, buildFormSchema } from "builderStore/dataBinding"
import { findComponent } from "builderStore/componentUtils"
export let parameters
export let nested
$: formComponent = findComponent($currentAsset.props, parameters.componentId)
$: formComponent = getFormComponent(
$currentAsset.props,
parameters.componentId
)
$: formSchema = buildFormSchema(formComponent)
$: fieldOptions = Object.keys(formSchema || {})
$: actionProviders = getActionProviderComponents(
$: actionProviders = getActionProviders(
$currentAsset,
$store.selectedComponentId,
"ScrollTo"
"ScrollTo",
{ includeSelf: nested }
)
const getFormComponent = (asset, id) => {
let component = findComponent(asset, id)
if (component) {
return component
}
// Check for block component IDs, and use the block itself instead
if (id?.includes("-")) {
return findComponent(asset, id.split("-")[0])
}
return null
}
</script>
<div class="root">
@ -24,8 +38,8 @@
<Select
bind:value={parameters.componentId}
options={actionProviders}
getOptionLabel={x => x._instanceName}
getOptionValue={x => x._id}
getOptionLabel={x => x.readableBinding}
getOptionValue={x => x.runtimeBinding}
/>
<Label small>Field</Label>
<Combobox bind:value={parameters.field} options={fieldOptions} />

View file

@ -3,14 +3,12 @@
import { onMount } from "svelte"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { currentAsset, store } from "builderStore"
import {
getActionProviderComponents,
buildFormSchema,
} from "builderStore/dataBinding"
import { getActionProviders, buildFormSchema } from "builderStore/dataBinding"
import { findComponent } from "builderStore/componentUtils"
export let parameters
export let bindings = []
export let nested
const typeOptions = [
{
@ -23,15 +21,31 @@
},
]
$: formComponent = findComponent($currentAsset.props, parameters.componentId)
$: formComponent = getFormComponent(
$currentAsset.props,
parameters.componentId
)
$: formSchema = buildFormSchema(formComponent)
$: fieldOptions = Object.keys(formSchema || {})
$: actionProviders = getActionProviderComponents(
$: actionProviders = getActionProviders(
$currentAsset,
$store.selectedComponentId,
"ValidateForm"
"ValidateForm",
{ includeSelf: nested }
)
const getFormComponent = (asset, id) => {
let component = findComponent(asset, id)
if (component) {
return component
}
// Check for block component IDs, and use the block itself instead
if (id?.includes("-")) {
return findComponent(asset, id.split("-")[0])
}
return null
}
onMount(() => {
if (!parameters.type) {
parameters.type = "set"
@ -44,8 +58,8 @@
<Select
bind:value={parameters.componentId}
options={actionProviders}
getOptionLabel={x => x._instanceName}
getOptionValue={x => x._id}
getOptionLabel={x => x.readableBinding}
getOptionValue={x => x.runtimeBinding}
/>
<Label small>Type</Label>
<Select

View file

@ -1,14 +1,16 @@
<script>
import { Select, Label } from "@budibase/bbui"
import { currentAsset, store } from "builderStore"
import { getActionProviderComponents } from "builderStore/dataBinding"
import { getActionProviders } from "builderStore/dataBinding"
export let parameters
export let nested
$: actionProviders = getActionProviderComponents(
$: actionProviders = getActionProviders(
$currentAsset,
$store.selectedComponentId,
"ValidateForm"
"ValidateForm",
{ includeSelf: nested }
)
</script>
@ -17,8 +19,8 @@
<Select
bind:value={parameters.componentId}
options={actionProviders}
getOptionLabel={x => x._instanceName}
getOptionValue={x => x._id}
getOptionLabel={x => x.readableBinding}
getOptionValue={x => x.runtimeBinding}
/>
<div />
</div>

View file

@ -0,0 +1,82 @@
import { getContextProviderComponents } from "builderStore/dataBinding"
import { store } from "builderStore"
import { capitalise } from "helpers"
// Generates bindings for all components that provider "datasource like"
// contexts. This includes "form" contexts and "schema" contexts. This is used
// by various button actions as candidates for whole "row" objects.
// Some examples are saving rows or duplicating rows.
export const getDatasourceLikeProviders = ({ asset, componentId, nested }) => {
// Get all form context providers
const formComponents = getContextProviderComponents(
asset,
componentId,
"form",
{ includeSelf: nested }
)
// Get all schema context providers
const schemaComponents = getContextProviderComponents(
asset,
componentId,
"schema",
{ includeSelf: nested }
)
// Generate contexts for all form providers
const formContexts = formComponents.map(component => ({
component,
context: extractComponentContext(component, "form"),
}))
// Generate contexts for all schema providers
const schemaContexts = schemaComponents.map(component => ({
component,
context: extractComponentContext(component, "schema"),
}))
// Check for duplicate contexts by the same component. In this case, attempt
// to label contexts with their suffixes
schemaContexts.forEach(schemaContext => {
// Check if we have a form context for this component
const id = schemaContext.component._id
const existing = formContexts.find(x => x.component._id === id)
if (existing) {
if (existing.context.suffix) {
const suffix = capitalise(existing.context.suffix)
existing.readableSuffix = ` - ${suffix}`
}
if (schemaContext.context.suffix) {
const suffix = capitalise(schemaContext.context.suffix)
schemaContext.readableSuffix = ` - ${suffix}`
}
}
})
// Generate bindings for all contexts
const allContexts = formContexts.concat(schemaContexts)
return allContexts.map(({ component, context, readableSuffix }) => {
let readableBinding = component._instanceName
let runtimeBinding = component._id
if (context.suffix) {
runtimeBinding += `-${context.suffix}`
}
if (readableSuffix) {
readableBinding += readableSuffix
}
return {
label: readableBinding,
value: runtimeBinding,
}
})
}
// Gets a context definition of a certain type from a component definition
const extractComponentContext = (component, contextType) => {
const def = store.actions.components.getDefinition(component?._component)
if (!def) {
return null
}
const contexts = Array.isArray(def.context) ? def.context : [def.context]
return contexts.find(context => context?.type === contextType)
}

View file

@ -6165,6 +6165,24 @@
"defaultValue": "spectrum--medium"
}
],
"actions": [
{
"type": "ValidateForm",
"suffix": "form"
},
{
"type": "ClearForm",
"suffix": "form"
},
{
"type": "UpdateFieldValue",
"suffix": "form"
},
{
"type": "ScrollTo",
"suffix": "form"
}
],
"context": [
{
"type": "form",
@ -6420,7 +6438,8 @@
],
"context": {
"type": "schema"
}
},
"actions": ["RefreshDatasource"]
},
"bbreferencefield": {
"devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels",

View file

@ -3,9 +3,9 @@
import Block from "../Block.svelte"
export let buttons = []
export let direction
export let hAlign
export let vAlign
export let direction = "row"
export let hAlign = "left"
export let vAlign = "top"
export let gap = "S"
</script>

View file

@ -27,8 +27,12 @@
builderStore,
notificationStore,
enrichButtonActions,
ActionTypes,
createContextStore,
} = getContext("sdk")
let grid
$: columnWhitelist = columns?.map(col => col.name)
$: schemaOverrides = getSchemaOverrides(columns)
$: enrichedButtons = enrichButtons(buttons)
@ -53,11 +57,16 @@
text: settings.text,
type: settings.type,
onClick: async row => {
// We add a fake context binding in here, which allows us to pretend
// that the grid provides a "schema" binding - that lets us use the
// clicked row in things like save row actions
const enrichedContext = { ...get(context), [get(component).id]: row }
const fn = enrichButtonActions(settings.onClick, enrichedContext)
// Create a fake, ephemeral context to run the buttons actions with
const id = get(component).id
const gridContext = createContextStore(context)
gridContext.actions.provideData(id, row)
gridContext.actions.provideAction(
id,
ActionTypes.RefreshDatasource,
() => grid?.getContext()?.rows.actions.refreshData()
)
const fn = enrichButtonActions(settings.onClick, get(gridContext))
return await fn?.({ row })
},
}))
@ -69,6 +78,7 @@
class:in-builder={$builderStore.inBuilder}
>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}

View file

@ -45,12 +45,13 @@
// Register any "refresh datasource" actions with a singleton store
// so we can easily refresh data at all levels for any datasource
if (type === ActionTypes.RefreshDatasource) {
const { dataSource } = metadata || {}
dataSourceStore.actions.registerDataSource(
dataSource,
instanceId,
callback
)
if (metadata?.dataSource) {
dataSourceStore.actions.registerDataSource(
metadata.dataSource,
instanceId,
callback
)
}
}
})
}

View file

@ -15,6 +15,7 @@ import {
confirmationStore,
roleStore,
stateStore,
createContextStore,
} from "stores"
import { styleable } from "utils/styleable"
import { linkable } from "utils/linkable"
@ -57,6 +58,7 @@ export default {
enrichButtonActions,
processStringSync,
makePropSafe,
createContextStore,
// Components
Provider,

View file

@ -33,6 +33,7 @@ const environment = {
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
REDIS_CLUSTERED: process.env.REDIS_CLUSTERED,
HTTP_MIGRATIONS: process.env.HTTP_MIGRATIONS,
CLUSTER_MODE: process.env.CLUSTER_MODE,
API_REQ_LIMIT_PER_SEC: process.env.API_REQ_LIMIT_PER_SEC,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,

View file

@ -1,11 +1,13 @@
import env from "./environment"
import * as redis from "./utilities/redis"
import { generateApiKey, getChecklist } from "./utilities/workerRequests"
import {
createAdminUser,
generateApiKey,
getChecklist,
} from "./utilities/workerRequests"
import { events, installation, logging, tenancy } from "@budibase/backend-core"
events,
installation,
logging,
tenancy,
users,
} from "@budibase/backend-core"
import fs from "fs"
import { watch } from "./watch"
import * as automations from "./automations"
@ -58,7 +60,7 @@ export async function startup(app?: any, server?: any) {
return
}
STARTUP_RAN = true
if (server) {
if (server && !env.CLUSTER_MODE) {
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
env._set("PORT", server.address().port)
}
@ -110,34 +112,37 @@ export async function startup(app?: any, server?: any) {
// check and create admin user if required
// this must be run after the api has been initialised due to
// the app user sync
const bbAdminEmail = env.BB_ADMIN_USER_EMAIL,
bbAdminPassword = env.BB_ADMIN_USER_PASSWORD
if (
env.SELF_HOSTED &&
!env.MULTI_TENANCY &&
env.BB_ADMIN_USER_EMAIL &&
env.BB_ADMIN_USER_PASSWORD
bbAdminEmail &&
bbAdminPassword
) {
const checklist = await getChecklist()
if (!checklist?.adminUser?.checked) {
try {
const tenantId = tenancy.getTenantId()
const user = await createAdminUser(
env.BB_ADMIN_USER_EMAIL,
env.BB_ADMIN_USER_PASSWORD,
tenantId
)
// Need to set up an API key for automated integration tests
if (env.isTest()) {
await generateApiKey(user._id)
}
const tenantId = tenancy.getTenantId()
await tenancy.doInTenant(tenantId, async () => {
const exists = await users.doesUserExist(bbAdminEmail)
const checklist = await getChecklist()
if (!checklist?.adminUser?.checked || !exists) {
try {
const user = await users.UserDB.createAdminUser(
bbAdminEmail,
bbAdminPassword,
tenantId,
{ hashPassword: true, requirePassword: true }
)
// Need to set up an API key for automated integration tests
if (env.isTest()) {
await generateApiKey(user._id!)
}
console.log(
"Admin account automatically created for",
env.BB_ADMIN_USER_EMAIL
)
} catch (e) {
logging.logAlert("Error creating initial admin user. Exiting.", e)
shutdown(server)
console.log("Admin account automatically created for", bbAdminEmail)
} catch (e) {
logging.logAlert("Error creating initial admin user. Exiting.", e)
shutdown(server)
}
}
}
})
}
}

View file

@ -155,19 +155,9 @@ export async function readGlobalUser(ctx: Ctx): Promise<User> {
return checkResponse(response, "get user", { ctx })
}
export async function createAdminUser(
email: string,
password: string,
tenantId: string
) {
const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + "/api/global/users/init"),
request(undefined, { method: "POST", body: { email, password, tenantId } })
)
return checkResponse(response, "create admin user")
}
export async function getChecklist() {
export async function getChecklist(): Promise<{
adminUser: { checked: boolean }
}> {
const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + "/api/global/configs/checklist"),
request(undefined, { method: "GET" })

View file

@ -120,28 +120,17 @@ export const adminUser = async (
)
}
const user: User = {
email: email,
password: password,
createdAt: Date.now(),
roles: {},
builder: {
global: true,
},
admin: {
global: true,
},
tenantId,
ssoId,
}
try {
// always bust checklist beforehand, if an error occurs but can proceed, don't get
// stuck in a cycle
await cache.bustCache(cache.CacheKey.CHECKLIST)
const finalUser = await userSdk.db.save(user, {
hashPassword,
requirePassword,
})
const finalUser = await userSdk.db.createAdminUser(
email,
password,
tenantId,
{
ssoId,
hashPassword,
requirePassword,
}
)
// events
let account: CloudAccount | undefined