Merge branch 'master' of github.com:budibase/budibase into dd-trace-5.43.0

This commit is contained in:
Sam Rose 2025-03-25 14:36:48 +00:00
commit c769d5860c
No known key found for this signature in database
39 changed files with 582 additions and 302 deletions

View File

@ -27,5 +27,6 @@
},
"[handlebars]": {
"editor.formatOnSave": false
}
},
"eslint.validate": ["javascript", "typescript", "svelte"]
}

View File

@ -10,7 +10,7 @@
},
"dependencies": {
"bulma": "^0.9.3",
"next": "14.2.21",
"next": "14.2.25",
"node-fetch": "^3.2.10",
"sass": "^1.52.3",
"react": "17.0.2",

View File

@ -46,10 +46,10 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@next/env@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.21.tgz#09ff0813d29c596397e141205d4f5fd5c236bdd0"
integrity sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==
"@next/env@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.25.tgz#936d10b967e103e49a4bcea1e97292d5605278dd"
integrity sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w==
"@next/eslint-plugin-next@12.1.0":
version "12.1.0"
@ -58,50 +58,50 @@
dependencies:
glob "7.1.7"
"@next/swc-darwin-arm64@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.21.tgz#32a31992aace1440981df9cf7cb3af7845d94fec"
integrity sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==
"@next/swc-darwin-arm64@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.25.tgz#7bcccfda0c0ff045c45fbe34c491b7368e373e3d"
integrity sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ==
"@next/swc-darwin-x64@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.21.tgz#5ab4b3f6685b6b52f810d0f5cf6e471480ddffdb"
integrity sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==
"@next/swc-darwin-x64@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.25.tgz#b489e209d7b405260b73f69a38186ed150fb7a08"
integrity sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug==
"@next/swc-linux-arm64-gnu@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.21.tgz#8a0e1fa887aef19ca218af2af515d0a5ee67ba3f"
integrity sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==
"@next/swc-linux-arm64-gnu@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.25.tgz#ba064fabfdce0190d9859493d8232fffa84ef2e2"
integrity sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg==
"@next/swc-linux-arm64-musl@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.21.tgz#ddad844406b42fa8965fe11250abc85c1fe0fd05"
integrity sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==
"@next/swc-linux-arm64-musl@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.25.tgz#bf0018267e4e0fbfa1524750321f8cae855144a3"
integrity sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw==
"@next/swc-linux-x64-gnu@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.21.tgz#db55fd666f9ba27718f65caa54b622a912cdd16b"
integrity sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==
"@next/swc-linux-x64-gnu@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.25.tgz#64f5a6016a7148297ee80542e0fd788418a32472"
integrity sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w==
"@next/swc-linux-x64-musl@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.21.tgz#dddb850353624efcd58c4c4e30ad8a1aab379642"
integrity sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==
"@next/swc-linux-x64-musl@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.25.tgz#58dc636d7c55828478159546f7b95ab1e902301c"
integrity sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w==
"@next/swc-win32-arm64-msvc@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.21.tgz#290012ee57b196d3d2d04853e6bf0179cae9fbaf"
integrity sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==
"@next/swc-win32-arm64-msvc@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.25.tgz#93562d447c799bded1e89c1a62d5195a2a8c6c0d"
integrity sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ==
"@next/swc-win32-ia32-msvc@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.21.tgz#c959135a78cab18cca588d11d1e33bcf199590d4"
integrity sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==
"@next/swc-win32-ia32-msvc@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.25.tgz#ad85a33466be1f41d083211ea21adc0d2c6e6554"
integrity sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw==
"@next/swc-win32-x64-msvc@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.21.tgz#21ff892286555b90538a7d1b505ea21a005d6ead"
integrity sha512-sT6+llIkzpsexGYZq8cjjthRyRGe5cJVhqh12FmlbxHqna6zsDDK8UNaV7g41T6atFHCJUPeLb3uyAwrBwy0NA==
"@next/swc-win32-x64-msvc@14.2.25":
version "14.2.25"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.25.tgz#3969c66609e683ec63a6a9f320a855f7be686a08"
integrity sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@ -1253,12 +1253,12 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
next@14.2.21:
version "14.2.21"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.21.tgz#f6da9e2abba1a0e4ca7a5273825daf06632554ba"
integrity sha512-rZmLwucLHr3/zfDMYbJXbw0ZeoBpirxkXuvsJbk7UPorvPYZhP7vq7aHbKnU7dQNCYIimRrbB2pp3xmf+wsYUg==
next@14.2.25:
version "14.2.25"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.25.tgz#0657551fde6a97f697cf9870e9ccbdaa465c6008"
integrity sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q==
dependencies:
"@next/env" "14.2.21"
"@next/env" "14.2.25"
"@swc/helpers" "0.5.5"
busboy "1.6.0"
caniuse-lite "^1.0.30001579"
@ -1266,15 +1266,15 @@ next@14.2.21:
postcss "8.4.31"
styled-jsx "5.1.1"
optionalDependencies:
"@next/swc-darwin-arm64" "14.2.21"
"@next/swc-darwin-x64" "14.2.21"
"@next/swc-linux-arm64-gnu" "14.2.21"
"@next/swc-linux-arm64-musl" "14.2.21"
"@next/swc-linux-x64-gnu" "14.2.21"
"@next/swc-linux-x64-musl" "14.2.21"
"@next/swc-win32-arm64-msvc" "14.2.21"
"@next/swc-win32-ia32-msvc" "14.2.21"
"@next/swc-win32-x64-msvc" "14.2.21"
"@next/swc-darwin-arm64" "14.2.25"
"@next/swc-darwin-x64" "14.2.25"
"@next/swc-linux-arm64-gnu" "14.2.25"
"@next/swc-linux-arm64-musl" "14.2.25"
"@next/swc-linux-x64-gnu" "14.2.25"
"@next/swc-linux-x64-musl" "14.2.25"
"@next/swc-win32-arm64-msvc" "14.2.25"
"@next/swc-win32-ia32-msvc" "14.2.25"
"@next/swc-win32-x64-msvc" "14.2.25"
node-domexception@^1.0.0:
version "1.0.0"

View File

@ -1,25 +1,26 @@
<script>
<script lang="ts">
import "@spectrum-css/textfield/dist/index-vars.css"
import { createEventDispatcher, onMount } from "svelte"
import clickOutside from "../../Actions/click_outside"
import Divider from "../../Divider/Divider.svelte"
import type { EnvDropdownType } from "../../types"
export let value = null
export let placeholder = null
export let type = "text"
export let disabled = false
export let id = null
export let readonly = false
export let updateOnChange = true
export let align
export let autofocus = false
export let value: string | number | undefined = undefined
export let placeholder: string | undefined = undefined
export let type: EnvDropdownType = "text"
export let disabled: boolean = false
export let id: string | undefined = undefined
export let readonly: boolean = false
export let updateOnChange: boolean = true
export let align: string | undefined = undefined
export let autofocus: boolean = false
export let variables
export let showModal
export let showModal: () => void
export let environmentVariablesEnabled
export let handleUpgradePanel
export let handleUpgradePanel: () => void
const dispatch = createEventDispatcher()
let field
let field: HTMLInputElement
let focus = false
let iconFocused = false
let open = false
@ -30,7 +31,7 @@
// Strips the name out of the value which is {{ env.Variable }} resulting in an array like ["Variable"]
$: hbsValue = String(value)?.match(STRIP_NAME_REGEX) || []
const updateValue = newValue => {
const updateValue = (newValue: any) => {
if (readonly) {
return
}
@ -48,7 +49,7 @@
focus = true
}
const onBlur = event => {
const onBlur = (event: any) => {
if (readonly) {
return
}
@ -56,14 +57,14 @@
updateValue(event.target.value)
}
const onInput = event => {
const onInput = (event: any) => {
if (readonly || !updateOnChange) {
return
}
updateValue(event.target.value)
}
const handleOutsideClick = event => {
const handleOutsideClick = (event: Event) => {
if (open) {
event.stopPropagation()
open = false
@ -73,7 +74,7 @@
}
}
const handleVarSelect = variable => {
const handleVarSelect = (variable: string) => {
open = false
focus = false
iconFocused = false
@ -121,10 +122,10 @@
<input
bind:this={field}
disabled={hbsValue.length || disabled}
disabled={!!hbsValue.length || disabled}
{readonly}
{id}
value={hbsValue.length ? `{{ ${hbsValue[0]} }}` : value}
value={(hbsValue.length ? `{{ ${hbsValue[0]} }}` : value) ?? ""}
placeholder={placeholder || ""}
on:click
on:blur

View File

@ -13,7 +13,7 @@
export let quiet = false
export let align: "left" | "right" | "center" | undefined = undefined
export let autofocus: boolean | null = false
export let autocomplete: boolean | undefined
export let autocomplete: boolean | string | undefined
const dispatch = createEventDispatcher()

View File

@ -1,26 +1,26 @@
<script>
<script lang="ts">
import Field from "./Field.svelte"
import EnvDropdown from "./Core/EnvDropdown.svelte"
import { createEventDispatcher } from "svelte"
import type { EnvDropdownType } from "../types"
export let value = null
export let label = null
export let labelPosition = "above"
export let placeholder = null
export let type = "text"
export let value: string | undefined = undefined
export let label: string | undefined = undefined
export let labelPosition: string = "above"
export let placeholder: string | undefined = undefined
export let type: EnvDropdownType = "text"
export let disabled = false
export let readonly = false
export let error = null
export let error: string | undefined = undefined
export let updateOnChange = true
export let quiet = false
export let autofocus
export let variables
export let showModal
export let helpText = null
export let environmentVariablesEnabled
export let handleUpgradePanel
export let autofocus: boolean = false
export let variables: { name: string }[] = []
export let showModal: () => void
export let helpText: string | undefined = undefined
export let environmentVariablesEnabled: boolean = false
export let handleUpgradePanel: () => void = () => {}
const dispatch = createEventDispatcher()
const onChange = e => {
const onChange = (e: any) => {
value = e.detail
dispatch("change", e.detail)
}
@ -29,13 +29,11 @@
<Field {helpText} {label} {labelPosition} {error}>
<EnvDropdown
{updateOnChange}
{error}
{disabled}
{readonly}
{value}
{placeholder}
{type}
{quiet}
{autofocus}
{variables}
{showModal}

View File

@ -14,7 +14,7 @@
export let updateOnChange = true
export let quiet = false
export let autofocus: boolean | undefined = undefined
export let autocomplete: boolean | undefined = undefined
export let autocomplete: boolean | string | undefined = undefined
export let helpText: string | undefined = undefined
const dispatch = createEventDispatcher()

View File

@ -3,7 +3,7 @@
import Icon from "../Icon/Icon.svelte"
const dispatch = createEventDispatcher()
const actionMenu = getContext("actionMenu") as { hideAll: () => void }
const actionMenu = getContext("actionMenu")
export let icon: string | undefined = undefined
export let disabled: boolean | undefined = undefined

View File

@ -1,7 +1,9 @@
import { ActionMenu } from "./types"
import { ModalContext } from "./types"
declare module "svelte" {
export function getContext(key: "actionMenu"): ActionMenu | undefined
export function getContext(key: "bbui-modal"): ModalContext
}
export const Modal = "bbui-modal"

View File

@ -111,3 +111,5 @@ export { banner, BANNER_TYPES } from "./Stores/banner"
// Helpers
export * as Helpers from "./helpers"
export type * from "./types"

View File

@ -1,3 +1,4 @@
export interface ActionMenu {
hide: () => void
hideAll: () => void
}

View File

@ -0,0 +1 @@
export type EnvDropdownType = "text" | "number" | "password" | "port"

View File

@ -0,0 +1,3 @@
export * from "./actionMenu"
export * from "./envDropdown"
export * from "./modalContext"

View File

@ -0,0 +1,3 @@
export interface ModalContext {
hide: () => void
}

View File

@ -1,4 +1,4 @@
<script>
<script lang="ts">
import ObjectField from "./fields/Object.svelte"
import BooleanField from "./fields/Boolean.svelte"
import LongFormField from "./fields/LongForm.svelte"
@ -6,15 +6,22 @@
import StringField from "./fields/String.svelte"
import SelectField from "./fields/Select.svelte"
export let type
export let value
export let error
export let name
export let config
export let showModal = () => {}
export let placeholder
type InputType =
| "string"
| "boolean"
| "object"
| "longForm"
| "fieldGroup"
| "select"
const selectComponent = type => {
export let type: InputType
export let value: any
export let error: string | null
export let name: string
export let config: any = undefined
export let placeholder: string | undefined = undefined
const selectComponent = (type: InputType) => {
if (type === "object") {
return ObjectField
} else if (type === "boolean") {
@ -40,7 +47,6 @@
{error}
{name}
{config}
{showModal}
{placeholder}
on:blur
on:change

View File

@ -1,33 +1,23 @@
<script>
import { Label, EnvDropdown } from "@budibase/bbui"
import { environment, licensing } from "@/stores/portal"
<script lang="ts">
import { Label } from "@budibase/bbui"
import EnvVariableInput from "@/components/portal/environment/EnvVariableInput.svelte"
export let type
export let name
export let value
export let error
export let placeholder
export let showModal = () => {}
async function handleUpgradePanel() {
await environment.upgradePanelOpened()
$licensing.goToUpgradePage()
}
</script>
<div class="form-row">
<Label>{name}</Label>
<EnvDropdown
<EnvVariableInput
on:change
on:blur
type={type === "port" ? "string" : type}
{value}
{error}
{placeholder}
variables={$environment.variables}
environmentVariablesEnabled={$licensing.environmentVariablesEnabled}
{showModal}
{handleUpgradePanel}
/>
</div>

View File

@ -1,19 +1,10 @@
<script>
import {
keepOpen,
Modal,
notifications,
Body,
Layout,
ModalContent,
} from "@budibase/bbui"
import { keepOpen, Body, Layout, ModalContent } from "@budibase/bbui"
import { processStringSync } from "@budibase/string-templates"
import CreateEditVariableModal from "@/components/portal/environment/CreateEditVariableModal.svelte"
import ConfigInput from "./ConfigInput.svelte"
import { createValidatedConfigStore } from "./stores/validatedConfig"
import { createValidatedNameStore } from "./stores/validatedName"
import { get } from "svelte/store"
import { environment } from "@/stores/portal"
export let integration
export let config
@ -39,24 +30,6 @@
return keepOpen
}
let createVariableModal
let configValueSetterCallback = () => {}
const showModal = setter => {
configValueSetterCallback = setter
createVariableModal.show()
}
async function saveVariable(data) {
try {
await environment.createVariable(data)
configValueSetterCallback(`{{ env.${data.name} }}`)
createVariableModal.hide()
} catch (err) {
notifications.error(`Failed to create variable: ${err.message}`)
}
}
</script>
<ModalContent
@ -79,7 +52,6 @@
value={$nameStore.name}
error={$nameStore.error}
name="Name"
showModal={() => showModal(nameStore.updateValue)}
on:blur={nameStore.markActive}
on:change={e => nameStore.updateValue(e.detail)}
/>
@ -94,15 +66,9 @@
{name}
{config}
{placeholder}
showModal={() =>
showModal(newValue => configStore.updateFieldValue(key, newValue))}
on:blur={() => configStore.markFieldActive(key)}
on:change={e => configStore.updateFieldValue(key, e.detail)}
/>
{/if}
{/each}
</ModalContent>
<Modal bind:this={createVariableModal}>
<CreateEditVariableModal save={saveVariable} />
</Modal>

View File

@ -9,10 +9,10 @@
export let value = ""
export let bindings = []
export let placeholder
export let placeholder = undefined
export let label
export let disabled = false
export let options
export let options = undefined
export let appendBindingsAsOptions = true
export let error

View File

@ -1,7 +1,7 @@
<script>
import { beforeUrlChange, goto, params } from "@roxi/routify"
import { datasources, flags, integrations, queries } from "@/stores/builder"
import { environment } from "@/stores/portal"
import {
Banner,
Body,
@ -407,13 +407,6 @@
notifications.error("Error getting datasources")
}
try {
// load the environment variables
await environment.loadVariables()
} catch (error) {
notifications.error(`Error getting environment variables - ${error}`)
}
datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
const datasourceUrl = datasource?.config.url
const qs = query?.fields.queryString

View File

@ -65,7 +65,7 @@
</script>
<DetailPopover bind:this={popover} {title} align={PopoverAlignment.Right}>
<div slot="anchor" class:display-new={!authConfig}>
<div slot="anchor" class:display-new={!authConfig && oauth2Enabled}>
<ActionButton icon="LockClosed" quiet selected>
{#if !authConfig}
Authentication

View File

@ -1,4 +1,4 @@
<script>
<script lang="ts">
import {
ModalContent,
Button,
@ -14,23 +14,23 @@
const modalContext = getContext(Context.Modal)
export let save
export let row
export let save: any
export let row: { name: string } | undefined = undefined
let deleteDialog
let deleteDialog: ConfirmDialog
let name = row?.name || ""
let productionValue
let developmentValue
let productionValue: string
let developmentValue: string
let useProductionValue = true
const HasSpacesRegex = /[\\"\s]/
const deleteVariable = async name => {
const deleteVariable = async (name: string) => {
try {
await environment.deleteVariable(name)
modalContext.hide()
notifications.success("Environment variable deleted")
} catch (err) {
} catch (err: any) {
notifications.error(err.message)
}
}
@ -43,7 +43,7 @@
development: developmentValue,
})
notifications.success("Environment variable saved")
} catch (err) {
} catch (err: any) {
notifications.error(`Error saving environment variable - ${err.message}`)
}
}
@ -55,10 +55,10 @@
title={!row ? "Add new environment variable" : "Edit environment variable"}
>
<Input
disabled={row}
disabled={!!row}
label="Name"
bind:value={name}
error={HasSpacesRegex.test(name) && "Must not include spaces"}
error={HasSpacesRegex.test(name) ? "Must not include spaces" : undefined}
/>
<div>
<Heading size="XS">Production</Heading>
@ -100,12 +100,12 @@
<ConfirmDialog
bind:this={deleteDialog}
onOk={() => {
deleteVariable(row.name)
deleteVariable(name)
}}
okText="Delete Environment Variable"
title="Confirm Deletion"
>
Are you sure you wish to delete the environment variable
<i>{row.name}?</i>
<i>{name}?</i>
This action cannot be undone.
</ConfirmDialog>

View File

@ -0,0 +1,58 @@
<script lang="ts">
import {
EnvDropdown,
Modal,
notifications,
type EnvDropdownType,
} from "@budibase/bbui"
import { environment, licensing } from "@/stores/portal"
import CreateEditVariableModal from "./CreateEditVariableModal.svelte"
import type { CreateEnvironmentVariableRequest } from "@budibase/types"
import { onMount } from "svelte"
export let label: string = ""
export let type: EnvDropdownType = "text"
export let value: string | undefined = undefined
export let error: string | undefined = undefined
export let placeholder: string | undefined = undefined
let modal: Modal
async function handleUpgradePanel() {
await environment.upgradePanelOpened()
$licensing.goToUpgradePage()
}
async function saveVariable(data: CreateEnvironmentVariableRequest) {
await environment.createVariable(data)
value = `{{ env.${data.name} }}`
modal.hide()
}
onMount(async () => {
try {
// load the environment variables
await environment.loadVariables()
} catch (error) {
notifications.error(`Error getting environment variables - ${error}`)
}
})
</script>
<EnvDropdown
on:change
on:blur
bind:value
{label}
type={type === "port" ? "text" : type}
{error}
{placeholder}
variables={$environment.variables}
environmentVariablesEnabled={$licensing.environmentVariablesEnabled}
showModal={() => modal.show()}
{handleUpgradePanel}
/>
<Modal bind:this={modal}>
<CreateEditVariableModal save={saveVariable} />
</Modal>

View File

@ -1,4 +1,4 @@
<script>
<script lang="ts">
import { onMount } from "svelte"
import {
ModalContent,
@ -6,32 +6,52 @@
Select,
Body,
Input,
EnvDropdown,
Modal,
notifications,
} from "@budibase/bbui"
import { AUTH_TYPE_LABELS, AUTH_TYPES } from "./authTypes"
import { BindableCombobox } from "@/components/common/bindings"
import { getAuthBindings, getEnvironmentBindings } from "@/dataBinding"
import { environment, licensing, auth } from "@/stores/portal"
import CreateEditVariableModal from "@/components/portal/environment/CreateEditVariableModal.svelte"
import { environment, licensing } from "@/stores/portal"
import EnvVariableInput from "@/components/portal/environment/EnvVariableInput.svelte"
interface FormData {
name?: string
type?: string
basic: {
username?: string
password?: string
}
bearer: {
token?: string
}
}
export let configs
export let currentConfig
export let onConfirm
export let onRemove
let form = {
let form: FormData = {
basic: {},
bearer: {},
}
let errors = {
let errors: FormData = {
basic: {},
bearer: {},
}
let blurred = {
let blurred: {
name?: boolean
type?: boolean
basic: {
username?: boolean
password?: boolean
}
bearer: {
token?: boolean
}
} = {
basic: {},
bearer: {},
}
@ -39,19 +59,7 @@
let hasErrors = false
let hasChanged = false
let createVariableModal
let formFieldkey
onMount(async () => {
try {
await environment.loadVariables()
if ($auth.user) {
await licensing.init()
}
} catch (err) {
console.error(err)
}
if (currentConfig) {
deconstructConfig()
}
@ -79,7 +87,7 @@
* map the form into a new config to save by type
*/
const constructConfig = () => {
const newConfig = {
const newConfig: any = {
name: form.name,
type: form.type,
}
@ -123,10 +131,10 @@
errors.name =
// check for duplicate excluding the current config
configs.find(
c => c.name === form.name && c.name !== currentConfig?.name
(c: any) => c.name === form.name && c.name !== currentConfig?.name
) !== undefined
? "Name must be unique"
: null
: undefined
}
// Name required
else {
@ -137,17 +145,17 @@
// TYPE
const typeError = () => {
errors.type = form.type ? null : "Type is required"
errors.type = form.type ? undefined : "Type is required"
return !!errors.type
}
// BASIC AUTH
const basicAuthErrors = () => {
errors.basic.username = form.basic.username
? null
? undefined
: "Username is required"
errors.basic.password = form.basic.password
? null
? undefined
: "Password is required"
return !!(errors.basic.username || errors.basic.password || commonError)
@ -155,7 +163,7 @@
// BEARER TOKEN
const bearerTokenErrors = () => {
errors.bearer.token = form.bearer.token ? null : "Token is required"
errors.bearer.token = form.bearer.token ? undefined : "Token is required"
return !!(errors.bearer.token || commonError)
}
@ -169,16 +177,6 @@
}
}
const save = async data => {
try {
await environment.createVariable(data)
form.basic[formFieldkey] = `{{ env.${data.name} }}`
createVariableModal.hide()
} catch (err) {
notifications.error(`Failed to create variable: ${err.message}`)
}
}
const onFieldChange = () => {
checkErrors()
checkChanged()
@ -188,15 +186,13 @@
onConfirm(constructConfig())
}
async function handleUpgradePanel() {
await environment.upgradePanelOpened()
$licensing.goToUpgradePage()
}
function showModal(key) {
formFieldkey = key
createVariableModal.show()
}
onMount(async () => {
try {
await environment.loadVariables()
} catch (error) {
notifications.error(`Error getting environment variables - ${error}`)
}
})
</script>
<ModalContent
@ -221,7 +217,7 @@
bind:value={form.name}
on:change={onFieldChange}
on:blur={() => (blurred.name = true)}
error={blurred.name ? errors.name : null}
error={blurred.name ? errors.name : undefined}
/>
<Select
label="Type"
@ -229,31 +225,24 @@
on:change={onFieldChange}
options={AUTH_TYPE_LABELS}
on:blur={() => (blurred.type = true)}
error={blurred.type ? errors.type : null}
error={blurred.type ? errors.type : undefined}
/>
{#if form.type === AUTH_TYPES.BASIC}
<EnvDropdown
<EnvVariableInput
label="Username"
bind:value={form.basic.username}
on:change={onFieldChange}
on:blur={() => (blurred.basic.username = true)}
error={blurred.basic.username ? errors.basic.username : null}
showModal={() => showModal("configKey")}
variables={$environment.variables}
environmentVariablesEnabled={$licensing.environmentVariablesEnabled}
{handleUpgradePanel}
error={blurred.basic.username ? errors.basic.username : undefined}
/>
<EnvDropdown
<EnvVariableInput
label="Password"
type="password"
bind:value={form.basic.password}
on:change={onFieldChange}
on:blur={() => (blurred.basic.password = true)}
error={blurred.basic.password ? errors.basic.password : null}
showModal={() => showModal("configKey")}
variables={$environment.variables}
environmentVariablesEnabled={$licensing.environmentVariablesEnabled}
{handleUpgradePanel}
error={blurred.basic.password ? errors.basic.password : undefined}
/>
{/if}
{#if form.type === AUTH_TYPES.BEARER}
@ -274,16 +263,10 @@
blurred.bearer.token = true
onFieldChange()
}}
allowJS={false}
placeholder="Token"
appendBindingsAsOptions={true}
drawerEnabled={false}
error={blurred.bearer.token ? errors.bearer.token : null}
/>
{/if}
</Layout>
</ModalContent>
<Modal bind:this={createVariableModal}>
<CreateEditVariableModal {save} />
</Modal>

View File

@ -1,14 +1,15 @@
<script>
import { Heading, Layout } from "@budibase/bbui"
import { Heading, Layout, notifications } from "@budibase/bbui"
import KeyValueBuilder from "@/components/integration/KeyValueBuilder.svelte"
import ViewDynamicVariables from "./ViewDynamicVariables.svelte"
import { getEnvironmentBindings } from "@/dataBinding"
import { licensing } from "@/stores/portal"
import { environment, licensing } from "@/stores/portal"
import { queries } from "@/stores/builder"
import { cloneDeep } from "lodash/fp"
import SaveDatasourceButton from "../SaveDatasourceButton.svelte"
import Panel from "../Panel.svelte"
import Tooltip from "../Tooltip.svelte"
import { onMount } from "svelte"
export let datasource
@ -27,6 +28,14 @@
updatedDatasource.config.staticVariables = newStaticVariables
}
onMount(async () => {
try {
await environment.loadVariables()
} catch (error) {
notifications.error(`Error getting environment variables - ${error}`)
}
})
</script>
<Panel>

View File

@ -55,6 +55,11 @@
active={$isActive("./oauth2")}
/>
{/if}
<SideNavItem
text="App scripts"
url={$url("./scripts")}
active={$isActive("./scripts")}
/>
<div class="delete-action">
<AbsTooltip
position={TooltipPosition.Bottom}

View File

@ -170,7 +170,7 @@
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading>Automations</Heading>
<Body size="S">See your automation history and edit advanced settings</Body>
<Body>See your automation history and edit advanced settings</Body>
</Layout>
<Divider />
@ -251,7 +251,6 @@
data={runHistory}
{customRenderers}
placeholderText="No history found"
border={false}
/>
<div class="pagination">
<Pagination

View File

@ -11,6 +11,7 @@
Tags,
Tag,
Table,
ButtonGroup,
} from "@budibase/bbui"
import { backups, licensing, auth, admin } from "@/stores/portal"
import { appStore } from "@/stores/builder"
@ -180,25 +181,17 @@
{#if !$auth.accountPortalAccess && $admin.cloud}
<Body>Contact your account holder to upgrade your plan.</Body>
{/if}
<div class="pro-buttons">
{#if $auth.accountPortalAccess}
<Button
primary
disabled={!$auth.accountPortalAccess && $admin.cloud}
on:click={$licensing.goToUpgradePage()}
>
Upgrade
</Button>
<ButtonGroup>
{#if $admin.cloud && $auth.accountPortalAccess}
<Button primary on:click={$licensing.goToUpgradePage}>Upgrade</Button>
{/if}
<Button
secondary
on:click={() => {
window.open("https://budibase.com/pricing/", "_blank")
}}
on:click={() => window.open("https://budibase.com/pricing/", "_blank")}
>
View plans
</Button>
</div>
</ButtonGroup>
{:else if !backupData?.length && !loading && !filterOpt && !dateRange?.length}
<div class="center">
<Layout noPadding gap="S" justifyItems="center">
@ -311,12 +304,7 @@
display: flex;
flex-direction: row;
align-items: center;
gap: var(--spacing-m);
}
.pro-buttons {
display: flex;
gap: var(--spacing-m);
gap: var(--spacing-l);
}
.center {

View File

@ -0,0 +1,202 @@
<script lang="ts">
import {
Body,
Button,
Link,
Divider,
Heading,
Layout,
Table,
Label,
Input,
TextArea,
Select,
Helpers,
notifications,
Tag,
Tags,
ButtonGroup,
} from "@budibase/bbui"
import { appStore } from "@/stores/builder"
import { type AppScript } from "@budibase/types"
import { getSequentialName } from "@/helpers/duplicate"
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
import { licensing, auth, admin } from "@/stores/portal"
const schema = {
name: {
type: "string",
label: "Name",
},
location: {
type: "string",
label: "Location",
},
}
let selectedScript: AppScript | undefined
let isNew = false
let confirmDeleteModal: any
$: nameError = selectedScript?.name ? undefined : "Please enter a name"
$: invalid = !!nameError
$: enabled = $licensing.customAppScriptsEnabled
const addScript = () => {
const name = getSequentialName($appStore.scripts, "Script ", {
getName: script => script.name,
numberFirstItem: true,
})
selectedScript = { id: Helpers.uuid(), location: "Head", name }
isNew = true
}
const editScript = (e: any) => {
selectedScript = { ...(e.detail as AppScript) }
isNew = false
}
const cancel = () => {
selectedScript = undefined
}
const save = async () => {
if (!selectedScript) {
return
}
const newScripts = $appStore.scripts
.filter(script => script.id !== selectedScript!.id)
.concat([selectedScript])
await appStore.updateApp({ scripts: newScripts })
notifications.success("Script saved successfully")
selectedScript = undefined
}
const requestDeletion = () => {
confirmDeleteModal?.show()
}
const deleteScript = async () => {
if (!selectedScript) {
return
}
const newScripts = $appStore.scripts.filter(
script => script.id !== selectedScript!.id
)
await appStore.updateApp({ scripts: newScripts })
notifications.success("Script deleted successfully")
selectedScript = undefined
}
</script>
<Layout noPadding>
<Layout gap="XS" noPadding>
<div class="title">
<Heading>App scripts</Heading>
{#if !enabled}
<Tags>
<Tag icon="LockClosed">Enterprise</Tag>
</Tags>
{/if}
</div>
<div class="subtitle">
<Body>
Inject analytics, scripts or stylesheets into your app<br />
<Link href="https://docs.budibase.com/docs/app-scripts" target="_blank">
Learn more about script injection in the docs
</Link>
</Body>
{#if !selectedScript && enabled}
<Button cta on:click={addScript}>Add script</Button>
{/if}
</div>
</Layout>
<Divider />
{#if !enabled}
{#if $admin.cloud && !$auth.accountPortalAccess}
<Body>Contact your account holder to upgrade your plan.</Body>
{/if}
<ButtonGroup>
{#if $admin.cloud && $auth.accountPortalAccess}
<Button cta on:click={$licensing.goToUpgradePage}>Upgrade</Button>
{/if}
<Button
secondary
on:click={() => window.open("https://budibase.com/pricing/", "_blank")}
>
View plans
</Button>
</ButtonGroup>
{:else if selectedScript}
<Heading size="S">{isNew ? "Add new script" : "Edit script"}</Heading>
<div class="form">
<Label size="L">Name</Label>
<Input bind:value={selectedScript.name} error={nameError} />
<Label size="L">Location</Label>
<Select
bind:value={selectedScript.location}
options={["Head", "Body"]}
placeholder={false}
/>
<Label size="L">HTML</Label>
<TextArea
bind:value={selectedScript.html}
minHeight={200}
placeholder="&lt;script&gt;...&lt;/script&gt;"
/>
<div />
<div class="buttons">
{#if !isNew}
<Button warning quiet on:click={requestDeletion}>Delete</Button>
{/if}
<Button secondary on:click={cancel}>Cancel</Button>
<Button cta disabled={invalid} on:click={save}>Save</Button>
</div>
</div>
{:else}
<Table
on:click={editScript}
{schema}
data={$appStore.scripts}
allowSelectRows={false}
allowEditColumns={false}
allowEditRows={false}
placeholderText="You haven't added any scripts yet"
/>
{/if}
</Layout>
<ConfirmDialog
bind:this={confirmDeleteModal}
title="Delete script"
body="Are you sure you want to delete this script?"
onOk={deleteScript}
/>
<style>
.title,
.subtitle {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-l);
}
.subtitle {
justify-content: space-between;
}
.form {
display: grid;
grid-template-columns: 100px 480px;
row-gap: var(--spacing-l);
}
.form :global(.spectrum-FieldLabel) {
padding-top: 7px;
}
.buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
gap: var(--spacing-l);
}
</style>

View File

@ -24,7 +24,6 @@
auth,
admin,
licensing,
environment,
enrichedApps,
sortBy,
} from "@/stores/portal"
@ -162,7 +161,6 @@
onMount(async () => {
try {
await environment.loadVariables()
// If the portal is loaded from an external URL with a template param
const initInfo = await auth.getInitInfo()
if (initInfo?.init_template) {

View File

@ -4,9 +4,12 @@ import {
App,
AppFeatures,
AppIcon,
AppScript,
AutomationSettings,
Plugin,
UpdateAppRequest,
} from "@budibase/types"
import { get } from "svelte/store"
interface ClientFeatures {
spectrumThemes: boolean
@ -46,6 +49,7 @@ interface AppMetaState {
revertableVersion?: string
upgradableVersion?: string
icon?: AppIcon
scripts: AppScript[]
}
export const INITIAL_APP_META_STATE: AppMetaState = {
@ -79,6 +83,7 @@ export const INITIAL_APP_META_STATE: AppMetaState = {
usedPlugins: [],
automations: {},
routes: {},
scripts: [],
}
export class AppMetaStore extends BudiStore<AppMetaState> {
@ -90,20 +95,12 @@ export class AppMetaStore extends BudiStore<AppMetaState> {
this.store.set({ ...INITIAL_APP_META_STATE })
}
syncAppPackage(pkg: {
application: App
clientLibPath: string
hasLock: boolean
}) {
const { application: app, clientLibPath, hasLock } = pkg
syncApp(app: App) {
this.update(state => ({
...state,
name: app.name,
appId: app.appId,
url: app.url || "",
hasLock,
clientLibPath,
libraries: app.componentLibraries,
version: app.version,
appInstance: app.instance,
@ -118,9 +115,24 @@ export class AppMetaStore extends BudiStore<AppMetaState> {
initialised: true,
automations: app.automations || {},
hasAppPackage: true,
scripts: app.scripts || [],
}))
}
syncAppPackage(pkg: {
application: App
clientLibPath: string
hasLock: boolean
}) {
const { application, clientLibPath, hasLock } = pkg
this.update(state => ({
...state,
hasLock,
clientLibPath,
}))
this.syncApp(application)
}
syncClientFeatures(features: Partial<ClientFeatures>) {
this.update(state => ({
...state,
@ -146,6 +158,11 @@ export class AppMetaStore extends BudiStore<AppMetaState> {
}))
}
async updateApp(updates: UpdateAppRequest) {
const app = await API.saveAppMetadata(get(this.store).appId, updates)
this.syncApp(app)
}
// Returned from socket
syncMetadata(metadata: { name: string; url: string; icon?: AppIcon }) {
const { name, url, icon } = metadata

View File

@ -36,6 +36,7 @@ interface LicensingState {
budibaseAIEnabled: boolean
customAIConfigsEnabled: boolean
auditLogsEnabled: boolean
customAppScriptsEnabled: boolean
syncAutomationsEnabled: boolean
triggerAutomationRunEnabled: boolean
// the currently used quotas from the db
@ -77,6 +78,7 @@ class LicensingStore extends BudiStore<LicensingState> {
budibaseAIEnabled: false,
customAIConfigsEnabled: false,
auditLogsEnabled: false,
customAppScriptsEnabled: false,
syncAutomationsEnabled: false,
triggerAutomationRunEnabled: false,
// the currently used quotas from the db
@ -182,6 +184,9 @@ class LicensingStore extends BudiStore<LicensingState> {
const customAIConfigsEnabled = features.includes(
Constants.Features.AI_CUSTOM_CONFIGS
)
const customAppScriptsEnabled = features.includes(
Constants.Features.CUSTOM_APP_SCRIPTS
)
this.update(state => {
return {
...state,
@ -202,6 +207,7 @@ class LicensingStore extends BudiStore<LicensingState> {
syncAutomationsEnabled,
triggerAutomationRunEnabled,
perAppBuildersEnabled,
customAppScriptsEnabled,
}
})
}

View File

@ -154,6 +154,21 @@ const requiresMigration = async (ctx: Ctx) => {
return latestMigrationApplied !== latestMigration
}
const getAppScriptHTML = (
app: App,
location: "Head" | "Body",
nonce: string
) => {
if (!app.scripts?.length) {
return ""
}
return app.scripts
.filter(script => script.location === location && script.html?.length)
.map(script => script.html)
.join("\n")
.replaceAll("<script", `<script nonce="${nonce}"`)
}
export const serveApp = async function (ctx: UserCtx<void, ServeAppResponse>) {
if (ctx.url.includes("apple-touch-icon.png")) {
ctx.redirect("/builder/bblogo.png")
@ -190,6 +205,9 @@ export const serveApp = async function (ctx: UserCtx<void, ServeAppResponse>) {
const hideFooter =
ctx?.user?.license?.features?.includes(Feature.BRANDING) || false
const themeVariables = getThemeVariables(appInfo?.theme)
const addAppScripts =
ctx?.user?.license?.features?.includes(Feature.CUSTOM_APP_SCRIPTS) ||
false
if (!env.isJest()) {
const plugins = await objectStore.enrichPluginURLs(appInfo.usedPlugins)
@ -199,7 +217,8 @@ export const serveApp = async function (ctx: UserCtx<void, ServeAppResponse>) {
* BudibaseApp.svelte file as we can never detect if the types are correct. To get around this
* I've created a type which expects what the app will expect to receive.
*/
const props: BudibaseAppProps = {
const nonce = ctx.state.nonce || ""
let props: BudibaseAppProps = {
title: branding?.platformTitle || `${appInfo.name}`,
showSkeletonLoader: appInfo.features?.skeletonLoader ?? false,
hideDevTools,
@ -218,7 +237,13 @@ export const serveApp = async function (ctx: UserCtx<void, ServeAppResponse>) {
? await objectStore.getGlobalFileUrl("settings", "faviconUrl")
: "",
appMigrating: needMigrations,
nonce: ctx.state.nonce,
nonce,
}
// Add custom app scripts if enabled
if (addAppScripts) {
props.headAppScripts = getAppScriptHTML(appInfo, "Head", nonce)
props.bodyAppScripts = getAppScriptHTML(appInfo, "Body", nonce)
}
const { head, html, css } = AppComponent.render({ props })
@ -273,10 +298,22 @@ export const serveBuilderPreview = async function (
const templateLoc = join(__dirname, "templates")
const previewLoc = fs.existsSync(templateLoc) ? templateLoc : __dirname
const previewHbs = loadHandlebarsFile(join(previewLoc, "preview.hbs"))
ctx.body = await processString(previewHbs, {
const nonce = ctx.state.nonce || ""
const addAppScripts =
ctx?.user?.license?.features?.includes(Feature.CUSTOM_APP_SCRIPTS) ||
false
let props: any = {
clientLibPath: objectStore.clientLibraryUrl(appId!, appInfo.version),
nonce: ctx.state.nonce,
})
nonce,
}
// Add custom app scripts if enabled
if (addAppScripts) {
props.headAppScripts = getAppScriptHTML(appInfo, "Head", nonce)
props.bodyAppScripts = getAppScriptHTML(appInfo, "Body", nonce)
}
ctx.body = await processString(previewHbs, props)
} else {
// just return the app info for jest to assert on
ctx.body = { ...appInfo, builderPreview: true }

View File

@ -88,6 +88,9 @@
font-weight: 400;
}
</style>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html props.headAppScripts || ""}
</svelte:head>
<body id="app">
@ -135,4 +138,7 @@
document.getElementById("error").style.display = "flex"
}
</script>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html props.bodyAppScripts || ""}
</body>

View File

@ -5,14 +5,12 @@
})
</script>
<head>
<script nonce="{{ nonce }}">
window["##BUDIBASE_APP_ID##"] = "{{appId}}"
window["##BUDIBASE_APP_EMBEDDED##"] = "{{embedded}}"
</script>
{{{head}}}
<style>{{{css}}}</style>
</head>
<script nonce="{{ nonce }}">
window["##BUDIBASE_APP_ID##"] = "{{appId}}"
window["##BUDIBASE_APP_EMBEDDED##"] = "{{embedded}}"
</script>
{{{body}}}
</html>

View File

@ -108,6 +108,9 @@
window.addEventListener("message", receiveMessage)
window.parent.postMessage({ type: "ready" })
</script>
{{ headAppScripts }}
</head>
<body></body>
<body>
{{ bodyAppScripts }}
</body>
</html>

View File

@ -29,6 +29,7 @@ export interface App extends Document {
snippets?: Snippet[]
creationVersion?: string
updatedBy?: string
scripts?: AppScript[]
}
export interface AppInstance {
@ -82,3 +83,10 @@ export interface AppFeatures {
export interface AutomationSettings {
chainAutomations?: boolean
}
export interface AppScript {
id: string
name: string
location: "Head" | "Body"
html?: string
}

View File

@ -13,6 +13,7 @@ export enum Feature {
APP_BUILDERS = "appBuilders",
OFFLINE = "offline",
EXPANDED_PUBLIC_API = "expandedPublicApi",
CUSTOM_APP_SCRIPTS = "customAppScripts",
// deprecated - no longer licensed
VIEW_PERMISSIONS = "viewPermissions",
VIEW_READONLY_COLUMNS = "viewReadonlyColumns",

View File

@ -14,4 +14,6 @@ export interface BudibaseAppProps {
sideNav?: boolean
hideFooter?: boolean
nonce?: string
headAppScripts?: string
bodyAppScripts?: string
}

View File

@ -2699,17 +2699,10 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2"
integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.8.3":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433"
integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==
"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2"
integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==
dependencies:
regenerator-runtime "^0.14.0"