Merge branch 'master' into BUDI-7580/account_portal_submodule
This commit is contained in:
commit
ef914882d4
|
@ -45,8 +45,8 @@ jobs:
|
|||
BUMP_TYPE=${BUMP_TYPE_INPUT:-"patch"}
|
||||
./versionCommit.sh $BUMP_TYPE
|
||||
|
||||
|
||||
new_version=$(./getCurrentVersion.sh)
|
||||
cd ..
|
||||
new_version=$(./scripts/getCurrentVersion.sh)
|
||||
echo "version=$new_version" >> $GITHUB_OUTPUT
|
||||
|
||||
trigger-release:
|
||||
|
|
|
@ -2,16 +2,18 @@ server {
|
|||
listen 443 ssl default_server;
|
||||
listen [::]:443 ssl default_server;
|
||||
server_name _;
|
||||
ssl_certificate /etc/letsencrypt/live/CUSTOM_DOMAIN/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/CUSTOM_DOMAIN/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
error_log /dev/stderr warn;
|
||||
access_log /dev/stdout main;
|
||||
client_max_body_size 1000m;
|
||||
ignore_invalid_headers off;
|
||||
proxy_buffering off;
|
||||
# port_in_redirect off;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/CUSTOM_DOMAIN/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/CUSTOM_DOMAIN/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
default_type "text/plain";
|
||||
root /var/www/html;
|
||||
|
@ -47,6 +49,24 @@ server {
|
|||
rewrite ^/worker/(.*)$ /$1 break;
|
||||
}
|
||||
|
||||
location /api/backups/ {
|
||||
# calls to export apps are limited
|
||||
limit_req zone=ratelimit burst=20 nodelay;
|
||||
|
||||
# 1800s timeout for app export requests
|
||||
proxy_read_timeout 1800s;
|
||||
proxy_connect_timeout 1800s;
|
||||
proxy_send_timeout 1800s;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
proxy_pass http://127.0.0.1:4001;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
# calls to the API are rate limited with bursting
|
||||
limit_req zone=ratelimit burst=20 nodelay;
|
||||
|
@ -70,18 +90,49 @@ server {
|
|||
rewrite ^/db/(.*)$ /$1 break;
|
||||
}
|
||||
|
||||
location /socket/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_pass http://127.0.0.1:4001;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_connect_timeout 300;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
chunked_transfer_encoding off;
|
||||
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
}
|
||||
|
||||
location /files/signed/ {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# IMPORTANT: Signed urls will inspect the host header of the request.
|
||||
# Normally a signed url will need to be generated with a specified client host in mind.
|
||||
# To support dynamic hosts, e.g. some unknown self-hosted installation url,
|
||||
# use a predefined host header. The host 'minio-service' is also used at the time of url signing.
|
||||
proxy_set_header Host minio-service;
|
||||
|
||||
proxy_connect_timeout 300;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
chunked_transfer_encoding off;
|
||||
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
rewrite ^/files/signed/(.*)$ /$1 break;
|
||||
}
|
||||
|
||||
client_header_timeout 60;
|
||||
client_body_timeout 60;
|
||||
keepalive_timeout 60;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.13.0",
|
||||
"version": "2.13.3",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
|
@ -30,6 +30,7 @@ export * as timers from "./timers"
|
|||
export { default as env } from "./environment"
|
||||
export * as blacklist from "./blacklist"
|
||||
export * as docUpdates from "./docUpdates"
|
||||
export * from "./utils/Duration"
|
||||
export { SearchParams } from "./db"
|
||||
// Add context to tenancy for backwards compatibility
|
||||
// only do this for external usages to prevent internal
|
||||
|
|
|
@ -36,7 +36,7 @@ class InMemoryQueue {
|
|||
* @param opts This is not used by the in memory queue as there is no real use
|
||||
* case when in memory, but is the same API as Bull
|
||||
*/
|
||||
constructor(name: string, opts = null) {
|
||||
constructor(name: string, opts?: any) {
|
||||
this._name = name
|
||||
this._opts = opts
|
||||
this._messages = []
|
||||
|
|
|
@ -2,11 +2,17 @@ import env from "../environment"
|
|||
import { getRedisOptions } from "../redis/utils"
|
||||
import { JobQueue } from "./constants"
|
||||
import InMemoryQueue from "./inMemoryQueue"
|
||||
import BullQueue from "bull"
|
||||
import BullQueue, { QueueOptions } from "bull"
|
||||
import { addListeners, StalledFn } from "./listeners"
|
||||
import { Duration } from "../utils"
|
||||
import * as timers from "../timers"
|
||||
|
||||
const CLEANUP_PERIOD_MS = 60 * 1000
|
||||
// the queue lock is held for 5 minutes
|
||||
const QUEUE_LOCK_MS = Duration.fromMinutes(5).toMs()
|
||||
// queue lock is refreshed every 30 seconds
|
||||
const QUEUE_LOCK_RENEW_INTERNAL_MS = Duration.fromSeconds(30).toMs()
|
||||
// cleanup the queue every 60 seconds
|
||||
const CLEANUP_PERIOD_MS = Duration.fromSeconds(60).toMs()
|
||||
let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = []
|
||||
let cleanupInterval: NodeJS.Timeout
|
||||
|
||||
|
@ -20,8 +26,15 @@ export function createQueue<T>(
|
|||
jobQueue: JobQueue,
|
||||
opts: { removeStalledCb?: StalledFn } = {}
|
||||
): BullQueue.Queue<T> {
|
||||
const { opts: redisOpts, redisProtocolUrl } = getRedisOptions()
|
||||
const queueConfig: any = redisProtocolUrl || { redis: redisOpts }
|
||||
const redisOpts = getRedisOptions()
|
||||
const queueConfig: QueueOptions = {
|
||||
redis: redisOpts,
|
||||
settings: {
|
||||
maxStalledCount: 0,
|
||||
lockDuration: QUEUE_LOCK_MS,
|
||||
lockRenewTime: QUEUE_LOCK_RENEW_INTERNAL_MS,
|
||||
},
|
||||
}
|
||||
let queue: any
|
||||
if (!env.isTest()) {
|
||||
queue = new BullQueue(jobQueue, queueConfig)
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
getRedisOptions,
|
||||
SEPARATOR,
|
||||
SelectableDatabase,
|
||||
getRedisConnectionDetails,
|
||||
} from "./utils"
|
||||
import * as timers from "../timers"
|
||||
|
||||
|
@ -91,12 +92,11 @@ function init(selectDb = DEFAULT_SELECT_DB) {
|
|||
if (client) {
|
||||
client.disconnect()
|
||||
}
|
||||
const { redisProtocolUrl, opts, host, port } = getRedisOptions()
|
||||
const { host, port } = getRedisConnectionDetails()
|
||||
const opts = getRedisOptions()
|
||||
|
||||
if (CLUSTERED) {
|
||||
client = new RedisCore.Cluster([{ host, port }], opts)
|
||||
} else if (redisProtocolUrl) {
|
||||
client = new RedisCore(redisProtocolUrl)
|
||||
} else {
|
||||
client = new RedisCore(opts)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import env from "../environment"
|
||||
import * as Redis from "ioredis"
|
||||
|
||||
const SLOT_REFRESH_MS = 2000
|
||||
const CONNECT_TIMEOUT_MS = 10000
|
||||
|
@ -42,7 +43,7 @@ export enum Databases {
|
|||
export enum SelectableDatabase {
|
||||
DEFAULT = 0,
|
||||
SOCKET_IO = 1,
|
||||
UNUSED_1 = 2,
|
||||
RATE_LIMITING = 2,
|
||||
UNUSED_2 = 3,
|
||||
UNUSED_3 = 4,
|
||||
UNUSED_4 = 5,
|
||||
|
@ -58,7 +59,7 @@ export enum SelectableDatabase {
|
|||
UNUSED_14 = 15,
|
||||
}
|
||||
|
||||
export function getRedisOptions() {
|
||||
export function getRedisConnectionDetails() {
|
||||
let password = env.REDIS_PASSWORD
|
||||
let url: string[] | string = env.REDIS_URL.split("//")
|
||||
// get rid of the protocol
|
||||
|
@ -74,28 +75,34 @@ export function getRedisOptions() {
|
|||
}
|
||||
const [host, port] = url.split(":")
|
||||
|
||||
let redisProtocolUrl
|
||||
|
||||
// fully qualified redis URL
|
||||
if (/rediss?:\/\//.test(env.REDIS_URL)) {
|
||||
redisProtocolUrl = env.REDIS_URL
|
||||
return {
|
||||
host,
|
||||
password,
|
||||
port: parseInt(port),
|
||||
}
|
||||
}
|
||||
|
||||
const opts: any = {
|
||||
export function getRedisOptions() {
|
||||
const { host, password, port } = getRedisConnectionDetails()
|
||||
let redisOpts: Redis.RedisOptions = {
|
||||
connectTimeout: CONNECT_TIMEOUT_MS,
|
||||
port: port,
|
||||
host,
|
||||
password,
|
||||
}
|
||||
let opts: Redis.ClusterOptions | Redis.RedisOptions = redisOpts
|
||||
if (env.REDIS_CLUSTERED) {
|
||||
opts.redisOptions = {}
|
||||
opts.redisOptions.tls = {}
|
||||
opts.redisOptions.password = password
|
||||
opts.slotsRefreshTimeout = SLOT_REFRESH_MS
|
||||
opts.dnsLookup = (address: string, callback: any) => callback(null, address)
|
||||
} else {
|
||||
opts.host = host
|
||||
opts.port = port
|
||||
opts.password = password
|
||||
opts = {
|
||||
connectTimeout: CONNECT_TIMEOUT_MS,
|
||||
redisOptions: {
|
||||
...redisOpts,
|
||||
tls: {},
|
||||
},
|
||||
slotsRefreshTimeout: SLOT_REFRESH_MS,
|
||||
dnsLookup: (address: string, callback: any) => callback(null, address),
|
||||
} as Redis.ClusterOptions
|
||||
}
|
||||
return { opts, host, port: parseInt(port), redisProtocolUrl }
|
||||
return opts
|
||||
}
|
||||
|
||||
export function addDbPrefix(db: string, key: string) {
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
export enum DurationType {
|
||||
MILLISECONDS = "milliseconds",
|
||||
SECONDS = "seconds",
|
||||
MINUTES = "minutes",
|
||||
HOURS = "hours",
|
||||
DAYS = "days",
|
||||
}
|
||||
|
||||
const conversion: Record<DurationType, number> = {
|
||||
milliseconds: 1,
|
||||
seconds: 1000,
|
||||
minutes: 60 * 1000,
|
||||
hours: 60 * 60 * 1000,
|
||||
days: 24 * 60 * 60 * 1000,
|
||||
}
|
||||
|
||||
export class Duration {
|
||||
static convert(from: DurationType, to: DurationType, duration: number) {
|
||||
const milliseconds = duration * conversion[from]
|
||||
return milliseconds / conversion[to]
|
||||
}
|
||||
|
||||
static from(from: DurationType, duration: number) {
|
||||
return {
|
||||
to: (to: DurationType) => {
|
||||
return Duration.convert(from, to, duration)
|
||||
},
|
||||
toMs: () => {
|
||||
return Duration.convert(from, DurationType.MILLISECONDS, duration)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
static fromSeconds(duration: number) {
|
||||
return Duration.from(DurationType.SECONDS, duration)
|
||||
}
|
||||
|
||||
static fromMinutes(duration: number) {
|
||||
return Duration.from(DurationType.MINUTES, duration)
|
||||
}
|
||||
|
||||
static fromHours(duration: number) {
|
||||
return Duration.from(DurationType.HOURS, duration)
|
||||
}
|
||||
|
||||
static fromDays(duration: number) {
|
||||
return Duration.from(DurationType.DAYS, duration)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./hashing"
|
||||
export * from "./utils"
|
||||
export * from "./stringUtils"
|
||||
export * from "./Duration"
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { Duration, DurationType } from "../Duration"
|
||||
|
||||
describe("duration", () => {
|
||||
it("should convert minutes to milliseconds", () => {
|
||||
expect(Duration.fromMinutes(5).toMs()).toBe(300000)
|
||||
})
|
||||
|
||||
it("should convert seconds to milliseconds", () => {
|
||||
expect(Duration.fromSeconds(30).toMs()).toBe(30000)
|
||||
})
|
||||
|
||||
it("should convert days to milliseconds", () => {
|
||||
expect(Duration.fromDays(1).toMs()).toBe(86400000)
|
||||
})
|
||||
|
||||
it("should convert minutes to days", () => {
|
||||
expect(Duration.fromMinutes(1440).to(DurationType.DAYS)).toBe(1)
|
||||
})
|
||||
})
|
|
@ -8,6 +8,7 @@
|
|||
export let id = null
|
||||
export let text = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let size
|
||||
export let indeterminate = false
|
||||
|
||||
|
@ -24,6 +25,7 @@
|
|||
class:is-invalid={!!error}
|
||||
class:checked={value}
|
||||
class:is-indeterminate={indeterminate}
|
||||
class:readonly
|
||||
>
|
||||
<input
|
||||
checked={value}
|
||||
|
@ -68,4 +70,7 @@
|
|||
.spectrum-Checkbox-input {
|
||||
opacity: 0;
|
||||
}
|
||||
.readonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export let options = []
|
||||
export let error = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let getOptionLabel = option => option
|
||||
export let getOptionValue = option => option
|
||||
|
||||
|
@ -34,6 +35,7 @@
|
|||
title={getOptionLabel(option)}
|
||||
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
||||
class:is-invalid={!!error}
|
||||
class:readonly
|
||||
>
|
||||
<label
|
||||
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-FieldGroup-item"
|
||||
|
@ -66,4 +68,7 @@
|
|||
.spectrum-Checkbox-input {
|
||||
opacity: 0;
|
||||
}
|
||||
.readonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export let id = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let enableTime = true
|
||||
export let value = null
|
||||
|
@ -186,7 +187,7 @@
|
|||
>
|
||||
<div
|
||||
id={flatpickrId}
|
||||
class:is-disabled={disabled}
|
||||
class:is-disabled={disabled || readonly}
|
||||
class:is-invalid={!!error}
|
||||
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
||||
class:is-focused={open}
|
||||
|
@ -211,6 +212,7 @@
|
|||
{/if}
|
||||
<input
|
||||
{disabled}
|
||||
{readonly}
|
||||
data-input
|
||||
type="text"
|
||||
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
||||
|
|
|
@ -386,7 +386,7 @@
|
|||
}
|
||||
.compact .placeholder,
|
||||
.compact img {
|
||||
margin: 10px 16px;
|
||||
margin: 8px 16px;
|
||||
}
|
||||
.compact img {
|
||||
height: 90px;
|
||||
|
@ -456,6 +456,12 @@
|
|||
color: var(--red);
|
||||
}
|
||||
|
||||
.spectrum-Dropzone {
|
||||
height: 220px;
|
||||
}
|
||||
.compact .spectrum-Dropzone {
|
||||
height: 40px;
|
||||
}
|
||||
.spectrum-Dropzone.disabled {
|
||||
pointer-events: none;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
|
@ -463,10 +469,6 @@
|
|||
.disabled .spectrum-Heading--sizeL {
|
||||
color: var(--spectrum-alias-text-color-disabled);
|
||||
}
|
||||
.compact .spectrum-Dropzone {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.compact .spectrum-IllustratedMessage-description {
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -477,7 +479,6 @@
|
|||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export let options = []
|
||||
export let error = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let getOptionLabel = option => option
|
||||
export let getOptionValue = option => option
|
||||
export let getOptionTitle = option => option
|
||||
|
@ -40,6 +41,7 @@
|
|||
title={getOptionTitle(option)}
|
||||
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
||||
class:is-invalid={!!error}
|
||||
class:readonly
|
||||
>
|
||||
<input
|
||||
on:change={onChange}
|
||||
|
@ -62,4 +64,7 @@
|
|||
.spectrum-Radio-input {
|
||||
opacity: 0;
|
||||
}
|
||||
.readonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
export let value = ""
|
||||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let height = null
|
||||
export let id = null
|
||||
|
@ -20,6 +21,7 @@
|
|||
{fullScreenOffset}
|
||||
{disabled}
|
||||
{easyMDEOptions}
|
||||
{readonly}
|
||||
on:change
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
export let value = ""
|
||||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let id = null
|
||||
export let height = null
|
||||
|
@ -61,6 +62,7 @@
|
|||
class="spectrum-Textfield-input"
|
||||
style={align ? `text-align: ${align}` : ""}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{id}
|
||||
on:focus={() => (focus = true)}
|
||||
on:blur={onChange}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
export let label = null
|
||||
export let labelPosition = "above"
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let enableTime = true
|
||||
export let timeOnly = false
|
||||
|
@ -33,6 +34,7 @@
|
|||
<DatePicker
|
||||
{error}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{value}
|
||||
{placeholder}
|
||||
{enableTime}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export let id = null
|
||||
export let fullScreenOffset = 0
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let easyMDEOptions
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -19,6 +20,9 @@
|
|||
// control
|
||||
$: checkValue(value)
|
||||
$: mde?.codemirror.on("change", debouncedUpdate)
|
||||
$: if (readonly || disabled) {
|
||||
mde?.togglePreview()
|
||||
}
|
||||
|
||||
const checkValue = val => {
|
||||
if (mde && val !== latestValue) {
|
||||
|
@ -54,6 +58,7 @@
|
|||
easyMDEOptions={{
|
||||
initialValue: value,
|
||||
placeholder,
|
||||
toolbar: disabled || readonly ? false : undefined,
|
||||
...easyMDEOptions,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
const NUMBER_TYPE = FIELDS.NUMBER.type
|
||||
const JSON_TYPE = FIELDS.JSON.type
|
||||
const DATE_TYPE = FIELDS.DATETIME.type
|
||||
const USER_TYPE = FIELDS.USER.subtype
|
||||
const USERS_TYPE = FIELDS.USERS.subtype
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
||||
|
@ -287,6 +289,14 @@
|
|||
if (saveColumn.type !== LINK_TYPE) {
|
||||
delete saveColumn.fieldName
|
||||
}
|
||||
if (isUsersColumn(saveColumn)) {
|
||||
if (saveColumn.subtype === USER_TYPE) {
|
||||
saveColumn.relationshipType = RelationshipType.ONE_TO_MANY
|
||||
} else if (saveColumn.subtype === USERS_TYPE) {
|
||||
saveColumn.relationshipType = RelationshipType.MANY_TO_MANY
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await tables.saveField({
|
||||
originalName,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
let open = false
|
||||
|
||||
// Auto hide the component when another item is selected
|
||||
$: if (open && $draggable.selected != componentInstance._id) {
|
||||
$: if (open && $draggable.selected !== componentInstance._id) {
|
||||
popover.hide()
|
||||
}
|
||||
|
||||
|
@ -100,13 +100,13 @@
|
|||
}}
|
||||
on:close={() => {
|
||||
open = false
|
||||
if ($draggable.selected == componentInstance._id) {
|
||||
if ($draggable.selected === componentInstance._id) {
|
||||
$draggable.actions.select()
|
||||
}
|
||||
}}
|
||||
{anchor}
|
||||
align="left-outside"
|
||||
showPopover={drawers.length == 0}
|
||||
showPopover={drawers.length === 0}
|
||||
clickOutsideOverride={drawers.length > 0}
|
||||
maxHeight={600}
|
||||
handlePostionUpdate={customPositionHandler}
|
||||
|
@ -115,6 +115,7 @@
|
|||
<Layout noPadding noGap>
|
||||
<slot name="header" />
|
||||
<ComponentSettingsSection
|
||||
includeHidden
|
||||
{componentInstance}
|
||||
componentDefinition={parsedComponentDef}
|
||||
isScreen={false}
|
||||
|
|
|
@ -112,9 +112,9 @@
|
|||
}
|
||||
await usersFetch.update({
|
||||
query: {
|
||||
appId: query || !filterByAppAccess ? null : prodAppId,
|
||||
email: query,
|
||||
string: { email: query },
|
||||
},
|
||||
appId: query || !filterByAppAccess ? null : prodAppId,
|
||||
limit: 50,
|
||||
paginate: query || !filterByAppAccess ? null : false,
|
||||
})
|
||||
|
|
|
@ -16,16 +16,18 @@
|
|||
export let isScreen = false
|
||||
export let onUpdateSetting
|
||||
export let showSectionTitle = true
|
||||
export let includeHidden = false
|
||||
export let tag
|
||||
|
||||
$: sections = getSections(
|
||||
componentInstance,
|
||||
componentDefinition,
|
||||
isScreen,
|
||||
tag
|
||||
tag,
|
||||
includeHidden
|
||||
)
|
||||
|
||||
const getSections = (instance, definition, isScreen, tag) => {
|
||||
const getSections = (instance, definition, isScreen, tag, includeHidden) => {
|
||||
const settings = definition?.settings ?? []
|
||||
const generalSettings = settings.filter(
|
||||
setting => !setting.section && setting.tag === tag
|
||||
|
@ -52,7 +54,12 @@
|
|||
return
|
||||
}
|
||||
section.settings.forEach(setting => {
|
||||
setting.visible = canRenderControl(instance, setting, isScreen)
|
||||
setting.visible = canRenderControl(
|
||||
instance,
|
||||
setting,
|
||||
isScreen,
|
||||
includeHidden
|
||||
)
|
||||
})
|
||||
section.visible =
|
||||
section.name === "General" ||
|
||||
|
@ -122,16 +129,20 @@
|
|||
})
|
||||
}
|
||||
|
||||
const canRenderControl = (instance, setting, isScreen) => {
|
||||
const canRenderControl = (instance, setting, isScreen, includeHidden) => {
|
||||
// Prevent rendering on click setting for screens
|
||||
if (setting?.type === "event" && isScreen) {
|
||||
return false
|
||||
}
|
||||
// Check we have a component to render for this setting
|
||||
const control = getComponentForSetting(setting)
|
||||
if (!control) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if setting is hidden
|
||||
if (setting.hidden && !includeHidden) {
|
||||
return false
|
||||
}
|
||||
return shouldDisplay(instance, setting)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2589,6 +2589,17 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Initial form step",
|
||||
|
@ -2738,6 +2749,17 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "validation/string",
|
||||
"label": "Validation",
|
||||
|
@ -2776,6 +2798,35 @@
|
|||
"barTitle": "Justify text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2829,10 +2880,50 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "validation/number",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2885,6 +2976,35 @@
|
|||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2942,6 +3062,35 @@
|
|||
"type": "validation/string",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3049,6 +3198,17 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Options source",
|
||||
|
@ -3110,6 +3270,35 @@
|
|||
"type": "validation/string",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3174,6 +3363,17 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Type",
|
||||
|
@ -3272,6 +3472,35 @@
|
|||
"type": "validation/array",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3348,10 +3577,50 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "validation/boolean",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3427,10 +3696,50 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "validation/string",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3508,10 +3817,50 @@
|
|||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "validation/datetime",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3598,6 +3947,22 @@
|
|||
"value": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Preferred camera",
|
||||
"key": "preferredCamera",
|
||||
"defaultValue": "environment",
|
||||
"options": [
|
||||
{
|
||||
"label": "Front",
|
||||
"value": "user"
|
||||
},
|
||||
{
|
||||
"label": "Back",
|
||||
"value": "environment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"label": "On change",
|
||||
|
@ -3613,6 +3978,35 @@
|
|||
"type": "validation/string",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3781,7 +4175,7 @@
|
|||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Disabled",
|
||||
"label": "Read only",
|
||||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
|
@ -3789,6 +4183,35 @@
|
|||
"type": "validation/attachment",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3857,6 +4280,46 @@
|
|||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3909,6 +4372,46 @@
|
|||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Read only",
|
||||
"key": "readonly",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "disabled",
|
||||
"value": true,
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -5530,12 +6033,7 @@
|
|||
"type": "boolean",
|
||||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "actionType",
|
||||
"value": "View",
|
||||
"invert": true
|
||||
}
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -5590,23 +6088,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "style",
|
||||
"type": "select",
|
||||
"label": "Align labels",
|
||||
"key": "labelPosition",
|
||||
"defaultValue": "left",
|
||||
"options": [
|
||||
{
|
||||
"label": "Left",
|
||||
"value": "left"
|
||||
},
|
||||
{
|
||||
"label": "Above",
|
||||
"value": "above"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "style",
|
||||
"type": "select",
|
||||
|
@ -5921,6 +6402,35 @@
|
|||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Layout",
|
||||
"key": "span",
|
||||
"defaultValue": 6,
|
||||
"hidden": true,
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "1 column",
|
||||
"value": 6,
|
||||
"barIcon": "Stop",
|
||||
"barTitle": "1 column"
|
||||
},
|
||||
{
|
||||
"label": "2 columns",
|
||||
"value": 3,
|
||||
"barIcon": "ColumnTwoA",
|
||||
"barTitle": "2 columns"
|
||||
},
|
||||
{
|
||||
"label": "3 columns",
|
||||
"value": 2,
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "3 columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -26,15 +26,15 @@
|
|||
$: parentId = $component?.id
|
||||
$: inBuilder = $builderStore.inBuilder
|
||||
$: instance = {
|
||||
...props,
|
||||
_component: getComponent(type),
|
||||
_id: id,
|
||||
_instanceName: getInstanceName(name, type),
|
||||
_containsSlot: containsSlot,
|
||||
_styles: {
|
||||
...styles,
|
||||
normal: styles?.normal || {},
|
||||
},
|
||||
_containsSlot: containsSlot,
|
||||
...props,
|
||||
}
|
||||
|
||||
// Register this block component if we're inside the builder so it can be
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
interactive &&
|
||||
!isLayout &&
|
||||
!isRoot &&
|
||||
!isBlock &&
|
||||
definition?.draggable !== false
|
||||
$: droppable = interactive
|
||||
$: builderHidden =
|
||||
|
@ -194,6 +195,7 @@
|
|||
interactive,
|
||||
draggable,
|
||||
editable,
|
||||
isBlock,
|
||||
},
|
||||
empty: emptyState,
|
||||
selected,
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
export let size
|
||||
export let disabled
|
||||
export let fields
|
||||
export let labelPosition
|
||||
export let title
|
||||
export let description
|
||||
export let showDeleteButton
|
||||
|
@ -97,7 +96,6 @@
|
|||
size,
|
||||
disabled,
|
||||
fields: fieldsOrDefault,
|
||||
labelPosition,
|
||||
title,
|
||||
description,
|
||||
saveButtonLabel: saveLabel,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Placeholder from "components/app/Placeholder.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
export let dataSource
|
||||
export let actionUrl
|
||||
|
@ -9,7 +10,6 @@
|
|||
export let size
|
||||
export let disabled
|
||||
export let fields
|
||||
export let labelPosition
|
||||
export let title
|
||||
export let description
|
||||
export let saveButtonLabel
|
||||
|
@ -33,6 +33,7 @@
|
|||
barcodeqr: "codescanner",
|
||||
bb_reference: "bbreferencefield",
|
||||
}
|
||||
const context = getContext("context")
|
||||
|
||||
let formId
|
||||
|
||||
|
@ -136,7 +137,8 @@
|
|||
actionType: actionType === "Create" ? "Create" : "Update",
|
||||
dataSource,
|
||||
size,
|
||||
disabled: disabled || actionType === "View",
|
||||
disabled,
|
||||
readonly: !disabled && actionType === "View",
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
|
@ -226,16 +228,20 @@
|
|||
<BlockComponent type="text" props={{ text: description }} order={1} />
|
||||
{/if}
|
||||
{#key fields}
|
||||
<BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}>
|
||||
<BlockComponent type="container">
|
||||
<div class="form-block fields" class:mobile={$context.device.mobile}>
|
||||
{#each fields as field, idx}
|
||||
{#if getComponentForField(field) && field.active}
|
||||
<BlockComponent
|
||||
type={getComponentForField(field)}
|
||||
props={getPropsForField(field)}
|
||||
order={idx}
|
||||
interactive
|
||||
name={field?.field}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</BlockComponent>
|
||||
{/key}
|
||||
</BlockComponent>
|
||||
|
@ -245,3 +251,14 @@
|
|||
text="Choose your table and add some fields to your form to get started"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.fields {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 8px 16px;
|
||||
}
|
||||
.fields.mobile :global(.spectrum-Form-item) {
|
||||
grid-column: span 6 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
export let field
|
||||
export let label
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let compact = false
|
||||
export let validation
|
||||
export let extensions
|
||||
export let onChange
|
||||
export let maximum = undefined
|
||||
export let span
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -71,17 +73,18 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{span}
|
||||
type="attachment"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
defaultValue={[]}
|
||||
>
|
||||
<div class="minHeightWrapper">
|
||||
{#if fieldState}
|
||||
<CoreDropzone
|
||||
value={fieldState.value}
|
||||
disabled={fieldState.disabled}
|
||||
disabled={fieldState.disabled || fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
on:change={handleChange}
|
||||
{processFiles}
|
||||
|
@ -93,11 +96,4 @@
|
|||
{compact}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<style>
|
||||
.minHeightWrapper {
|
||||
min-height: 80px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let label
|
||||
export let text
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let size
|
||||
export let validation
|
||||
export let defaultValue
|
||||
|
@ -39,6 +40,7 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
defaultValue={isTruthy(defaultValue)}
|
||||
type="boolean"
|
||||
|
@ -49,6 +51,7 @@
|
|||
<CoreCheckbox
|
||||
value={fieldState.value}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
id={fieldState.fieldId}
|
||||
{size}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let beepOnScan = false
|
||||
export let beepFrequency = 2637
|
||||
export let customFrequency = 1046
|
||||
export let preferredCamera = "environment"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -20,7 +21,7 @@
|
|||
let cameraEnabled
|
||||
let cameraStarted = false
|
||||
let html5QrCode
|
||||
let cameraSetting = { facingMode: "environment" }
|
||||
let cameraSetting = { facingMode: preferredCamera }
|
||||
let cameraConfig = {
|
||||
fps: 25,
|
||||
qrbox: { width: 250, height: 250 },
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let label
|
||||
export let type = "barcodeqr"
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let validation
|
||||
export let defaultValue = ""
|
||||
export let onChange
|
||||
|
@ -14,6 +15,7 @@
|
|||
export let beepOnScan
|
||||
export let beepFrequency
|
||||
export let customFrequency
|
||||
export let preferredCamera
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -32,6 +34,7 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{defaultValue}
|
||||
{type}
|
||||
|
@ -42,12 +45,13 @@
|
|||
<CodeScanner
|
||||
value={fieldState.value}
|
||||
on:change={handleUpdate}
|
||||
disabled={fieldState.disabled}
|
||||
disabled={fieldState.disabled || fieldState.readonly}
|
||||
{allowManualEntry}
|
||||
scanButtonText={scanText}
|
||||
{beepOnScan}
|
||||
{beepFrequency}
|
||||
{customFrequency}
|
||||
{preferredCamera}
|
||||
/>
|
||||
{/if}
|
||||
</Field>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let label
|
||||
export let placeholder
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let enableTime = true
|
||||
export let timeOnly = false
|
||||
export let time24hr = false
|
||||
|
@ -13,6 +14,7 @@
|
|||
export let validation
|
||||
export let defaultValue
|
||||
export let onChange
|
||||
export let span
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -29,8 +31,10 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{defaultValue}
|
||||
{span}
|
||||
type="datetime"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
|
@ -40,6 +44,7 @@
|
|||
value={fieldState.value}
|
||||
on:change={handleChange}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
id={fieldState.fieldId}
|
||||
appendTo={document.getElementById("flatpickr-root")}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import Placeholder from "../Placeholder.svelte"
|
||||
import FieldGroupFallback from "./FieldGroupFallback.svelte"
|
||||
import { getContext, onDestroy } from "svelte"
|
||||
|
||||
export let label
|
||||
|
@ -11,7 +10,9 @@
|
|||
export let defaultValue
|
||||
export let type
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let validation
|
||||
export let span = 6
|
||||
|
||||
// Get contexts
|
||||
const formContext = getContext("form")
|
||||
|
@ -29,6 +30,7 @@
|
|||
type,
|
||||
defaultValue,
|
||||
disabled,
|
||||
readonly,
|
||||
validation,
|
||||
formStep
|
||||
)
|
||||
|
@ -62,14 +64,21 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<FieldGroupFallback>
|
||||
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
||||
<div
|
||||
class="spectrum-Form-item"
|
||||
class:span-2={span === 2}
|
||||
class:span-3={span === 3}
|
||||
class:span-6={span === 6 || !span}
|
||||
use:styleable={$component.styles}
|
||||
class:above={labelPos === "above"}
|
||||
>
|
||||
{#key $component.editing}
|
||||
<label
|
||||
bind:this={labelNode}
|
||||
contenteditable={$component.editing}
|
||||
on:blur={$component.editing ? updateLabel : null}
|
||||
class:hidden={!label}
|
||||
class:readonly
|
||||
for={fieldState?.fieldId}
|
||||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
|
||||
>
|
||||
|
@ -93,9 +102,21 @@
|
|||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</FieldGroupFallback>
|
||||
|
||||
<style>
|
||||
:global(.form-block .spectrum-Form-item.span-2) {
|
||||
grid-column: span 2;
|
||||
}
|
||||
:global(.form-block .spectrum-Form-item.span-3) {
|
||||
grid-column: span 3;
|
||||
}
|
||||
:global(.form-block .spectrum-Form-item.span-6) {
|
||||
grid-column: span 6;
|
||||
}
|
||||
.spectrum-Form-item.above {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -118,4 +139,7 @@
|
|||
.spectrum-FieldLabel--left {
|
||||
padding-right: var(--spectrum-global-dimension-size-200);
|
||||
}
|
||||
.readonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export let theme
|
||||
export let size
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let actionType = "Create"
|
||||
export let initialFormStep = 1
|
||||
|
||||
|
@ -39,7 +40,7 @@
|
|||
$: schemaKey = generateSchemaKey(schema)
|
||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||
$: resetKey = Helpers.hashString(
|
||||
schemaKey + JSON.stringify(initialValues) + disabled
|
||||
schemaKey + JSON.stringify(initialValues) + disabled + readonly
|
||||
)
|
||||
|
||||
// Returns the closes data context which isn't a built in context
|
||||
|
@ -97,6 +98,7 @@
|
|||
{theme}
|
||||
{size}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{actionType}
|
||||
{schema}
|
||||
{table}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
export let dataSource
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let initialValues
|
||||
export let size
|
||||
export let schema
|
||||
|
@ -148,6 +149,7 @@
|
|||
type,
|
||||
defaultValue = null,
|
||||
fieldDisabled = false,
|
||||
fieldReadOnly = false,
|
||||
validationRules,
|
||||
step = 1
|
||||
) => {
|
||||
|
@ -205,6 +207,7 @@
|
|||
error: initialError,
|
||||
disabled:
|
||||
disabled || fieldDisabled || (isAutoColumn && !editAutoColumns),
|
||||
readonly: readonly || fieldReadOnly,
|
||||
defaultValue,
|
||||
validator,
|
||||
lastUpdate: Date.now(),
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
export let label
|
||||
export let placeholder
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let defaultValue = ""
|
||||
export let onChange
|
||||
|
||||
|
@ -48,6 +49,7 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{defaultValue}
|
||||
type="json"
|
||||
|
@ -60,6 +62,7 @@
|
|||
value={serialiseValue(fieldState.value)}
|
||||
on:change={handleChange}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
id={fieldState.fieldId}
|
||||
{placeholder}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export let label
|
||||
export let placeholder
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let validation
|
||||
export let defaultValue = ""
|
||||
export let format = "auto"
|
||||
|
@ -58,6 +59,7 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{defaultValue}
|
||||
type="longform"
|
||||
|
@ -71,6 +73,7 @@
|
|||
value={fieldState.value}
|
||||
on:change={handleChange}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
id={fieldState.fieldId}
|
||||
{placeholder}
|
||||
|
@ -88,6 +91,7 @@
|
|||
value={fieldState.value}
|
||||
on:change={handleChange}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
id={fieldState.fieldId}
|
||||
{placeholder}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let label
|
||||
export let placeholder
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let validation
|
||||
export let defaultValue
|
||||
export let optionsSource = "schema"
|
||||
|
@ -17,6 +18,7 @@
|
|||
export let onChange
|
||||
export let optionsType = "select"
|
||||
export let direction = "vertical"
|
||||
export let span
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -55,7 +57,9 @@
|
|||
{field}
|
||||
{label}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{span}
|
||||
defaultValue={expandedDefaultValue}
|
||||
type="array"
|
||||
bind:fieldState
|
||||
|
@ -71,6 +75,7 @@
|
|||
getOptionValue={flatOptions ? x => x : x => x.value}
|
||||
id={fieldState.fieldId}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
on:change={handleChange}
|
||||
{placeholder}
|
||||
{options}
|
||||
|
@ -81,6 +86,7 @@
|
|||
value={fieldState.value || []}
|
||||
id={fieldState.fieldId}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
{options}
|
||||
{direction}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let label
|
||||
export let placeholder
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let optionsType = "select"
|
||||
export let validation
|
||||
export let defaultValue
|
||||
|
@ -18,6 +19,7 @@
|
|||
export let direction = "vertical"
|
||||
export let onChange
|
||||
export let sort = true
|
||||
export let span
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -45,8 +47,10 @@
|
|||
{field}
|
||||
{label}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{defaultValue}
|
||||
{span}
|
||||
type="options"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
|
@ -58,6 +62,7 @@
|
|||
value={fieldState.value}
|
||||
id={fieldState.fieldId}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
{options}
|
||||
{placeholder}
|
||||
|
@ -72,6 +77,7 @@
|
|||
value={fieldState.value}
|
||||
id={fieldState.fieldId}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
{options}
|
||||
{direction}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let label
|
||||
export let placeholder
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let validation
|
||||
export let autocomplete = true
|
||||
export let defaultValue
|
||||
|
@ -18,6 +19,7 @@
|
|||
export let filter
|
||||
export let datasourceType = "table"
|
||||
export let primaryDisplay
|
||||
export let span
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -137,7 +139,9 @@
|
|||
typeof value === "object" ? value._id : value
|
||||
)
|
||||
// Make sure field state is valid
|
||||
if (values?.length > 0) {
|
||||
fieldApi.setValue(values)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
|
@ -183,9 +187,11 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
defaultValue={expandedDefaultValue}
|
||||
{type}
|
||||
{span}
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
bind:fieldSchema
|
||||
|
@ -200,6 +206,7 @@
|
|||
on:loadMore={loadMore}
|
||||
id={fieldState.fieldId}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
getOptionLabel={getDisplayName}
|
||||
getOptionValue={option => option._id}
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
export let placeholder
|
||||
export let type = "text"
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let validation
|
||||
export let defaultValue = ""
|
||||
export let align
|
||||
export let onChange
|
||||
export let span
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -27,8 +29,10 @@
|
|||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{validation}
|
||||
{defaultValue}
|
||||
{span}
|
||||
type={type === "number" ? "number" : "string"}
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
|
@ -39,6 +43,7 @@
|
|||
value={fieldState.value}
|
||||
on:change={handleChange}
|
||||
disabled={fieldState.disabled}
|
||||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
id={fieldState.fieldId}
|
||||
{placeholder}
|
||||
|
|
|
@ -40,6 +40,7 @@ export const styleable = (node, styles = {}) => {
|
|||
|
||||
const componentId = newStyles.id
|
||||
const customStyles = newStyles.custom || ""
|
||||
const { isBlock } = newStyles
|
||||
const normalStyles = { ...baseStyles, ...newStyles.normal }
|
||||
const hoverStyles = {
|
||||
...normalStyles,
|
||||
|
@ -76,6 +77,9 @@ export const styleable = (node, styles = {}) => {
|
|||
// Handler to start editing a component (if applicable) when double
|
||||
// clicking in the builder preview
|
||||
editComponent = event => {
|
||||
if (isBlock) {
|
||||
return
|
||||
}
|
||||
if (newStyles.interactive && newStyles.editable) {
|
||||
builderStore.actions.setEditMode(true)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export default class UserFetch extends DataFetch {
|
|||
let finalQuery
|
||||
// convert old format to new one - we now allow use of the lucene format
|
||||
const { appId, paginated, ...rest } = query
|
||||
if (!LuceneUtils.hasFilters(query) && rest.email) {
|
||||
if (!LuceneUtils.hasFilters(query) && rest.email != null) {
|
||||
finalQuery = { string: { email: rest.email } }
|
||||
} else {
|
||||
finalQuery = rest
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bbbb7a1f9e4358ec8dc428f9a1034599c81f498d
|
||||
Subproject commit ac7785c832285255aee0b8f5309fed950f7b598c
|
|
@ -254,7 +254,7 @@ export const exportRows = async (
|
|||
|
||||
const format = ctx.query.format
|
||||
|
||||
const { rows, columns, query } = ctx.request.body
|
||||
const { rows, columns, query, sort, sortOrder } = ctx.request.body
|
||||
if (typeof format !== "string" || !exporters.isFormat(format)) {
|
||||
ctx.throw(
|
||||
400,
|
||||
|
@ -272,6 +272,8 @@ export const exportRows = async (
|
|||
rowIds: rows,
|
||||
columns,
|
||||
query,
|
||||
sort,
|
||||
sortOrder,
|
||||
})
|
||||
ctx.attachment(fileName)
|
||||
return apiFileReturn(content)
|
||||
|
|
|
@ -15,6 +15,16 @@ import env from "../../../environment"
|
|||
const Router = require("@koa/router")
|
||||
const { RateLimit, Stores } = require("koa2-ratelimit")
|
||||
import { middleware, redis } from "@budibase/backend-core"
|
||||
import { SelectableDatabase } from "@budibase/backend-core/src/redis/utils"
|
||||
|
||||
interface KoaRateLimitOptions {
|
||||
socket: {
|
||||
host: string
|
||||
port: number
|
||||
}
|
||||
password?: string
|
||||
database?: number
|
||||
}
|
||||
|
||||
const PREFIX = "/api/public/v1"
|
||||
// allow a lot more requests when in test
|
||||
|
@ -29,32 +39,21 @@ function getApiLimitPerSecond(): number {
|
|||
|
||||
let rateLimitStore: any = null
|
||||
if (!env.isTest()) {
|
||||
const REDIS_OPTS = redis.utils.getRedisOptions()
|
||||
let options
|
||||
if (REDIS_OPTS.redisProtocolUrl) {
|
||||
// fully qualified redis URL
|
||||
options = {
|
||||
url: REDIS_OPTS.redisProtocolUrl,
|
||||
}
|
||||
} else {
|
||||
options = {
|
||||
const { password, host, port } = redis.utils.getRedisConnectionDetails()
|
||||
let options: KoaRateLimitOptions = {
|
||||
socket: {
|
||||
host: REDIS_OPTS.host,
|
||||
port: REDIS_OPTS.port,
|
||||
host: host,
|
||||
port: port,
|
||||
},
|
||||
}
|
||||
|
||||
if (REDIS_OPTS.opts?.password || REDIS_OPTS.opts.redisOptions?.password) {
|
||||
// @ts-ignore
|
||||
options.password =
|
||||
REDIS_OPTS.opts.password || REDIS_OPTS.opts.redisOptions.password
|
||||
if (password) {
|
||||
options.password = password
|
||||
}
|
||||
|
||||
if (!env.REDIS_CLUSTERED) {
|
||||
// @ts-ignore
|
||||
// Can't set direct redis db in clustered env
|
||||
options.database = 1
|
||||
}
|
||||
options.database = SelectableDatabase.RATE_LIMITING
|
||||
}
|
||||
rateLimitStore = new Stores.Redis(options)
|
||||
RateLimit.defaultOptions({
|
||||
|
|
|
@ -563,6 +563,56 @@ describe.each([
|
|||
await assertRowUsage(rowUsage)
|
||||
await assertQueryUsage(queryUsage)
|
||||
})
|
||||
|
||||
it("should not overwrite links if those links are not set", async () => {
|
||||
let linkField: FieldSchema = {
|
||||
type: FieldType.LINK,
|
||||
name: "",
|
||||
fieldName: "",
|
||||
constraints: {
|
||||
type: "array",
|
||||
presence: false,
|
||||
},
|
||||
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||
tableId: InternalTable.USER_METADATA,
|
||||
}
|
||||
|
||||
let table = await config.api.table.create({
|
||||
name: "TestTable",
|
||||
type: "table",
|
||||
sourceType: TableSourceType.INTERNAL,
|
||||
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||
schema: {
|
||||
user1: { ...linkField, name: "user1", fieldName: "user1" },
|
||||
user2: { ...linkField, name: "user2", fieldName: "user2" },
|
||||
},
|
||||
})
|
||||
|
||||
let user1 = await config.createUser()
|
||||
let user2 = await config.createUser()
|
||||
|
||||
let row = await config.api.row.save(table._id!, {
|
||||
user1: [{ _id: user1._id }],
|
||||
user2: [{ _id: user2._id }],
|
||||
})
|
||||
|
||||
let getResp = await config.api.row.get(table._id!, row._id!)
|
||||
expect(getResp.body.user1[0]._id).toEqual(user1._id)
|
||||
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||
|
||||
let patchResp = await config.api.row.patch(table._id!, {
|
||||
_id: row._id!,
|
||||
_rev: row._rev!,
|
||||
tableId: table._id!,
|
||||
user1: [{ _id: user2._id }],
|
||||
})
|
||||
expect(patchResp.user1[0]._id).toEqual(user2._id)
|
||||
expect(patchResp.user2[0]._id).toEqual(user2._id)
|
||||
|
||||
getResp = await config.api.row.get(table._id!, row._id!)
|
||||
expect(getResp.body.user1[0]._id).toEqual(user2._id)
|
||||
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
|
|
|
@ -36,7 +36,7 @@ describe("Run through some parts of the automations system", () => {
|
|||
it("should be able to init in builder", async () => {
|
||||
const automation: Automation = {
|
||||
...basicAutomation(),
|
||||
appId: config.appId,
|
||||
appId: config.appId!,
|
||||
}
|
||||
const fields: any = { a: 1, appId: config.appId }
|
||||
await triggers.externalTrigger(automation, fields)
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
const setup = require("./utilities")
|
||||
|
||||
describe("test the update row action", () => {
|
||||
let table, row, inputs
|
||||
let config = setup.getConfig()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.init()
|
||||
table = await config.createTable()
|
||||
row = await config.createRow()
|
||||
inputs = {
|
||||
rowId: row._id,
|
||||
row: {
|
||||
...row,
|
||||
name: "Updated name",
|
||||
// put a falsy option in to be removed
|
||||
description: "",
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
it("should be able to run the action", async () => {
|
||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs)
|
||||
expect(res.success).toEqual(true)
|
||||
const updatedRow = await config.getRow(table._id, res.id)
|
||||
expect(updatedRow.name).toEqual("Updated name")
|
||||
expect(updatedRow.description).not.toEqual("")
|
||||
})
|
||||
|
||||
it("should check invalid inputs return an error", async () => {
|
||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {})
|
||||
expect(res.success).toEqual(false)
|
||||
})
|
||||
|
||||
it("should return an error when table doesn't exist", async () => {
|
||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
||||
row: { _id: "invalid" },
|
||||
rowId: "invalid",
|
||||
})
|
||||
expect(res.success).toEqual(false)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,169 @@
|
|||
import {
|
||||
FieldSchema,
|
||||
FieldType,
|
||||
INTERNAL_TABLE_SOURCE_ID,
|
||||
InternalTable,
|
||||
RelationshipType,
|
||||
Row,
|
||||
Table,
|
||||
TableSourceType,
|
||||
} from "@budibase/types"
|
||||
|
||||
import * as setup from "./utilities"
|
||||
import * as uuid from "uuid"
|
||||
|
||||
describe("test the update row action", () => {
|
||||
let table: Table, row: Row, inputs: any
|
||||
let config = setup.getConfig()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.init()
|
||||
table = await config.createTable()
|
||||
row = await config.createRow()
|
||||
inputs = {
|
||||
rowId: row._id,
|
||||
row: {
|
||||
...row,
|
||||
name: "Updated name",
|
||||
// put a falsy option in to be removed
|
||||
description: "",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
it("should be able to run the action", async () => {
|
||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs)
|
||||
expect(res.success).toEqual(true)
|
||||
const updatedRow = await config.getRow(table._id!, res.id)
|
||||
expect(updatedRow.name).toEqual("Updated name")
|
||||
expect(updatedRow.description).not.toEqual("")
|
||||
})
|
||||
|
||||
it("should check invalid inputs return an error", async () => {
|
||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {})
|
||||
expect(res.success).toEqual(false)
|
||||
})
|
||||
|
||||
it("should return an error when table doesn't exist", async () => {
|
||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
||||
row: { _id: "invalid" },
|
||||
rowId: "invalid",
|
||||
})
|
||||
expect(res.success).toEqual(false)
|
||||
})
|
||||
|
||||
it("should not overwrite links if those links are not set", async () => {
|
||||
let linkField: FieldSchema = {
|
||||
type: FieldType.LINK,
|
||||
name: "",
|
||||
fieldName: "",
|
||||
constraints: {
|
||||
type: "array",
|
||||
presence: false,
|
||||
},
|
||||
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||
tableId: InternalTable.USER_METADATA,
|
||||
}
|
||||
|
||||
let table = await config.api.table.create({
|
||||
name: uuid.v4(),
|
||||
type: "table",
|
||||
sourceType: TableSourceType.INTERNAL,
|
||||
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||
schema: {
|
||||
user1: { ...linkField, name: "user1", fieldName: uuid.v4() },
|
||||
user2: { ...linkField, name: "user2", fieldName: uuid.v4() },
|
||||
},
|
||||
})
|
||||
|
||||
let user1 = await config.createUser()
|
||||
let user2 = await config.createUser()
|
||||
|
||||
let row = await config.api.row.save(table._id!, {
|
||||
user1: [{ _id: user1._id }],
|
||||
user2: [{ _id: user2._id }],
|
||||
})
|
||||
|
||||
let getResp = await config.api.row.get(table._id!, row._id!)
|
||||
expect(getResp.body.user1[0]._id).toEqual(user1._id)
|
||||
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||
|
||||
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
||||
rowId: row._id,
|
||||
row: {
|
||||
_id: row._id,
|
||||
_rev: row._rev,
|
||||
tableId: row.tableId,
|
||||
user1: [user2._id],
|
||||
user2: "",
|
||||
},
|
||||
})
|
||||
expect(stepResp.success).toEqual(true)
|
||||
|
||||
getResp = await config.api.row.get(table._id!, row._id!)
|
||||
expect(getResp.body.user1[0]._id).toEqual(user2._id)
|
||||
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||
})
|
||||
|
||||
it("should overwrite links if those links are not set and we ask it do", async () => {
|
||||
let linkField: FieldSchema = {
|
||||
type: FieldType.LINK,
|
||||
name: "",
|
||||
fieldName: "",
|
||||
constraints: {
|
||||
type: "array",
|
||||
presence: false,
|
||||
},
|
||||
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||
tableId: InternalTable.USER_METADATA,
|
||||
}
|
||||
|
||||
let table = await config.api.table.create({
|
||||
name: uuid.v4(),
|
||||
type: "table",
|
||||
sourceType: TableSourceType.INTERNAL,
|
||||
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||
schema: {
|
||||
user1: { ...linkField, name: "user1", fieldName: uuid.v4() },
|
||||
user2: { ...linkField, name: "user2", fieldName: uuid.v4() },
|
||||
},
|
||||
})
|
||||
|
||||
let user1 = await config.createUser()
|
||||
let user2 = await config.createUser()
|
||||
|
||||
let row = await config.api.row.save(table._id!, {
|
||||
user1: [{ _id: user1._id }],
|
||||
user2: [{ _id: user2._id }],
|
||||
})
|
||||
|
||||
let getResp = await config.api.row.get(table._id!, row._id!)
|
||||
expect(getResp.body.user1[0]._id).toEqual(user1._id)
|
||||
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||
|
||||
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
||||
rowId: row._id,
|
||||
row: {
|
||||
_id: row._id,
|
||||
_rev: row._rev,
|
||||
tableId: row.tableId,
|
||||
user1: [user2._id],
|
||||
user2: "",
|
||||
},
|
||||
meta: {
|
||||
fields: {
|
||||
user2: {
|
||||
clearRelationships: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(stepResp.success).toEqual(true)
|
||||
|
||||
getResp = await config.api.row.get(table._id!, row._id!)
|
||||
expect(getResp.body.user1[0]._id).toEqual(user2._id)
|
||||
expect(getResp.body.user2).toBeUndefined()
|
||||
})
|
||||
})
|
|
@ -4,11 +4,11 @@ import { BUILTIN_ACTION_DEFINITIONS, getAction } from "../../actions"
|
|||
import emitter from "../../../events/index"
|
||||
import env from "../../../environment"
|
||||
|
||||
let config: any
|
||||
let config: TestConfig
|
||||
|
||||
export function getConfig() {
|
||||
export function getConfig(): TestConfig {
|
||||
if (!config) {
|
||||
config = new TestConfig(false)
|
||||
config = new TestConfig(true)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { Row, SearchFilters, SearchParams } from "@budibase/types"
|
||||
import {
|
||||
Row,
|
||||
SearchFilters,
|
||||
SearchParams,
|
||||
SortOrder,
|
||||
SortType,
|
||||
} from "@budibase/types"
|
||||
import { isExternalTableID } from "../../../integrations/utils"
|
||||
import * as internal from "./search/internal"
|
||||
import * as external from "./search/external"
|
||||
|
@ -32,6 +38,8 @@ export interface ExportRowsParams {
|
|||
rowIds?: string[]
|
||||
columns?: string[]
|
||||
query?: SearchFilters
|
||||
sort?: string
|
||||
sortOrder?: SortOrder
|
||||
}
|
||||
|
||||
export interface ExportRowsResult {
|
||||
|
|
|
@ -98,12 +98,12 @@ export async function search(options: SearchParams) {
|
|||
export async function exportRows(
|
||||
options: ExportRowsParams
|
||||
): Promise<ExportRowsResult> {
|
||||
const { tableId, format, columns, rowIds } = options
|
||||
const { tableId, format, columns, rowIds, query, sort, sortOrder } = options
|
||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||
|
||||
let query: SearchFilters = {}
|
||||
let requestQuery: SearchFilters = {}
|
||||
if (rowIds?.length) {
|
||||
query = {
|
||||
requestQuery = {
|
||||
oneOf: {
|
||||
_id: rowIds.map((row: string) => {
|
||||
const ids = JSON.parse(
|
||||
|
@ -119,6 +119,8 @@ export async function exportRows(
|
|||
}),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
requestQuery = query || {}
|
||||
}
|
||||
|
||||
const datasource = await sdk.datasources.get(datasourceId!)
|
||||
|
@ -126,7 +128,7 @@ export async function exportRows(
|
|||
throw new HTTPError("Datasource has not been configured for plus API.", 400)
|
||||
}
|
||||
|
||||
let result = await search({ tableId, query })
|
||||
let result = await search({ tableId, query: requestQuery, sort, sortOrder })
|
||||
let rows: Row[] = []
|
||||
|
||||
// Filter data to only specified columns if required
|
||||
|
|
|
@ -84,7 +84,7 @@ export async function search(options: SearchParams) {
|
|||
export async function exportRows(
|
||||
options: ExportRowsParams
|
||||
): Promise<ExportRowsResult> {
|
||||
const { tableId, format, rowIds, columns, query } = options
|
||||
const { tableId, format, rowIds, columns, query, sort, sortOrder } = options
|
||||
const db = context.getAppDB()
|
||||
const table = await sdk.tables.getTable(tableId)
|
||||
|
||||
|
@ -99,7 +99,12 @@ export async function exportRows(
|
|||
|
||||
result = await outputProcessing(table, response)
|
||||
} else if (query) {
|
||||
let searchResponse = await search({ tableId, query })
|
||||
let searchResponse = await search({
|
||||
tableId,
|
||||
query,
|
||||
sort,
|
||||
sortOrder,
|
||||
})
|
||||
result = searchResponse.rows
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,15 @@ export class TableAPI extends TestAPI {
|
|||
.send(data)
|
||||
.set(this.config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(expectStatus)
|
||||
|
||||
if (res.status !== expectStatus) {
|
||||
throw new Error(
|
||||
`Expected status ${expectStatus} but got ${
|
||||
res.status
|
||||
} with body ${JSON.stringify(res.body)}`
|
||||
)
|
||||
}
|
||||
|
||||
return res.body
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { SearchFilters, SearchParams } from "../../../sdk"
|
||||
import { Row } from "../../../documents"
|
||||
import { SortOrder } from "../../../api"
|
||||
import { ReadStream } from "fs"
|
||||
|
||||
export interface SaveRowRequest extends Row {}
|
||||
|
@ -34,6 +35,8 @@ export interface ExportRowsRequest {
|
|||
rows: string[]
|
||||
columns?: string[]
|
||||
query?: SearchFilters
|
||||
sort?: string
|
||||
sortOrder?: SortOrder
|
||||
}
|
||||
|
||||
export type ExportRowsResponse = ReadStream
|
||||
|
|
|
@ -102,6 +102,7 @@ export interface BBReferenceFieldMetadata
|
|||
extends Omit<BaseFieldSchema, "subtype"> {
|
||||
type: FieldType.BB_REFERENCE
|
||||
subtype: FieldSubtype.USER | FieldSubtype.USERS
|
||||
relationshipType?: RelationshipType
|
||||
}
|
||||
|
||||
export interface FieldConstraints {
|
||||
|
|
|
@ -31,10 +31,6 @@ import destroyable from "server-destroy"
|
|||
import { initPro } from "./initPro"
|
||||
import { handleScimBody } from "./middleware/handleScimBody"
|
||||
|
||||
// configure events to use the pro audit log write
|
||||
// can't integrate directly into backend-core due to cyclic issues
|
||||
events.processors.init(proSdk.auditLogs.write)
|
||||
|
||||
if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE) {
|
||||
console.warn(
|
||||
"Warning: ENABLE_SSO_MAINTENANCE_MODE is set. It is recommended this flag is disabled if maintenance is not in progress"
|
||||
|
@ -93,6 +89,9 @@ export default server.listen(parseInt(env.PORT || "4002"), async () => {
|
|||
console.log(`Worker running on ${JSON.stringify(server.address())}`)
|
||||
await initPro()
|
||||
await redis.init()
|
||||
// configure events to use the pro audit log write
|
||||
// can't integrate directly into backend-core due to cyclic issues
|
||||
await events.processors.init(proSdk.auditLogs.write)
|
||||
})
|
||||
|
||||
process.on("uncaughtException", err => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
## Description
|
||||
_Describe the problem or feature in addition to a link to the relevant github issues._
|
||||
|
||||
### Addresses:
|
||||
## Addresses
|
||||
- `<Enter the Link to the issue(s) this PR addresses>`
|
||||
- ...more if required
|
||||
|
||||
|
|
Loading…
Reference in New Issue