Merge branch 'csp-whitelist' of github.com:Budibase/budibase into csp-whitelist

This commit is contained in:
Andrew Kingston 2025-04-08 09:59:29 +01:00
commit 95e3f17d8f
No known key found for this signature in database
8 changed files with 112 additions and 80 deletions

View File

@ -1,15 +1,16 @@
<script> <script lang="ts">
import "@spectrum-css/switch/dist/index-vars.css" import "@spectrum-css/switch/dist/index-vars.css"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let value = false export let value: boolean = false
export let id = null export let id: string | null = null
export let text = null export let text: string | null = null
export let disabled = false export let disabled: boolean = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = event => { const onChange = (event: Event) => {
dispatch("change", event.target.checked) const target = event.target as HTMLInputElement
dispatch("change", target.checked)
} }
</script> </script>

View File

@ -1,23 +1,29 @@
<script> <script lang="ts">
import Field from "./Field.svelte" import Field from "./Field.svelte"
import Switch from "./Core/Switch.svelte" import Switch from "./Core/Switch.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let value = undefined export let value: boolean | null | undefined = undefined
export let label = null export let label: string | undefined = undefined
export let labelPosition = "above" export let labelPosition: "above" = "above"
export let text = undefined export let text: string | undefined = undefined
export let disabled = false export let disabled: boolean | undefined = false
export let error = null export let error: string | undefined = undefined
export let helpText = null export let helpText: string | undefined = undefined
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = (e: CustomEvent<boolean>) => {
value = e.detail value = e.detail
dispatch("change", e.detail) dispatch("change", e.detail)
} }
</script> </script>
<Field {helpText} {label} {labelPosition} {error}> <Field {helpText} {label} {labelPosition} {error}>
<Switch {error} {disabled} {text} {value} on:change={onChange} on:click /> <Switch
{disabled}
{text}
value={value ?? undefined}
on:change={onChange}
on:click
/>
</Field> </Field>

View File

@ -1,20 +1,20 @@
<script> <script lang="ts">
import { Select } from "@budibase/bbui" import { Select } from "@budibase/bbui"
import { roles } from "@/stores/builder" import { roles } from "@/stores/builder"
export let value export let value: string
export let error export let error: string | undefined = undefined
export let placeholder = null export let placeholder: string | undefined = undefined
export let autoWidth = false export let autoWidth: boolean = false
</script> </script>
<Select <Select
bind:value bind:value
on:change on:change
options={$roles} options={$roles}
getOptionLabel={role => role.uiMetadata.displayName} getOptionLabel={role => role.uiMetadata?.displayName}
getOptionValue={role => role._id} getOptionValue={role => role._id}
getOptionColour={role => role.uiMetadata.color} getOptionColour={role => role.uiMetadata?.color}
{placeholder} {placeholder}
{error} {error}
{autoWidth} {autoWidth}

View File

@ -1,22 +1,23 @@
<script> <script lang="ts">
import { Label, notifications } from "@budibase/bbui" import { Label, notifications } from "@budibase/bbui"
import { permissions } from "@/stores/builder" import { permissions } from "@/stores/builder"
import { Constants } from "@budibase/frontend-core" import { Constants } from "@budibase/frontend-core"
import RoleSelect from "@/components/design/settings/controls/RoleSelect.svelte" import RoleSelect from "@/components/design/settings/controls/RoleSelect.svelte"
import { PermissionLevel, type Query } from "@budibase/types"
export let query export let query: Query
export let label export let label
$: getPermissions(query) $: getPermissions(query)
let roleId, loaded, fetched let roleId: string, loaded: boolean, fetched: Query | undefined
async function updateRole(role, id) { async function updateRole(role: string) {
try { try {
roleId = role roleId = role
const queryId = query?._id || id const queryId = query._id
if (roleId && queryId) { if (roleId && queryId) {
for (let level of ["read", "write"]) { for (let level of [PermissionLevel.READ, PermissionLevel.WRITE]) {
await permissions.save({ await permissions.save({
level, level,
role, role,
@ -29,7 +30,7 @@
} }
} }
async function getPermissions(queryToFetch) { async function getPermissions(queryToFetch: Query) {
if (fetched?._id === queryToFetch?._id) { if (fetched?._id === queryToFetch?._id) {
loaded = true loaded = true
return return

View File

@ -1,4 +1,4 @@
<script> <script lang="ts" generics="O">
import { import {
Icon, Icon,
ActionButton, ActionButton,
@ -15,30 +15,30 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let defaults = undefined export let defaults: Record<string, string> | undefined = undefined
export let object = defaults || {} export let object: Record<string, string> = defaults || {}
export let activity = {} export let activity: Record<string, boolean> = {}
export let readOnly = false export let readOnly: boolean = false
export let noAddButton = false export let noAddButton: boolean = false
export let name = "" export let name: string = ""
export let headings = false export let headings: boolean = false
export let options = undefined export let options: O[] | undefined = undefined
export let toggle = false export let toggle: boolean = false
export let keyPlaceholder = "Key" export let keyPlaceholder: string = "Key"
export let valuePlaceholder = "Value" export let valuePlaceholder: string = "Value"
export let valueHeading = "" export let valueHeading: string = ""
export let keyHeading = "" export let keyHeading: string = ""
export let tooltip = "" export let tooltip: string = ""
export let menuItems = [] export let menuItems: any[] = []
export let showMenu = false export let showMenu: boolean = false
export let bindings = [] export let bindings: any[] = []
export let allowHelpers = true export let allowHelpers: boolean = true
export let customButtonText = null export let customButtonText = null
export let keyBindings = false export let keyBindings: boolean = false
export let allowJS = false export let allowJS: boolean = false
export let actionButtonDisabled = false export let actionButtonDisabled: boolean = false
export let compare = (option, value) => option === value export let compare = (option: O, value: O) => option === value
export let context = null export let context: any = null
let fields = Object.entries(object || {}).map(([name, value]) => ({ let fields = Object.entries(object || {}).map(([name, value]) => ({
name, name,
@ -46,27 +46,32 @@
})) }))
let fieldActivity = buildFieldActivity(activity) let fieldActivity = buildFieldActivity(activity)
$: fullObject = fields.reduce((acc, next) => { $: fullObject = fields.reduce<Record<string, string>>((acc, next) => {
acc[next.name] = next.value acc[next.name] = next.value
return acc return acc
}, {}) }, {})
$: object = Object.entries(fullObject).reduce((acc, [key, next]) => { $: object = Object.entries(fullObject).reduce<Record<string, string>>(
if (key) { (acc, [key, next]) => {
acc[key] = next if (key) {
} acc[key] = next
return acc }
}, {}) return acc
},
{}
)
function buildFieldActivity(obj) { function buildFieldActivity(obj: Record<string, boolean>) {
if (!obj || typeof obj !== "object") { if (!obj || typeof obj !== "object") {
return [] return []
} }
const array = Array(fields.length) const array: boolean[] = Array(fields.length)
for (let [key, value] of Object.entries(obj)) { for (let [key, value] of Object.entries(obj)) {
const field = fields.find(el => el.name === key) const field = fields.find(el => el.name === key)
const idx = fields.indexOf(field) if (field) {
array[idx] = idx !== -1 ? value : true const idx = fields.indexOf(field)
array[idx] = idx !== -1 ? value : true
}
} }
return array return array
} }
@ -77,7 +82,7 @@
changed() changed()
} }
function deleteEntry(idx) { function deleteEntry(idx: number) {
fields.splice(idx, 1) fields.splice(idx, 1)
fieldActivity.splice(idx, 1) fieldActivity.splice(idx, 1)
changed() changed()
@ -86,7 +91,7 @@
function changed() { function changed() {
// Required for reactivity // Required for reactivity
fields = fields fields = fields
const newActivity = {} const newActivity: Record<string, boolean> = {}
for (let idx = 0; idx < fields.length; idx++) { for (let idx = 0; idx < fields.length; idx++) {
const fieldName = fields[idx].name const fieldName = fields[idx].name
if (fieldName) { if (fieldName) {
@ -97,7 +102,7 @@
dispatch("change", fields) dispatch("change", fields)
} }
function isJsonArray(value) { function isJsonArray(value: any) {
if (!value || typeof value === "string") { if (!value || typeof value === "string") {
return false return false
} }
@ -181,7 +186,7 @@
{#if !readOnly} {#if !readOnly}
<Icon hoverable name="Close" on:click={() => deleteEntry(idx)} /> <Icon hoverable name="Close" on:click={() => deleteEntry(idx)} />
{/if} {/if}
{#if menuItems?.length > 0 && showMenu} {#if menuItems?.length && showMenu}
<ActionMenu> <ActionMenu>
<div slot="control" class="control icon"> <div slot="control" class="control icon">
<Icon size="S" hoverable name="MoreSmallList" /> <Icon size="S" hoverable name="MoreSmallList" />
@ -201,9 +206,6 @@
<ActionButton <ActionButton
disabled={actionButtonDisabled} disabled={actionButtonDisabled}
icon="Add" icon="Add"
secondary
thin
outline
on:click={addEntry} on:click={addEntry}
> >
{#if customButtonText} {#if customButtonText}

View File

@ -764,6 +764,7 @@ if (descriptions.length) {
const verbs = ["read", "create", "update", "delete"] const verbs = ["read", "create", "update", "delete"]
for (const verb of verbs) { for (const verb of verbs) {
const query = await createQuery({ const query = await createQuery({
// @ts-expect-error
fields: { json: {}, extra: { actionType: "invalid" } }, fields: { json: {}, extra: { actionType: "invalid" } },
queryVerb: verb, queryVerb: verb,
}) })

View File

@ -1,6 +1,6 @@
import * as setup from "../utilities" import * as setup from "../utilities"
import TestConfiguration from "../../../../tests/utilities/TestConfiguration" import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
import { Datasource, SourceName } from "@budibase/types" import { BodyType, Datasource, SourceName } from "@budibase/types"
import { getCachedVariable } from "../../../../threads/utils" import { getCachedVariable } from "../../../../threads/utils"
import nock from "nock" import nock from "nock"
import { generator } from "@budibase/backend-core/tests" import { generator } from "@budibase/backend-core/tests"
@ -259,7 +259,7 @@ describe("rest", () => {
readable: true, readable: true,
fields: { fields: {
path: "www.example.com", path: "www.example.com",
bodyType: "text", bodyType: BodyType.TEXT,
queryString: "&testParam={{testParam}}", queryString: "&testParam={{testParam}}",
requestBody: requestBody:
"This is plain text and this is my email: {{[user].[email]}}. This is a test param: {{testParam}}", "This is plain text and this is my email: {{[user].[email]}}. This is a test param: {{testParam}}",
@ -305,7 +305,7 @@ describe("rest", () => {
readable: true, readable: true,
fields: { fields: {
path: "www.example.com", path: "www.example.com",
bodyType: "json", bodyType: BodyType.JSON,
queryString: "&testParam={{testParam}}", queryString: "&testParam={{testParam}}",
requestBody: requestBody:
'{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}', '{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}',
@ -350,7 +350,7 @@ describe("rest", () => {
readable: true, readable: true,
fields: { fields: {
path: "www.example.com", path: "www.example.com",
bodyType: "xml", bodyType: BodyType.XML,
queryString: "&testParam={{testParam}}", queryString: "&testParam={{testParam}}",
requestBody: requestBody:
"<note> <email>{{[user].[email]}}</email> <code>{{testParam}}</code> " + "<note> <email>{{[user].[email]}}</email> <code>{{testParam}}</code> " +
@ -399,7 +399,7 @@ describe("rest", () => {
readable: true, readable: true,
fields: { fields: {
path: "www.example.com", path: "www.example.com",
bodyType: "form", bodyType: BodyType.FORM_DATA,
queryString: "&testParam={{testParam}}", queryString: "&testParam={{testParam}}",
requestBody: requestBody:
'{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}', '{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}',
@ -445,7 +445,7 @@ describe("rest", () => {
readable: true, readable: true,
fields: { fields: {
path: "www.example.com", path: "www.example.com",
bodyType: "encoded", bodyType: BodyType.ENCODED,
queryString: "&testParam={{testParam}}", queryString: "&testParam={{testParam}}",
requestBody: requestBody:
'{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}', '{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}',

View File

@ -12,7 +12,7 @@ export interface Query extends Document {
datasourceId: string datasourceId: string
name: string name: string
parameters: QueryParameter[] parameters: QueryParameter[]
fields: RestQueryFields | any fields: RestQueryFields & SQLQueryFields & MongoQueryFields
transformer: string | null transformer: string | null
schema: Record<string, QuerySchema | string> schema: Record<string, QuerySchema | string>
nestedSchemaFields?: Record<string, Record<string, QuerySchema | string>> nestedSchemaFields?: Record<string, Record<string, QuerySchema | string>>
@ -61,6 +61,27 @@ export interface RestQueryFields {
pagination?: PaginationConfig pagination?: PaginationConfig
paginationValues?: PaginationValues paginationValues?: PaginationValues
} }
export interface SQLQueryFields {
sql?: string
}
export interface MongoQueryFields {
extra?: {
collection?: string
actionType:
| "findOne"
| "find"
| "updateOne"
| "updateMany"
| "findOneAndUpdate"
| "count"
| "distinct"
| "insertOne"
| "deleteOne"
| "deleteMany"
}
json?: object | string
}
export interface PaginationConfig { export interface PaginationConfig {
type: string type: string