Merge branch 'develop' of github.com:Budibase/budibase into feature/test-image
This commit is contained in:
commit
e8667fd04d
|
@ -4,6 +4,9 @@ metadata:
|
|||
annotations:
|
||||
kompose.cmd: kompose convert
|
||||
kompose.version: 1.21.0 (992df58d8)
|
||||
{{ if .Values.globals.logAnnotations }}
|
||||
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
|
||||
{{ end }}
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
io.kompose.service: app-service
|
||||
|
@ -156,8 +159,24 @@ spec:
|
|||
- name: ELASTIC_APM_SERVER_URL
|
||||
value: {{ .Values.globals.elasticApmServerUrl | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.globals.globalAgentHttpProxy }}
|
||||
- name: GLOBAL_AGENT_HTTP_PROXY
|
||||
value: {{ .Values.globals.globalAgentHttpProxy | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.globals.globalAgentHttpsProxy }}
|
||||
- name: GLOBAL_AGENT_HTTPS_PROXY
|
||||
value: {{ .Values.globals.globalAgentHttpsProxy | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.globals.globalAgentNoProxy }}
|
||||
- name: GLOBAL_AGENT_NO_PROXY
|
||||
value: {{ .Values.globals.globalAgentNoProxy | quote }}
|
||||
{{ end }}
|
||||
- name: CDN_URL
|
||||
value: {{ .Values.globals.cdnUrl }}
|
||||
{{ if .Values.services.tlsRejectUnauthorized }}
|
||||
- name: NODE_TLS_REJECT_UNAUTHORIZED
|
||||
value: {{ .Values.services.tlsRejectUnauthorized }}
|
||||
{{ end }}
|
||||
|
||||
image: budibase/apps:{{ .Values.globals.appVersion }}
|
||||
imagePullPolicy: Always
|
||||
|
|
|
@ -42,6 +42,7 @@ spec:
|
|||
secretKeyRef:
|
||||
name: {{ template "budibase.fullname" . }}
|
||||
key: objectStoreSecret
|
||||
|
||||
image: minio/minio
|
||||
imagePullPolicy: ""
|
||||
livenessProbe:
|
||||
|
|
|
@ -4,6 +4,9 @@ metadata:
|
|||
annotations:
|
||||
kompose.cmd: kompose convert
|
||||
kompose.version: 1.21.0 (992df58d8)
|
||||
{{ if .Values.globals.logAnnotations }}
|
||||
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
|
||||
{{ end }}
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app.kubernetes.io/name: budibase-proxy
|
||||
|
|
|
@ -60,5 +60,6 @@ spec:
|
|||
- name: redis-data
|
||||
persistentVolumeClaim:
|
||||
claimName: redis-data
|
||||
|
||||
status: {}
|
||||
{{- end }}
|
||||
|
|
|
@ -4,6 +4,9 @@ metadata:
|
|||
annotations:
|
||||
kompose.cmd: kompose convert
|
||||
kompose.version: 1.21.0 (992df58d8)
|
||||
{{ if .Values.globals.logAnnotations }}
|
||||
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
|
||||
{{ end }}
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
io.kompose.service: worker-service
|
||||
|
@ -147,8 +150,24 @@ spec:
|
|||
- name: ELASTIC_APM_SERVER_URL
|
||||
value: {{ .Values.globals.elasticApmServerUrl | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.globals.globalAgentHttpProxy }}
|
||||
- name: GLOBAL_AGENT_HTTP_PROXY
|
||||
value: {{ .Values.globals.globalAgentHttpProxy | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.globals.globalAgentHttpsProxy }}
|
||||
- name: GLOBAL_AGENT_HTTPS_PROXY
|
||||
value: {{ .Values.globals.globalAgentHttpsProxy | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.globals.globalAgentNoProxy }}
|
||||
- name: GLOBAL_AGENT_NO_PROXY
|
||||
value: {{ .Values.globals.globalAgentNoProxy | quote }}
|
||||
{{ end }}
|
||||
- name: CDN_URL
|
||||
value: {{ .Values.globals.cdnUrl }}
|
||||
{{ if .Values.services.tlsRejectUnauthorized }}
|
||||
- name: NODE_TLS_REJECT_UNAUTHORIZED
|
||||
value: {{ .Values.services.tlsRejectUnauthorized }}
|
||||
{{ end }}
|
||||
|
||||
image: budibase/worker:{{ .Values.globals.appVersion }}
|
||||
imagePullPolicy: Always
|
||||
|
|
|
@ -22,6 +22,12 @@ serviceAccount:
|
|||
|
||||
podAnnotations: {}
|
||||
|
||||
# logAnnotations:
|
||||
# co.elastic.logs/multiline.type: pattern
|
||||
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
|
||||
# co.elastic.logs/multiline.negate: false
|
||||
# co.elastic.logs/multiline.match: after
|
||||
|
||||
podSecurityContext:
|
||||
{}
|
||||
# fsGroup: 2000
|
||||
|
@ -106,10 +112,14 @@ globals:
|
|||
# elasticApmEnabled:
|
||||
# elasticApmSecretToken:
|
||||
# elasticApmServerUrl:
|
||||
# globalAgentHttpProxy:
|
||||
# globalAgentHttpsProxy:
|
||||
# globalAgentNoProxy:
|
||||
|
||||
services:
|
||||
budibaseVersion: latest
|
||||
dns: cluster.local
|
||||
# tlsRejectUnauthorized: 0
|
||||
|
||||
proxy:
|
||||
port: 10000
|
||||
|
|
|
@ -47,7 +47,8 @@ ADD hosting/single/nginx/nginx.conf /etc/nginx
|
|||
ADD hosting/single/nginx/nginx-default-site.conf /etc/nginx/sites-enabled/default
|
||||
RUN mkdir -p /var/log/nginx && \
|
||||
touch /var/log/nginx/error.log && \
|
||||
touch /var/run/nginx.pid
|
||||
touch /var/run/nginx.pid && \
|
||||
usermod -a -G tty www-data
|
||||
|
||||
WORKDIR /
|
||||
RUN mkdir -p scripts/integrations/oracle
|
||||
|
|
|
@ -2,7 +2,8 @@ server {
|
|||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
|
||||
error_log /dev/stderr warn;
|
||||
access_log /dev/stdout main;
|
||||
client_max_body_size 1000m;
|
||||
ignore_invalid_headers off;
|
||||
proxy_buffering off;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
user www-data www-data;
|
||||
error_log /var/log/nginx/error.log;
|
||||
error_log /dev/stderr warn;
|
||||
pid /var/run/nginx.pid;
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 8192;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/backend-core",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"description": "Budibase backend core libraries used in server and worker",
|
||||
"main": "dist/src/index.js",
|
||||
"types": "dist/src/index.d.ts",
|
||||
|
@ -16,11 +16,11 @@
|
|||
"prepack": "cp package.json dist",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||
"test": "jest --coverage",
|
||||
"test": "jest --coverage --maxWorkers=2",
|
||||
"test:watch": "jest --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/types": "2.1.40-alpha.7",
|
||||
"@budibase/types": "2.1.46-alpha.1",
|
||||
"@shopify/jest-koa-mocks": "5.0.1",
|
||||
"@techpass/passport-openidconnect": "0.3.2",
|
||||
"aws-sdk": "2.1030.0",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ssoCallbackUrl } from "./utils"
|
||||
import { authenticateThirdParty } from "./third-party-common"
|
||||
import { authenticateThirdParty, SaveUserFunction } from "./third-party-common"
|
||||
import { ConfigType, GoogleConfig, Database, SSOProfile } from "@budibase/types"
|
||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||
|
||||
export function buildVerifyFn(saveUserFn?: Function) {
|
||||
export function buildVerifyFn(saveUserFn?: SaveUserFunction) {
|
||||
return (
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
|
@ -39,7 +39,7 @@ export function buildVerifyFn(saveUserFn?: Function) {
|
|||
export async function strategyFactory(
|
||||
config: GoogleConfig["config"],
|
||||
callbackUrl: string,
|
||||
saveUserFn?: Function
|
||||
saveUserFn?: SaveUserFunction
|
||||
) {
|
||||
try {
|
||||
const { clientID, clientSecret } = config
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import fetch from "node-fetch"
|
||||
import { authenticateThirdParty } from "./third-party-common"
|
||||
import { authenticateThirdParty, SaveUserFunction } from "./third-party-common"
|
||||
import { ssoCallbackUrl } from "./utils"
|
||||
import {
|
||||
Config,
|
||||
|
@ -17,7 +17,7 @@ type JwtClaims = {
|
|||
email: string
|
||||
}
|
||||
|
||||
export function buildVerifyFn(saveUserFn?: Function) {
|
||||
export function buildVerifyFn(saveUserFn?: SaveUserFunction) {
|
||||
/**
|
||||
* @param {*} issuer The identity provider base URL
|
||||
* @param {*} sub The user ID
|
||||
|
@ -106,7 +106,7 @@ function validEmail(value: string) {
|
|||
*/
|
||||
export async function strategyFactory(
|
||||
config: OIDCConfiguration,
|
||||
saveUserFn?: Function
|
||||
saveUserFn?: SaveUserFunction
|
||||
) {
|
||||
try {
|
||||
const verify = buildVerifyFn(saveUserFn)
|
||||
|
|
|
@ -9,6 +9,17 @@ import fetch from "node-fetch"
|
|||
import { ThirdPartyUser } from "@budibase/types"
|
||||
const jwt = require("jsonwebtoken")
|
||||
|
||||
type SaveUserOpts = {
|
||||
requirePassword?: boolean
|
||||
hashPassword?: boolean
|
||||
currentUserId?: string
|
||||
}
|
||||
|
||||
export type SaveUserFunction = (
|
||||
user: ThirdPartyUser,
|
||||
opts: SaveUserOpts
|
||||
) => Promise<any>
|
||||
|
||||
/**
|
||||
* Common authentication logic for third parties. e.g. OAuth, OIDC.
|
||||
*/
|
||||
|
@ -16,7 +27,7 @@ export async function authenticateThirdParty(
|
|||
thirdPartyUser: ThirdPartyUser,
|
||||
requireLocalAccount: boolean = true,
|
||||
done: Function,
|
||||
saveUserFn?: Function
|
||||
saveUserFn?: SaveUserFunction
|
||||
) {
|
||||
if (!saveUserFn) {
|
||||
throw new Error("Save user function must be provided")
|
||||
|
@ -81,7 +92,7 @@ export async function authenticateThirdParty(
|
|||
|
||||
// create or sync the user
|
||||
try {
|
||||
await saveUserFn(dbUser, false, false)
|
||||
await saveUserFn(dbUser, { hashPassword: false, requirePassword: false })
|
||||
} catch (err: any) {
|
||||
return authError(done, err)
|
||||
}
|
||||
|
|
|
@ -1521,13 +1521,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
|
||||
|
||||
async@~2.1.4:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
|
||||
integrity sha512-+g/Ncjbx0JSq2Mk03WQkyKvNh5q9Qvyo/RIqIqnmC5feJY70PNl2ESwZU2BhAB+AZPkHNzzyC2Dq2AS5VnTKhQ==
|
||||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
|
@ -2669,32 +2662,6 @@ globals@^11.1.0:
|
|||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
||||
|
||||
google-auth-library@~0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e"
|
||||
integrity sha512-KM54Y9GhdAzfXUHmWEoYmaOykSLuMG7W4HvVLYqyogxOyE6px8oSS8W13ngqW0oDGZ915GFW3V6OM6+qcdvPOA==
|
||||
dependencies:
|
||||
gtoken "^1.2.1"
|
||||
jws "^3.1.4"
|
||||
lodash.noop "^3.0.1"
|
||||
request "^2.74.0"
|
||||
|
||||
google-p12-pem@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177"
|
||||
integrity sha512-puhMlJ2+E/rgvxWaqgN/nC7x623OAE8MR9vBUqxF0inCE7HoVfCHvTeQ9+BR+rj9KM0fIg6XV6tmbt7XHHssoQ==
|
||||
dependencies:
|
||||
node-forge "^0.7.1"
|
||||
|
||||
googleapis@^16.0.0:
|
||||
version "16.1.0"
|
||||
resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-16.1.0.tgz#0f19f2d70572d918881a0f626e3b1a2fa8629576"
|
||||
integrity sha512-5czmF7xkIlJKc1+/+5tltrI1skoR3HKtkDOld9rk+DOucTpZRjOhCoJzoSjxB3M8rP2tEb1VIr1TPyzR3V2PUQ==
|
||||
dependencies:
|
||||
async "~2.1.4"
|
||||
google-auth-library "~0.10.0"
|
||||
string-template "~1.0.0"
|
||||
|
||||
got@^9.6.0:
|
||||
version "9.6.0"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
|
||||
|
@ -2717,16 +2684,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.9:
|
|||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
gtoken@^1.2.1:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8"
|
||||
integrity sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w==
|
||||
dependencies:
|
||||
google-p12-pem "^0.1.0"
|
||||
jws "^3.0.0"
|
||||
mime "^1.4.1"
|
||||
request "^2.72.0"
|
||||
|
||||
har-schema@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
|
@ -3609,7 +3566,7 @@ jwa@^1.4.1:
|
|||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.0.0, jws@^3.1.4, jws@^3.2.2:
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
|
@ -3855,11 +3812,6 @@ lodash.memoize@4.x:
|
|||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
|
||||
|
||||
lodash.noop@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c"
|
||||
integrity sha512-TmYdmu/pebrdTIBDK/FDx9Bmfzs9x0sZG6QIJuMDTqEPfeciLcN13ij+cOd0i9vwJfBtbG9UQ+C7MkXgYxrIJg==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
|
@ -3870,7 +3822,7 @@ lodash.pick@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
||||
integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
|
||||
|
||||
lodash@4.17.21, lodash@^4.14.0, lodash@^4.17.21:
|
||||
lodash@4.17.21, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
@ -3982,7 +3934,7 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24,
|
|||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mime@^1.3.4, mime@^1.4.1:
|
||||
mime@^1.3.4:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
@ -4129,11 +4081,6 @@ node-fetch@2.6.7, node-fetch@^2.6.7:
|
|||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-forge@^0.7.1:
|
||||
version "0.7.6"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
|
||||
integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
|
||||
|
||||
node-gyp-build-optional-packages@5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17"
|
||||
|
@ -4356,14 +4303,6 @@ parseurl@^1.3.2, parseurl@^1.3.3:
|
|||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
passport-google-auth@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938"
|
||||
integrity sha512-cfAqna6jZLyMEwUdd4PIwAh2mQKQVEDAaRIaom1pG6h4x4Gwjllf/Jflt3TkR1Sen5Rkvr3l7kSXCWE1EKkh8g==
|
||||
dependencies:
|
||||
googleapis "^16.0.0"
|
||||
passport-strategy "1.x"
|
||||
|
||||
passport-google-oauth1@1.x.x:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc"
|
||||
|
@ -4426,7 +4365,7 @@ passport-oauth2@1.x.x:
|
|||
uid2 "0.0.x"
|
||||
utils-merge "1.x.x"
|
||||
|
||||
passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0:
|
||||
passport-strategy@1.x.x, passport-strategy@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
|
||||
integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
|
||||
|
@ -4938,7 +4877,7 @@ remove-trailing-slash@^0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d"
|
||||
integrity sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==
|
||||
|
||||
request@^2.72.0, request@^2.74.0, request@^2.88.0:
|
||||
request@^2.88.0:
|
||||
version "2.88.2"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
|
||||
|
@ -5204,11 +5143,6 @@ string-length@^4.0.1:
|
|||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
string-template@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
|
||||
integrity sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg==
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
@ -38,7 +38,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||
"@budibase/string-templates": "2.1.40-alpha.7",
|
||||
"@budibase/string-templates": "2.1.46-alpha.1",
|
||||
"@spectrum-css/actionbutton": "^1.0.1",
|
||||
"@spectrum-css/actiongroup": "^1.0.1",
|
||||
"@spectrum-css/avatar": "^3.0.2",
|
||||
|
@ -67,19 +67,19 @@
|
|||
"@spectrum-css/search": "^3.0.2",
|
||||
"@spectrum-css/sidenav": "^3.0.2",
|
||||
"@spectrum-css/slider": "3.0.1",
|
||||
"@spectrum-css/statuslight": "^3.0.2",
|
||||
"@spectrum-css/stepper": "^3.0.3",
|
||||
"@spectrum-css/switch": "^1.0.2",
|
||||
"@spectrum-css/table": "^3.0.1",
|
||||
"@spectrum-css/tabs": "^3.2.12",
|
||||
"@spectrum-css/tags": "^3.0.2",
|
||||
"@spectrum-css/textfield": "^3.0.1",
|
||||
"@spectrum-css/toast": "^3.0.1",
|
||||
"@spectrum-css/tooltip": "^3.0.3",
|
||||
"@spectrum-css/treeview": "^3.0.2",
|
||||
"@spectrum-css/typography": "^3.0.1",
|
||||
"@spectrum-css/underlay": "^2.0.9",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
"@spectrum-css/statuslight": "3.0.2",
|
||||
"@spectrum-css/stepper": "3.0.3",
|
||||
"@spectrum-css/switch": "1.0.2",
|
||||
"@spectrum-css/table": "3.0.1",
|
||||
"@spectrum-css/tabs": "3.2.12",
|
||||
"@spectrum-css/tags": "3.0.2",
|
||||
"@spectrum-css/textfield": "3.0.1",
|
||||
"@spectrum-css/toast": "3.0.1",
|
||||
"@spectrum-css/tooltip": "3.0.3",
|
||||
"@spectrum-css/treeview": "3.0.2",
|
||||
"@spectrum-css/typography": "3.0.1",
|
||||
"@spectrum-css/underlay": "2.0.9",
|
||||
"@spectrum-css/vars": "3.0.1",
|
||||
"dayjs": "^1.10.4",
|
||||
"easymde": "^2.16.1",
|
||||
"svelte-flatpickr": "^3.2.3",
|
||||
|
|
|
@ -205,7 +205,10 @@
|
|||
width: 100%;
|
||||
}
|
||||
.spectrum-Popover.auto-width :global(.spectrum-Menu-itemLabel) {
|
||||
max-width: 400px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.spectrum-Picker {
|
||||
width: 100%;
|
||||
|
|
|
@ -28,9 +28,9 @@
|
|||
let loading = false
|
||||
$: confirmDisabled = disabled || loading
|
||||
|
||||
async function secondary() {
|
||||
async function secondary(e) {
|
||||
loading = true
|
||||
if (!secondaryAction || (await secondaryAction()) !== false) {
|
||||
if (!secondaryAction || (await secondaryAction(e)) !== false) {
|
||||
hide()
|
||||
}
|
||||
loading = false
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
background-color: var(--spectrum-global-color-gray-300) !important;
|
||||
background-color: var(--spectrum-global-color-gray-200) !important;
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
@ -31,8 +31,8 @@
|
|||
background-image: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0) 0,
|
||||
rgba(255, 255, 255, 0.2) 20%,
|
||||
rgba(255, 255, 255, 0.5) 60%,
|
||||
rgba(255, 255, 255, 0.15) 20%,
|
||||
rgba(255, 255, 255, 0.3) 60%,
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
|
@ -44,7 +44,7 @@
|
|||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,20 +2,20 @@ import filterTests from "../../support/filterTests"
|
|||
const interact = require('../../support/interact')
|
||||
|
||||
filterTests(["smoke", "all"], () => {
|
||||
context("User Management", () => {
|
||||
xcontext("User Management", () => {
|
||||
before(() => {
|
||||
cy.login()
|
||||
cy.deleteApp("Cypress Tests")
|
||||
cy.createApp("Cypress Tests", false)
|
||||
})
|
||||
|
||||
it("should create a user via basic onboarding", () => {
|
||||
xit("should create a user via basic onboarding", () => {
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
|
||||
cy.createUser("bbuser@test.com")
|
||||
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser")
|
||||
})
|
||||
|
||||
it("should confirm App User role for a New User", () => {
|
||||
xit("should confirm App User role for a New User", () => {
|
||||
cy.contains("bbuser").click()
|
||||
cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
|
||||
|
||||
|
@ -166,7 +166,7 @@ filterTests(["smoke", "all"], () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("Should edit user details within user details page", () => {
|
||||
xit("Should edit user details within user details page", () => {
|
||||
// Add First name
|
||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
||||
cy.wait(500)
|
||||
|
@ -190,7 +190,7 @@ filterTests(["smoke", "all"], () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should reset the users password", () => {
|
||||
xit("should reset the users password", () => {
|
||||
cy.get(".title").within(() => {
|
||||
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
||||
})
|
||||
|
@ -230,7 +230,7 @@ filterTests(["smoke", "all"], () => {
|
|||
cy.login()
|
||||
})
|
||||
|
||||
it("should delete a user", () => {
|
||||
xit("should delete a user", () => {
|
||||
cy.deleteUser("bbuser@test.com")
|
||||
cy.get(interact.SPECTRUM_TABLE, { timeout: 4000 }).should("not.have.text", "bbuser")
|
||||
})
|
||||
|
|
|
@ -140,7 +140,8 @@ filterTests(["all"], () => {
|
|||
})
|
||||
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||
cy.get(".appTable .app-row-actions button")
|
||||
cy.wait(1000)
|
||||
cy.get(".appTable .app-row-actions button", { timeout: 10000 })
|
||||
.contains("Manage")
|
||||
.eq(0)
|
||||
.click({ force: true })
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -71,10 +71,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.1.40-alpha.7",
|
||||
"@budibase/client": "2.1.40-alpha.7",
|
||||
"@budibase/frontend-core": "2.1.40-alpha.7",
|
||||
"@budibase/string-templates": "2.1.40-alpha.7",
|
||||
"@budibase/bbui": "2.1.46-alpha.1",
|
||||
"@budibase/client": "2.1.46-alpha.1",
|
||||
"@budibase/frontend-core": "2.1.46-alpha.1",
|
||||
"@budibase/string-templates": "2.1.46-alpha.1",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -481,6 +481,7 @@ const getSelectedRowsBindings = asset => {
|
|||
block._id + "-table"
|
||||
)}.${makePropSafe("selectedRows")}`,
|
||||
readableBinding: `${block._instanceName}.Selected rows`,
|
||||
category: "Selected rows",
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
@ -1004,7 +1005,10 @@ const bindingReplacement = (
|
|||
* {{ literal [componentId] }}
|
||||
*/
|
||||
const extractLiteralHandlebarsID = value => {
|
||||
return value?.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
|
||||
if (!value || typeof value !== "string") {
|
||||
return null
|
||||
}
|
||||
return value.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
DB_TYPE_INTERNAL,
|
||||
DB_TYPE_EXTERNAL,
|
||||
} from "constants/backend"
|
||||
import { getSchemaForDatasource } from "builderStore/dataBinding"
|
||||
|
||||
const INITIAL_FRONTEND_STATE = {
|
||||
apps: [],
|
||||
|
@ -524,7 +525,9 @@ export const getFrontendStore = () => {
|
|||
// Generate default props
|
||||
let props = { ...presetProps }
|
||||
settings.forEach(setting => {
|
||||
if (setting.defaultValue !== undefined) {
|
||||
if (setting.type === "multifield" && setting.selectAllFields) {
|
||||
props[setting.key] = Object.keys(defaultDatasource.schema || {})
|
||||
} else if (setting.defaultValue !== undefined) {
|
||||
props[setting.key] = setting.defaultValue
|
||||
}
|
||||
})
|
||||
|
@ -1041,6 +1044,27 @@ export const getFrontendStore = () => {
|
|||
if (component[name] === value) {
|
||||
return false
|
||||
}
|
||||
|
||||
const settings = getComponentSettings(component._component)
|
||||
const updatedSetting = settings.find(setting => setting.key === name)
|
||||
|
||||
if (
|
||||
updatedSetting?.type === "dataSource" ||
|
||||
updatedSetting?.type === "table"
|
||||
) {
|
||||
const { schema } = getSchemaForDatasource(null, value)
|
||||
const columnNames = Object.keys(schema || {})
|
||||
const multifieldKeysToSelectAll = settings
|
||||
.filter(setting => {
|
||||
return setting.type === "multifield" && setting.selectAllFields
|
||||
})
|
||||
.map(setting => setting.key)
|
||||
|
||||
multifieldKeysToSelectAll.forEach(key => {
|
||||
component[key] = columnNames
|
||||
})
|
||||
}
|
||||
|
||||
component[name] = value
|
||||
})
|
||||
},
|
||||
|
|
|
@ -38,13 +38,15 @@
|
|||
export let testData
|
||||
export let schemaProperties
|
||||
export let isTestModal = false
|
||||
|
||||
let webhookModal
|
||||
let drawer
|
||||
let tempFilters = lookForFilters(schemaProperties) || []
|
||||
let fillWidth = true
|
||||
let codeBindingOpen = false
|
||||
let inputData
|
||||
|
||||
$: filters = lookForFilters(schemaProperties) || []
|
||||
$: tempFilters = filters
|
||||
$: stepId = block.stepId
|
||||
$: bindings = getAvailableBindings(
|
||||
block || $automationStore.selectedBlock,
|
||||
|
@ -222,16 +224,17 @@
|
|||
{:else if value.customType === "filters"}
|
||||
<ActionButton on:click={drawer.show}>Define filters</ActionButton>
|
||||
<Drawer bind:this={drawer} {fillWidth} title="Filtering">
|
||||
<Button cta slot="buttons" on:click={() => saveFilters(key)}
|
||||
>Save</Button
|
||||
>
|
||||
<Button cta slot="buttons" on:click={() => saveFilters(key)}>
|
||||
Save
|
||||
</Button>
|
||||
<FilterDrawer
|
||||
slot="body"
|
||||
bind:filters={tempFilters}
|
||||
{filters}
|
||||
{bindings}
|
||||
{schemaFields}
|
||||
panel={AutomationBindingPanel}
|
||||
fillWidth
|
||||
on:change={e => (tempFilters = e.detail)}
|
||||
/>
|
||||
</Drawer>
|
||||
{:else if value.customType === "password"}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
import { API } from "api"
|
||||
|
||||
let hideAutocolumns = true
|
||||
let filters
|
||||
|
||||
$: isUsersTable = $tables.selected?._id === TableNames.USERS
|
||||
$: type = $tables.selected?.type
|
||||
|
@ -36,6 +37,7 @@
|
|||
$: hasCols = checkHasCols(schema)
|
||||
$: hasRows = !!$fetch.rows?.length
|
||||
$: showError($fetch.error)
|
||||
$: id, (filters = null)
|
||||
|
||||
const showError = error => {
|
||||
if (error) {
|
||||
|
@ -102,8 +104,9 @@
|
|||
|
||||
// Fetch data whenever filters change
|
||||
const onFilter = e => {
|
||||
filters = e.detail
|
||||
fetch.update({
|
||||
filter: e.detail,
|
||||
filter: filters,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -117,6 +120,12 @@
|
|||
const onUpdateRows = () => {
|
||||
fetch.refresh()
|
||||
}
|
||||
|
||||
// When importing new rows it is better to reinitialise request/paging data.
|
||||
// Not doing so causes inconsistency in paging behaviour and content.
|
||||
const onImportData = () => {
|
||||
fetch.getInitialData()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
|
@ -169,7 +178,7 @@
|
|||
<ImportButton
|
||||
disabled={$tables.selected?._id === "ta_users"}
|
||||
tableId={$tables.selected?._id}
|
||||
on:updaterows={onUpdateRows}
|
||||
on:importrows={onImportData}
|
||||
/>
|
||||
<ExportButton
|
||||
disabled={!hasRows || !hasCols}
|
||||
|
@ -178,6 +187,7 @@
|
|||
{#key id}
|
||||
<TableFilterButton
|
||||
{schema}
|
||||
{filters}
|
||||
on:change={onFilter}
|
||||
disabled={!hasCols}
|
||||
/>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
|
||||
import { API } from "api"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
|
||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
||||
|
@ -34,8 +35,8 @@
|
|||
let editRowModal
|
||||
let editColumnModal
|
||||
let customRenderers = []
|
||||
let confirmDelete
|
||||
|
||||
$: isInternal = type !== "external"
|
||||
$: isUsersTable = tableId === TableNames.USERS
|
||||
$: data && resetSelectedRows()
|
||||
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
||||
|
@ -89,15 +90,17 @@
|
|||
)
|
||||
}
|
||||
|
||||
const deleteRows = async () => {
|
||||
const deleteRows = async targetRows => {
|
||||
try {
|
||||
await API.deleteRows({
|
||||
tableId,
|
||||
rows: selectedRows,
|
||||
rows: targetRows,
|
||||
})
|
||||
data = data.filter(row => !selectedRows.includes(row))
|
||||
notifications.success(`Successfully deleted ${selectedRows.length} rows`)
|
||||
selectedRows = []
|
||||
|
||||
const deletedRowIds = targetRows.map(row => row._id)
|
||||
data = data.filter(row => deletedRowIds.indexOf(row._id))
|
||||
|
||||
notifications.success(`Successfully deleted ${targetRows.length} rows`)
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting rows")
|
||||
}
|
||||
|
@ -133,7 +136,14 @@
|
|||
<div class="popovers">
|
||||
<slot />
|
||||
{#if !isUsersTable && selectedRows.length > 0}
|
||||
<DeleteRowsButton on:updaterows {selectedRows} {deleteRows} />
|
||||
<DeleteRowsButton
|
||||
on:updaterows
|
||||
{selectedRows}
|
||||
deleteRows={async rows => {
|
||||
await deleteRows(rows)
|
||||
resetSelectedRows()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Layout>
|
||||
|
@ -164,8 +174,33 @@
|
|||
</Layout>
|
||||
|
||||
<Modal bind:this={editRowModal}>
|
||||
<svelte:component this={editRowComponent} on:updaterows row={editableRow} />
|
||||
<svelte:component
|
||||
this={editRowComponent}
|
||||
on:updaterows
|
||||
on:deleteRows={() => {
|
||||
confirmDelete.show()
|
||||
}}
|
||||
row={editableRow}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDelete}
|
||||
okText="Delete"
|
||||
onOk={async () => {
|
||||
if (editableRow) {
|
||||
await deleteRows([editableRow])
|
||||
}
|
||||
editableRow = undefined
|
||||
}}
|
||||
onCancel={async () => {
|
||||
editRow(editableRow)
|
||||
}}
|
||||
title="Confirm Deletion"
|
||||
>
|
||||
Are you sure you want to delete this row?
|
||||
</ConfirmDialog>
|
||||
|
||||
<Modal bind:this={editColumnModal}>
|
||||
<CreateEditColumn
|
||||
field={editableColumn}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
let modal
|
||||
|
||||
async function confirmDeletion() {
|
||||
await deleteRows()
|
||||
await deleteRows(selectedRows)
|
||||
modal?.hide()
|
||||
dispatch("updaterows")
|
||||
}
|
||||
|
|
|
@ -12,5 +12,5 @@
|
|||
Import
|
||||
</ActionButton>
|
||||
<Modal bind:this={modal}>
|
||||
<ImportModal {tableId} on:updaterows />
|
||||
<ImportModal {tableId} on:importrows />
|
||||
</Modal>
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
export let disabled = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let modal
|
||||
let tempValue = filters || []
|
||||
|
||||
let modal
|
||||
|
||||
$: tempValue = filters || []
|
||||
$: schemaFields = Object.values(schema || {})
|
||||
</script>
|
||||
|
||||
|
@ -34,8 +35,9 @@
|
|||
<div class="wrapper">
|
||||
<FilterDrawer
|
||||
allowBindings={false}
|
||||
bind:filters={tempValue}
|
||||
{filters}
|
||||
{schemaFields}
|
||||
on:change={e => (tempValue = e.detail)}
|
||||
/>
|
||||
</div>
|
||||
</ModalContent>
|
||||
|
|
|
@ -50,22 +50,34 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title={creating ? "Create Row" : "Edit Row"}
|
||||
confirmText={creating ? "Create Row" : "Save Row"}
|
||||
onConfirm={saveRow}
|
||||
>
|
||||
{#each tableSchema as [key, meta]}
|
||||
{#if !meta.autocolumn && meta.type !== FORMULA_TYPE}
|
||||
<div>
|
||||
<RowFieldControl error={errors[key]} {meta} bind:value={row[key]} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</ModalContent>
|
||||
<span class="modal-wrap">
|
||||
<ModalContent
|
||||
title={creating ? "Create Row" : "Edit Row"}
|
||||
confirmText={creating ? "Create Row" : "Save Row"}
|
||||
onConfirm={saveRow}
|
||||
showCancelButton={creating}
|
||||
showSecondaryButton={!creating}
|
||||
secondaryButtonWarning={!creating}
|
||||
secondaryButtonText="Delete"
|
||||
secondaryAction={() => {
|
||||
dispatch("deleteRows", row)
|
||||
}}
|
||||
>
|
||||
{#each tableSchema as [key, meta]}
|
||||
{#if !meta.autocolumn && meta.type !== FORMULA_TYPE}
|
||||
<div>
|
||||
<RowFieldControl error={errors[key]} {meta} bind:value={row[key]} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</ModalContent>
|
||||
</span>
|
||||
|
||||
<style>
|
||||
div {
|
||||
min-width: 0;
|
||||
}
|
||||
.modal-wrap :global(.secondary-action) {
|
||||
margin-right: unset;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
|
||||
// Always refresh rows just to be sure
|
||||
dispatch("updaterows")
|
||||
dispatch("importrows")
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -45,6 +45,23 @@
|
|||
|
||||
const touched = writable({})
|
||||
|
||||
function invalidThroughTable({ through, throughTo, throughFrom }) {
|
||||
// need to know the foreign key columns to check error
|
||||
if (!through || !throughTo || !throughFrom) {
|
||||
return false
|
||||
}
|
||||
const throughTable = plusTables.find(tbl => tbl._id === through)
|
||||
const otherColumns = Object.values(throughTable.schema).filter(
|
||||
col => col.name !== throughFrom && col.name !== throughTo
|
||||
)
|
||||
for (let col of otherColumns) {
|
||||
if (col.constraints?.presence && !col.autocolumn) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function checkForErrors(fromRelate, toRelate) {
|
||||
const isMany =
|
||||
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||
|
@ -59,6 +76,10 @@
|
|||
if ($touched.through && isMany && !fromRelate.through) {
|
||||
errObj.through = tableNotSet
|
||||
}
|
||||
if ($touched.through && invalidThroughTable(fromRelate)) {
|
||||
errObj.through =
|
||||
"Ensure all columns in table are nullable or auto generated"
|
||||
}
|
||||
if ($touched.foreign && !isMany && !fromRelate.fieldName) {
|
||||
errObj.foreign = "Please pick the foreign key"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { store } from "builderStore"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { tables, datasources } from "stores/backend"
|
||||
import {
|
||||
ActionMenu,
|
||||
|
@ -18,7 +19,10 @@
|
|||
let editorModal
|
||||
let confirmDeleteDialog
|
||||
let error = ""
|
||||
let originalName = table.name
|
||||
|
||||
let originalName
|
||||
let updatedName
|
||||
|
||||
let templateScreens
|
||||
let willBeDeleted
|
||||
let deleteTableName
|
||||
|
@ -59,7 +63,9 @@
|
|||
}
|
||||
|
||||
async function save() {
|
||||
await tables.save(table)
|
||||
const updatedTable = cloneDeep(table)
|
||||
updatedTable.name = updatedName
|
||||
await tables.save(updatedTable)
|
||||
notifications.success("Table renamed successfully")
|
||||
}
|
||||
|
||||
|
@ -70,6 +76,11 @@
|
|||
? `Table with name ${tableName} already exists. Please choose another name.`
|
||||
: ""
|
||||
}
|
||||
|
||||
const initForm = () => {
|
||||
originalName = table.name + ""
|
||||
updatedName = table.name + ""
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if allowDeletion}
|
||||
|
@ -84,17 +95,17 @@
|
|||
</ActionMenu>
|
||||
{/if}
|
||||
|
||||
<Modal bind:this={editorModal}>
|
||||
<Modal bind:this={editorModal} on:show={initForm}>
|
||||
<ModalContent
|
||||
title="Edit Table"
|
||||
confirmText="Save"
|
||||
onConfirm={save}
|
||||
disabled={table.name === originalName || error}
|
||||
disabled={updatedName === originalName || error}
|
||||
>
|
||||
<Input
|
||||
label="Table Name"
|
||||
thin
|
||||
bind:value={table.name}
|
||||
bind:value={updatedName}
|
||||
on:input={checkValid}
|
||||
{error}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { views } from "stores/backend"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import {
|
||||
notifications,
|
||||
|
@ -15,13 +16,17 @@
|
|||
export let view
|
||||
|
||||
let editorModal
|
||||
let originalName = view.name
|
||||
let originalName
|
||||
let updatedName
|
||||
let confirmDeleteDialog
|
||||
|
||||
async function save() {
|
||||
const updatedView = cloneDeep(view)
|
||||
updatedView.name = updatedName
|
||||
|
||||
await views.save({
|
||||
originalName,
|
||||
...view,
|
||||
...updatedView,
|
||||
})
|
||||
notifications.success("View renamed successfully")
|
||||
}
|
||||
|
@ -37,6 +42,11 @@
|
|||
notifications.error("Error deleting view")
|
||||
}
|
||||
}
|
||||
|
||||
const initForm = () => {
|
||||
updatedName = view.name + ""
|
||||
originalName = view.name + ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
|
@ -46,9 +56,9 @@
|
|||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
||||
</ActionMenu>
|
||||
<Modal bind:this={editorModal}>
|
||||
<Modal bind:this={editorModal} on:show={initForm}>
|
||||
<ModalContent title="Edit View" onConfirm={save} confirmText="Save">
|
||||
<Input label="View Name" thin bind:value={view.name} />
|
||||
<Input label="View Name" thin bind:value={updatedName} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<ConfirmDialog
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
export let value = ""
|
||||
export let valid
|
||||
export let allowJS = false
|
||||
export let allowHelpers = true
|
||||
|
||||
let helpers = handlebarsCompletions()
|
||||
let getCaretPosition
|
||||
|
@ -85,7 +86,7 @@
|
|||
return helper.label.match(searchRgx) || helper.description.match(searchRgx)
|
||||
})
|
||||
|
||||
$: categoryNames = [...categories.map(cat => cat[0]), "Helpers"]
|
||||
$: categoryNames = getCategoryNames(categories)
|
||||
|
||||
$: codeMirrorHints = bindings?.map(x => `$("${x.readableBinding}")`)
|
||||
|
||||
|
@ -96,6 +97,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
const getCategoryNames = categories => {
|
||||
let names = [...categories.map(cat => cat[0])]
|
||||
if (allowHelpers) {
|
||||
names.push("Helpers")
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// Adds a JS/HBS helper to the expression
|
||||
const addHelper = (helper, js) => {
|
||||
let tempVal
|
||||
|
@ -343,7 +352,7 @@
|
|||
for more details.
|
||||
</p>
|
||||
{/if}
|
||||
{#if $admin.isDev}
|
||||
{#if $admin.isDev && allowJS}
|
||||
<div class="convert">
|
||||
<Button secondary on:click={convert}>Convert to JS</Button>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
export let valid
|
||||
export let value = ""
|
||||
export let allowJS = false
|
||||
export let allowHelpers = true
|
||||
|
||||
$: enrichedBindings = enrichBindings(bindings)
|
||||
|
||||
|
@ -25,5 +26,6 @@
|
|||
bindings={enrichedBindings}
|
||||
{value}
|
||||
{allowJS}
|
||||
{allowHelpers}
|
||||
on:change
|
||||
/>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
export let disabled = false
|
||||
export let fillWidth
|
||||
export let allowJS = true
|
||||
export let allowHelpers = true
|
||||
export let updateOnChange = true
|
||||
export let drawerLeft
|
||||
|
||||
|
@ -77,6 +78,7 @@
|
|||
on:change={event => (tempValue = event.detail)}
|
||||
{bindings}
|
||||
{allowJS}
|
||||
{allowHelpers}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export function addJSBinding(value, caretPos, binding, { helper } = {}) {
|
|||
if (!helper) {
|
||||
binding = `$("${binding}")`
|
||||
} else {
|
||||
binding = `helper.${binding}()`
|
||||
binding = `helpers.${binding}()`
|
||||
}
|
||||
if (caretPos.start) {
|
||||
value =
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
/>
|
||||
|
||||
<Label small />
|
||||
<Checkbox
|
||||
text="Do not display default notification"
|
||||
bind:value={parameters.notificationOverride}
|
||||
/>
|
||||
<br />
|
||||
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||
|
||||
{#if parameters.confirm}
|
||||
|
|
|
@ -95,6 +95,11 @@
|
|||
/>
|
||||
|
||||
<Label small />
|
||||
<Checkbox
|
||||
text="Do not display default notification"
|
||||
bind:value={parameters.notificationOverride}
|
||||
/>
|
||||
<br />
|
||||
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||
|
||||
{#if parameters.confirm}
|
||||
|
|
|
@ -48,7 +48,11 @@
|
|||
getOptionLabel={query => query.name}
|
||||
getOptionValue={query => query._id}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
text="Do not display default notification"
|
||||
bind:value={parameters.notificationOverride}
|
||||
/>
|
||||
<br />
|
||||
{#if parameters.queryId}
|
||||
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||
|
||||
|
|
|
@ -95,6 +95,11 @@
|
|||
/>
|
||||
|
||||
<Label small />
|
||||
<Checkbox
|
||||
text="Do not display default notification"
|
||||
bind:value={parameters.notificationOverride}
|
||||
/>
|
||||
<br />
|
||||
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||
|
||||
{#if parameters.confirm}
|
||||
|
|
|
@ -93,6 +93,11 @@
|
|||
{/if}
|
||||
|
||||
<Label small />
|
||||
<Checkbox
|
||||
text="Do not display default notification"
|
||||
bind:value={parameters.notificationOverride}
|
||||
/>
|
||||
<br />
|
||||
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||
|
||||
{#if parameters.confirm}
|
||||
|
|
|
@ -17,33 +17,73 @@
|
|||
import { generate } from "shortid"
|
||||
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
||||
import { getFields } from "helpers/searchFields"
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const { OperatorOptions } = Constants
|
||||
const { getValidOperatorsForType } = LuceneUtils
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let schemaFields
|
||||
export let filters = []
|
||||
export let bindings = []
|
||||
export let panel = ClientBindingPanel
|
||||
export let allowBindings = true
|
||||
export let allOr = false
|
||||
export let fillWidth = false
|
||||
export let tableId
|
||||
|
||||
$: dispatch("change", filters)
|
||||
$: enrichedSchemaFields = getFields(schemaFields || [])
|
||||
const dispatch = createEventDispatcher()
|
||||
const { OperatorOptions } = Constants
|
||||
const { getValidOperatorsForType } = LuceneUtils
|
||||
const KeyedFieldRegex = /\d[0-9]*:/g
|
||||
const behaviourOptions = [
|
||||
{ value: "and", label: "Match all filters" },
|
||||
{ value: "or", label: "Match any filter" },
|
||||
]
|
||||
|
||||
let rawFilters
|
||||
let matchAny = false
|
||||
|
||||
$: parseFilters(filters)
|
||||
$: dispatch("change", enrichFilters(rawFilters, matchAny))
|
||||
$: enrichedSchemaFields = getFields(
|
||||
schemaFields || [],
|
||||
{ allowLinks: true },
|
||||
tableId
|
||||
)
|
||||
$: fieldOptions = enrichedSchemaFields.map(field => field.name) || []
|
||||
$: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"]
|
||||
|
||||
let behaviourValue
|
||||
const behaviourOptions = [
|
||||
{ value: "and", label: "Match all of the following filters" },
|
||||
{ value: "or", label: "Match any of the following filters" },
|
||||
]
|
||||
// Remove field key prefixes and determine whether to use the "match all"
|
||||
// or "match any" behaviour
|
||||
const parseFilters = filters => {
|
||||
matchAny = filters?.find(filter => filter.operator === "allOr") != null
|
||||
rawFilters = (filters || [])
|
||||
.filter(filter => filter.operator !== "allOr")
|
||||
.map(filter => {
|
||||
const { field } = filter
|
||||
let newFilter = { ...filter }
|
||||
delete newFilter.allOr
|
||||
if (typeof field === "string" && field.match(KeyedFieldRegex) != null) {
|
||||
const parts = field.split(":")
|
||||
parts.shift()
|
||||
newFilter.field = parts.join(":")
|
||||
}
|
||||
return newFilter
|
||||
})
|
||||
}
|
||||
|
||||
// Add field key prefixes and a special metadata filter object to indicate
|
||||
// whether to use the "match all" or "match any" behaviour
|
||||
const enrichFilters = (rawFilters, matchAny) => {
|
||||
let count = 1
|
||||
return rawFilters
|
||||
.filter(filter => filter.field)
|
||||
.map(filter => ({
|
||||
...filter,
|
||||
field: `${count++}:${filter.field}`,
|
||||
}))
|
||||
.concat(matchAny ? [{ operator: "allOr" }] : [])
|
||||
}
|
||||
|
||||
const addFilter = () => {
|
||||
filters = [
|
||||
...filters,
|
||||
rawFilters = [
|
||||
...rawFilters,
|
||||
{
|
||||
id: generate(),
|
||||
field: null,
|
||||
|
@ -55,13 +95,13 @@
|
|||
}
|
||||
|
||||
const removeFilter = id => {
|
||||
filters = filters.filter(field => field.id !== id)
|
||||
rawFilters = rawFilters.filter(field => field.id !== id)
|
||||
}
|
||||
|
||||
const duplicateFilter = id => {
|
||||
const existingFilter = filters.find(filter => filter.id === id)
|
||||
const existingFilter = rawFilters.find(filter => filter.id === id)
|
||||
const duplicate = { ...existingFilter, id: generate() }
|
||||
filters = [...filters, duplicate]
|
||||
rawFilters = [...rawFilters, duplicate]
|
||||
}
|
||||
|
||||
const getSchema = filter => {
|
||||
|
@ -128,32 +168,22 @@
|
|||
const schema = enrichedSchemaFields.find(x => x.name === field)
|
||||
return schema?.constraints?.inclusion || []
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
behaviourValue = allOr ? "or" : "and"
|
||||
})
|
||||
</script>
|
||||
|
||||
<DrawerContent>
|
||||
<div class="container">
|
||||
<Layout noPadding>
|
||||
<Body size="S">
|
||||
{#if !filters?.length}
|
||||
Add your first filter expression.
|
||||
{:else}
|
||||
Results are filtered to only those which match all of the following
|
||||
constraints.
|
||||
{/if}
|
||||
</Body>
|
||||
{#if filters?.length}
|
||||
{#if !rawFilters?.length}
|
||||
<Body size="S">Add your first filter expression.</Body>
|
||||
{:else}
|
||||
<div class="fields">
|
||||
<Select
|
||||
label="Behaviour"
|
||||
value={behaviourValue}
|
||||
value={matchAny ? "or" : "and"}
|
||||
options={behaviourOptions}
|
||||
getOptionLabel={opt => opt.label}
|
||||
getOptionValue={opt => opt.value}
|
||||
on:change={e => (allOr = e.detail === "or")}
|
||||
on:change={e => (matchAny = e.detail === "or")}
|
||||
placeholder={null}
|
||||
/>
|
||||
</div>
|
||||
|
@ -162,7 +192,7 @@
|
|||
<Label>Filters</Label>
|
||||
</div>
|
||||
<div class="fields">
|
||||
{#each filters as filter, idx}
|
||||
{#each rawFilters as filter, idx}
|
||||
<Select
|
||||
bind:value={filter.field}
|
||||
options={fieldOptions}
|
||||
|
@ -264,7 +294,7 @@
|
|||
column-gap: var(--spacing-l);
|
||||
row-gap: var(--spacing-s);
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr 150px 120px 1fr 16px 16px;
|
||||
grid-template-columns: minmax(150px, 1fr) 170px 120px minmax(150px, 1fr) 16px 16px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
|
|
|
@ -8,74 +8,22 @@
|
|||
import FilterDrawer from "./FilterDrawer.svelte"
|
||||
import { currentAsset } from "builderStore"
|
||||
|
||||
const QUERY_START_REGEX = /\d[0-9]*:/g
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = []
|
||||
export let componentInstance
|
||||
export let bindings = []
|
||||
|
||||
let drawer,
|
||||
toSaveFilters = null,
|
||||
allOr,
|
||||
initialAllOr
|
||||
let drawer
|
||||
|
||||
$: initialFilters = correctFilters(value || [])
|
||||
$: tempValue = value
|
||||
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
|
||||
$: schemaFields = Object.values(schema || {})
|
||||
|
||||
function addNumbering(filters) {
|
||||
let count = 1
|
||||
for (let value of filters) {
|
||||
if (value.field && value.field?.match(QUERY_START_REGEX) == null) {
|
||||
value.field = `${count++}:${value.field}`
|
||||
}
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
function correctFilters(filters) {
|
||||
const corrected = []
|
||||
for (let filter of filters) {
|
||||
let field = filter.field
|
||||
if (filter.operator === "allOr") {
|
||||
initialAllOr = allOr = true
|
||||
continue
|
||||
}
|
||||
if (
|
||||
typeof filter.field === "string" &&
|
||||
filter.field.match(QUERY_START_REGEX) != null
|
||||
) {
|
||||
const parts = field.split(":")
|
||||
const number = parts[0]
|
||||
// it's the new format, remove number
|
||||
if (!isNaN(parseInt(number))) {
|
||||
parts.shift()
|
||||
field = parts.join(":")
|
||||
}
|
||||
}
|
||||
corrected.push({
|
||||
...filter,
|
||||
field,
|
||||
})
|
||||
}
|
||||
return corrected
|
||||
}
|
||||
|
||||
async function saveFilter() {
|
||||
if (!toSaveFilters && allOr !== initialAllOr) {
|
||||
toSaveFilters = initialFilters
|
||||
}
|
||||
const filters = toSaveFilters?.filter(filter => filter.operator !== "allOr")
|
||||
if (allOr && filters) {
|
||||
filters.push({ operator: "allOr" })
|
||||
}
|
||||
// only save if anything was updated
|
||||
if (filters) {
|
||||
dispatch("change", addNumbering(filters))
|
||||
}
|
||||
notifications.success("Filters saved.")
|
||||
dispatch("change", tempValue)
|
||||
notifications.success("Filters saved")
|
||||
drawer.hide()
|
||||
}
|
||||
</script>
|
||||
|
@ -85,12 +33,10 @@
|
|||
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
||||
<FilterDrawer
|
||||
slot="body"
|
||||
filters={initialFilters}
|
||||
filters={value}
|
||||
{bindings}
|
||||
{schemaFields}
|
||||
bind:allOr
|
||||
on:change={event => {
|
||||
toSaveFilters = event.detail
|
||||
}}
|
||||
tableId={dataSource.tableId}
|
||||
on:change={e => (tempValue = e.detail)}
|
||||
/>
|
||||
</Drawer>
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
export let showMenu = false
|
||||
export let bindings = []
|
||||
export let bindingDrawerLeft
|
||||
export let allowHelpers = true
|
||||
|
||||
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
||||
name,
|
||||
|
@ -122,6 +123,7 @@
|
|||
disabled={readOnly}
|
||||
value={field.value}
|
||||
allowJS={false}
|
||||
{allowHelpers}
|
||||
fillWidth={true}
|
||||
drawerLeft={bindingDrawerLeft}
|
||||
/>
|
||||
|
|
|
@ -223,6 +223,7 @@
|
|||
.config {
|
||||
display: grid;
|
||||
grid-gap: var(--spacing-s);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.config-field {
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
valuePlaceholder="Default"
|
||||
bindings={[...userBindings]}
|
||||
bindingDrawerLeft="260px"
|
||||
allowHelpers={false}
|
||||
on:change
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,11 @@ export function getTableFields(linkField) {
|
|||
}))
|
||||
}
|
||||
|
||||
export function getFields(fields, { allowLinks } = { allowLinks: true }) {
|
||||
export function getFields(
|
||||
fields,
|
||||
{ allowLinks } = { allowLinks: true },
|
||||
tableId
|
||||
) {
|
||||
let filteredFields = fields.filter(
|
||||
field => !BannedSearchTypes.includes(field.type)
|
||||
)
|
||||
|
@ -30,5 +34,9 @@ export function getFields(fields, { allowLinks } = { allowLinks: true }) {
|
|||
const staticFormulaFields = fields.filter(
|
||||
field => field.type === "formula" && field.formulaType === "static"
|
||||
)
|
||||
const table = get(tables).list.find(table => table._id === tableId)
|
||||
if (table?.type === "external" && table?.sql) {
|
||||
filteredFields = filteredFields.filter(field => field.name !== "_id")
|
||||
}
|
||||
return filteredFields.concat(staticFormulaFields)
|
||||
}
|
||||
|
|
|
@ -61,6 +61,13 @@
|
|||
align-items: center;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
.header-left {
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
}
|
||||
.header-left :global(> *) {
|
||||
max-width: 100%;
|
||||
}
|
||||
.header-left :global(.spectrum-Picker) {
|
||||
font-weight: 600;
|
||||
color: var(--spectrum-global-color-gray-900);
|
||||
|
|
|
@ -282,12 +282,16 @@
|
|||
gap: var(--spacing-l);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: auto 160px auto 1fr 130px 130px 1fr auto auto;
|
||||
grid-template-columns:
|
||||
auto 150px auto minmax(140px, 1fr) 120px 100px minmax(140px, 1fr)
|
||||
auto auto;
|
||||
border-radius: var(--border-radius-s);
|
||||
transition: background-color ease-in-out 130ms;
|
||||
}
|
||||
.condition.update {
|
||||
grid-template-columns: auto 160px 1fr auto 1fr auto 1fr 130px 130px 1fr auto auto;
|
||||
grid-template-columns:
|
||||
auto 150px minmax(140px, 1fr) auto minmax(140px, 1fr) auto
|
||||
minmax(140px, 1fr) 120px 100px minmax(140px, 1fr) auto auto;
|
||||
}
|
||||
.condition:hover {
|
||||
background-color: var(--spectrum-global-color-gray-100);
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
? {
|
||||
title: "User Groups",
|
||||
href: "/builder/portal/manage/groups",
|
||||
badge: "New",
|
||||
}
|
||||
: undefined,
|
||||
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
||||
|
@ -56,7 +55,6 @@
|
|||
{
|
||||
title: "Plugins",
|
||||
href: "/builder/portal/manage/plugins",
|
||||
badge: "New",
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -119,14 +117,12 @@
|
|||
{
|
||||
title: "Upgrade",
|
||||
href: $adminStore.accountPortalUrl + "/portal/upgrade",
|
||||
badge: "New",
|
||||
},
|
||||
])
|
||||
} else if (!$adminStore.cloud && admin) {
|
||||
menu = menu.concat({
|
||||
title: "Upgrade",
|
||||
href: "/builder/portal/settings/upgrade",
|
||||
badge: "New",
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<ModalContent showCancelButton={false} {title} confirmText="Done">
|
||||
<ModalContent size="M" showCancelButton={false} {title} confirmText="Done">
|
||||
{#if hasSuccess}
|
||||
<Body size="XS">
|
||||
Your users should now receive an email invite to get access to their
|
||||
|
@ -70,6 +70,3 @@
|
|||
/>
|
||||
{/if}
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
Table,
|
||||
Layout,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Search,
|
||||
notifications,
|
||||
Pagination,
|
||||
|
@ -23,6 +22,7 @@
|
|||
import { goto } from "@roxi/routify"
|
||||
import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte"
|
||||
import PasswordModal from "./_components/PasswordModal.svelte"
|
||||
import InvitedModal from "./_components/InvitedModal.svelte"
|
||||
import ImportUsersModal from "./_components/ImportUsersModal.svelte"
|
||||
import { get } from "svelte/store"
|
||||
import { Constants, Utils, fetchData } from "@budibase/frontend-core"
|
||||
|
@ -67,6 +67,8 @@
|
|||
sortable: false,
|
||||
},
|
||||
}
|
||||
$: userData = []
|
||||
$: inviteUsersResponse = { successful: [], unsuccessful: [] }
|
||||
$: {
|
||||
enrichedUsers = $fetch.rows?.map(user => {
|
||||
let userGroups = []
|
||||
|
@ -112,8 +114,7 @@
|
|||
groups: userData.groups,
|
||||
}))
|
||||
try {
|
||||
const res = await users.invite(payload)
|
||||
notifications.success(res.message)
|
||||
inviteUsersResponse = await users.invite(payload)
|
||||
inviteConfirmationModal.show()
|
||||
} catch (error) {
|
||||
notifications.error("Error inviting user")
|
||||
|
@ -273,16 +274,7 @@
|
|||
</Modal>
|
||||
|
||||
<Modal bind:this={inviteConfirmationModal}>
|
||||
<ModalContent
|
||||
showCancelButton={false}
|
||||
title="Invites sent!"
|
||||
confirmText="Done"
|
||||
>
|
||||
<Body size="S">
|
||||
Your users should now recieve an email invite to get access to their
|
||||
Budibase account
|
||||
</Body>
|
||||
</ModalContent>
|
||||
<InvitedModal {inviteUsersResponse} />
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={onboardingTypeModal}>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
@ -26,9 +26,9 @@
|
|||
"outputPath": "build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/backend-core": "2.1.40-alpha.7",
|
||||
"@budibase/string-templates": "2.1.40-alpha.7",
|
||||
"@budibase/types": "2.1.40-alpha.7",
|
||||
"@budibase/backend-core": "2.1.46-alpha.1",
|
||||
"@budibase/string-templates": "2.1.46-alpha.1",
|
||||
"@budibase/types": "2.1.46-alpha.1",
|
||||
"axios": "0.21.2",
|
||||
"chalk": "4.1.0",
|
||||
"cli-progress": "3.11.2",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2598,6 +2598,7 @@
|
|||
]
|
||||
},
|
||||
"passwordfield": {
|
||||
"skeleton": false,
|
||||
"name": "Password Field",
|
||||
"icon": "LockClosed",
|
||||
"styles": [
|
||||
|
@ -3066,6 +3067,7 @@
|
|||
]
|
||||
},
|
||||
"longformfield": {
|
||||
"skeleton": false,
|
||||
"name": "Long Form Field",
|
||||
"icon": "TextAlignLeft",
|
||||
"styles": [
|
||||
|
@ -5118,7 +5120,8 @@
|
|||
{
|
||||
"type": "multifield",
|
||||
"label": "Fields",
|
||||
"key": "fields"
|
||||
"key": "fields",
|
||||
"selectAllFields": true
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
|
@ -5204,7 +5207,8 @@
|
|||
"icon": "RailRight",
|
||||
"hasChildren": true,
|
||||
"illegalChildren": [
|
||||
"section"
|
||||
"section",
|
||||
"sidepanel"
|
||||
],
|
||||
"showEmptyState": false,
|
||||
"draggable": false,
|
||||
|
@ -5283,7 +5287,8 @@
|
|||
"type": "multifield",
|
||||
"label": "Fields",
|
||||
"key": "detailFields",
|
||||
"nested": true
|
||||
"nested": true,
|
||||
"selectAllFields": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -5293,4 +5298,4 @@
|
|||
"suffix": "repeater"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.1.40-alpha.7",
|
||||
"@budibase/frontend-core": "2.1.40-alpha.7",
|
||||
"@budibase/string-templates": "2.1.40-alpha.7",
|
||||
"@budibase/bbui": "2.1.46-alpha.1",
|
||||
"@budibase/frontend-core": "2.1.46-alpha.1",
|
||||
"@budibase/string-templates": "2.1.46-alpha.1",
|
||||
"@spectrum-css/button": "^3.0.3",
|
||||
"@spectrum-css/card": "^3.0.3",
|
||||
"@spectrum-css/divider": "^1.0.3",
|
||||
|
|
|
@ -479,6 +479,7 @@
|
|||
definition.name !== "Screenslot" &&
|
||||
children.length === 0 &&
|
||||
!instance._blockElementHasChildren &&
|
||||
!definition.block &&
|
||||
definition.skeleton !== false
|
||||
</script>
|
||||
|
||||
|
|
|
@ -29,6 +29,17 @@
|
|||
// Derive visibility
|
||||
$: open = $sidePanelStore.contentId === $component.id
|
||||
|
||||
// Derive a render key which is only changed whenever this panel is made
|
||||
// visible after being hidden. We need to key the content to avoid showing
|
||||
// stale data when re-revealing a side panel that was closed, but we cannot
|
||||
// hide the content altogether when hidden as this breaks ejection.
|
||||
let renderKey = null
|
||||
$: {
|
||||
if (open) {
|
||||
renderKey = Math.random()
|
||||
}
|
||||
}
|
||||
|
||||
const showInSidePanel = (el, visible) => {
|
||||
const update = visible => {
|
||||
const target = document.getElementById("side-panel-container")
|
||||
|
@ -47,7 +58,10 @@
|
|||
// Apply initial visibility
|
||||
update(visible)
|
||||
|
||||
return { update }
|
||||
return {
|
||||
update,
|
||||
destroy: () => update(false),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -57,7 +71,9 @@
|
|||
class="side-panel"
|
||||
class:open
|
||||
>
|
||||
<slot />
|
||||
{#key renderKey}
|
||||
<slot />
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
let dataProviderId
|
||||
let repeaterId
|
||||
let schema
|
||||
let schemaLoaded = false
|
||||
|
||||
$: fetchSchema(dataSource)
|
||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||
|
@ -75,138 +74,135 @@
|
|||
enrichRelationships: true,
|
||||
})
|
||||
}
|
||||
schemaLoaded = true
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if schemaLoaded}
|
||||
<Block>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{ dataSource, disableValidation: true }}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<Block>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{ dataSource, disableValidation: true }}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "middle",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-bottom": "20px",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
props={{
|
||||
text: title,
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
hAlign: "left",
|
||||
vAlign: "middle",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
{#if enrichedSearchColumns?.length}
|
||||
{#each enrichedSearchColumns as column, idx}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
props={{
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
order={idx}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "192px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
order={enrichedSearchColumns?.length ?? 0}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
bind:id={repeaterId}
|
||||
context="repeater"
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
gap: "M",
|
||||
noRowsMessage: "No rows found",
|
||||
}}
|
||||
styles={{
|
||||
custom: `display: grid;\ngrid-template-columns: repeat(auto-fill, minmax(min(${cardWidth}px, 100%), 1fr));`,
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="spectrumcard"
|
||||
props={{
|
||||
title: cardTitle,
|
||||
subtitle: cardSubtitle,
|
||||
description: cardDescription,
|
||||
imageURL: cardImageURL,
|
||||
horizontal: cardHorizontal,
|
||||
showButton: showCardButton,
|
||||
buttonText: cardButtonText,
|
||||
buttonOnClick: cardButtonOnClick,
|
||||
linkURL: fullCardURL,
|
||||
linkPeek: cardPeek,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-bottom": "20px",
|
||||
width: "auto",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
props={{
|
||||
text: title,
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "left",
|
||||
vAlign: "middle",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
{#if enrichedSearchColumns?.length}
|
||||
{#each enrichedSearchColumns as column, idx}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
props={{
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
order={idx}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "192px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
order={enrichedSearchColumns?.length ?? 0}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
bind:id={repeaterId}
|
||||
context="repeater"
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
gap: "M",
|
||||
noRowsMessage: "No rows found",
|
||||
}}
|
||||
styles={{
|
||||
custom: `display: grid;\ngrid-template-columns: repeat(auto-fill, minmax(min(${cardWidth}px, 100%), 1fr));`,
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="spectrumcard"
|
||||
props={{
|
||||
title: cardTitle,
|
||||
subtitle: cardSubtitle,
|
||||
description: cardDescription,
|
||||
imageURL: cardImageURL,
|
||||
horizontal: cardHorizontal,
|
||||
showButton: showCardButton,
|
||||
buttonText: cardButtonText,
|
||||
buttonOnClick: cardButtonOnClick,
|
||||
linkURL: fullCardURL,
|
||||
linkPeek: cardPeek,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "auto",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
</BlockComponent>
|
||||
/>
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
</Block>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</Block>
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
let newRowSidePanelId
|
||||
let schema
|
||||
let primaryDisplay
|
||||
let schemaLoaded = false
|
||||
|
||||
$: fetchSchema(dataSource)
|
||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||
|
@ -66,7 +65,7 @@
|
|||
},
|
||||
]
|
||||
$: buttonClickActions =
|
||||
clickBehaviour === "actions" || dataSource?.type !== "table"
|
||||
titleButtonClickBehaviour === "actions" || dataSource?.type !== "table"
|
||||
? onClickTitleButton
|
||||
: [
|
||||
{
|
||||
|
@ -89,7 +88,6 @@
|
|||
enrichRelationships: true,
|
||||
})
|
||||
}
|
||||
schemaLoaded = true
|
||||
}
|
||||
|
||||
const getNormalFields = schema => {
|
||||
|
@ -113,162 +111,160 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if schemaLoaded}
|
||||
<Block>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{
|
||||
dataSource,
|
||||
disableValidation: true,
|
||||
editAutoColumns: true,
|
||||
size,
|
||||
}}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<Block>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{
|
||||
dataSource,
|
||||
disableValidation: true,
|
||||
editAutoColumns: true,
|
||||
size,
|
||||
}}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "middle",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-bottom": "20px",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
props={{
|
||||
text: title,
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "middle",
|
||||
hAlign: "left",
|
||||
vAlign: "center",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-bottom": "20px",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
order={1}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
props={{
|
||||
text: title,
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "left",
|
||||
vAlign: "center",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
{#if enrichedSearchColumns?.length}
|
||||
{#each enrichedSearchColumns as column, idx}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
props={{
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "192px",
|
||||
},
|
||||
}}
|
||||
order={idx}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
{#if enrichedSearchColumns?.length}
|
||||
{#each enrichedSearchColumns as column, idx}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
type={column.componentType}
|
||||
props={{
|
||||
onClick: buttonClickActions,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
order={enrichedSearchColumns?.length ?? 0}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "192px",
|
||||
},
|
||||
}}
|
||||
order={idx}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: buttonClickActions,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
order={enrichedSearchColumns?.length ?? 0}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn: sortColumn || primaryDisplay,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit: rowCount,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
type="table"
|
||||
context="table"
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn: sortColumn || primaryDisplay,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit: rowCount,
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
columns: tableColumns,
|
||||
rowCount,
|
||||
quiet,
|
||||
compact,
|
||||
allowSelectRows,
|
||||
size,
|
||||
onClick: rowClickActions,
|
||||
}}
|
||||
order={1}
|
||||
/>
|
||||
</BlockComponent>
|
||||
{#if clickBehaviour === "details"}
|
||||
<BlockComponent
|
||||
name="Details side panel"
|
||||
type="sidepanel"
|
||||
bind:id={detailsSidePanelId}
|
||||
context="details-side-panel"
|
||||
order={2}
|
||||
>
|
||||
<BlockComponent
|
||||
type="table"
|
||||
context="table"
|
||||
name="Details form block"
|
||||
type="formblock"
|
||||
bind:id={detailsFormBlockId}
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
columns: tableColumns,
|
||||
rowCount,
|
||||
quiet,
|
||||
compact,
|
||||
allowSelectRows,
|
||||
size,
|
||||
onClick: rowClickActions,
|
||||
dataSource,
|
||||
showSaveButton: true,
|
||||
showDeleteButton: true,
|
||||
actionType: "Update",
|
||||
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
||||
fields: normalFields,
|
||||
title: editTitle,
|
||||
labelPosition: "left",
|
||||
}}
|
||||
/>
|
||||
</BlockComponent>
|
||||
{#if clickBehaviour === "details"}
|
||||
{/if}
|
||||
{#if showTitleButton && titleButtonClickBehaviour === "new"}
|
||||
<BlockComponent
|
||||
name="New row side panel"
|
||||
type="sidepanel"
|
||||
bind:id={newRowSidePanelId}
|
||||
context="new-side-panel"
|
||||
order={3}
|
||||
>
|
||||
<BlockComponent
|
||||
name="Details side panel"
|
||||
type="sidepanel"
|
||||
bind:id={detailsSidePanelId}
|
||||
context="details-side-panel"
|
||||
order={2}
|
||||
>
|
||||
<BlockComponent
|
||||
name="Details form block"
|
||||
type="formblock"
|
||||
bind:id={detailsFormBlockId}
|
||||
props={{
|
||||
dataSource,
|
||||
showSaveButton: true,
|
||||
showDeleteButton: true,
|
||||
actionType: "Update",
|
||||
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
||||
fields: normalFields,
|
||||
title: editTitle,
|
||||
labelPosition: "left",
|
||||
}}
|
||||
/>
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
{#if showTitleButton && titleButtonClickBehaviour === "new"}
|
||||
<BlockComponent
|
||||
name="New row side panel"
|
||||
type="sidepanel"
|
||||
bind:id={newRowSidePanelId}
|
||||
context="new-side-panel"
|
||||
order={3}
|
||||
>
|
||||
<BlockComponent
|
||||
name="New row form block"
|
||||
type="formblock"
|
||||
props={{
|
||||
dataSource,
|
||||
showSaveButton: true,
|
||||
showDeleteButton: false,
|
||||
actionType: "Create",
|
||||
fields: normalFields,
|
||||
title: "Create Row",
|
||||
labelPosition: "left",
|
||||
}}
|
||||
/>
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</Block>
|
||||
{/if}
|
||||
name="New row form block"
|
||||
type="formblock"
|
||||
props={{
|
||||
dataSource,
|
||||
showSaveButton: true,
|
||||
showDeleteButton: false,
|
||||
actionType: "Create",
|
||||
fields: normalFields,
|
||||
title: "Create Row",
|
||||
labelPosition: "left",
|
||||
}}
|
||||
/>
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</Block>
|
||||
|
|
|
@ -20,12 +20,10 @@
|
|||
const context = getContext("context")
|
||||
const { API, fetchDatasourceSchema } = getContext("sdk")
|
||||
|
||||
let loaded = false
|
||||
let schema
|
||||
let table
|
||||
|
||||
$: fetchSchema(dataSource)
|
||||
$: fetchTable(dataSource)
|
||||
|
||||
// Returns the closes data context which isn't a built in context
|
||||
const getInitialValues = (type, dataSource, context) => {
|
||||
|
@ -48,13 +46,6 @@
|
|||
|
||||
// Fetches the form schema from this form's dataSource
|
||||
const fetchSchema = async dataSource => {
|
||||
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||
if (!loaded) {
|
||||
loaded = true
|
||||
}
|
||||
}
|
||||
|
||||
const fetchTable = async dataSource => {
|
||||
if (dataSource?.tableId && dataSource?.type !== "query") {
|
||||
try {
|
||||
table = await API.fetchTableDefinition(dataSource.tableId)
|
||||
|
@ -62,29 +53,32 @@
|
|||
table = null
|
||||
}
|
||||
}
|
||||
const res = await fetchDatasourceSchema(dataSource)
|
||||
schema = res || {}
|
||||
}
|
||||
|
||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||
$: resetKey = Helpers.hashString(
|
||||
JSON.stringify(initialValues) + JSON.stringify(dataSource) + disabled
|
||||
!!schema +
|
||||
JSON.stringify(initialValues) +
|
||||
JSON.stringify(dataSource) +
|
||||
disabled
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if loaded}
|
||||
{#key resetKey}
|
||||
<InnerForm
|
||||
{dataSource}
|
||||
{theme}
|
||||
{size}
|
||||
{disabled}
|
||||
{actionType}
|
||||
{schema}
|
||||
{table}
|
||||
{initialValues}
|
||||
{disableValidation}
|
||||
{editAutoColumns}
|
||||
>
|
||||
<slot />
|
||||
</InnerForm>
|
||||
{/key}
|
||||
{/if}
|
||||
{#key resetKey}
|
||||
<InnerForm
|
||||
{dataSource}
|
||||
{theme}
|
||||
{size}
|
||||
{disabled}
|
||||
{actionType}
|
||||
{schema}
|
||||
{table}
|
||||
{initialValues}
|
||||
{disableValidation}
|
||||
{editAutoColumns}
|
||||
>
|
||||
<slot />
|
||||
</InnerForm>
|
||||
{/key}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { derived } from "svelte/store"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import { devToolsStore } from "../devTools.js"
|
||||
import { authStore } from "../auth.js"
|
||||
|
||||
|
@ -6,6 +7,10 @@ import { authStore } from "../auth.js"
|
|||
export const currentRole = derived(
|
||||
[devToolsStore, authStore],
|
||||
([$devToolsStore, $authStore]) => {
|
||||
return ($devToolsStore.enabled && $devToolsStore.role) || $authStore?.roleId
|
||||
return (
|
||||
($devToolsStore.enabled && $devToolsStore.role) ||
|
||||
$authStore?.roleId ||
|
||||
Constants.Roles.PUBLIC
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -11,18 +11,25 @@ export const createSidePanelStore = () => {
|
|||
open: $store.contentId != null,
|
||||
}
|
||||
})
|
||||
let timeout
|
||||
|
||||
const open = id => {
|
||||
clearTimeout(timeout)
|
||||
store.update(state => {
|
||||
state.contentId = id
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
// Delay closing by 50ms to avoid toggling visibility when cycling though
|
||||
// records
|
||||
const close = () => {
|
||||
store.update(state => {
|
||||
state.contentId = null
|
||||
return state
|
||||
})
|
||||
timeout = setTimeout(() => {
|
||||
store.update(state => {
|
||||
state.contentId = null
|
||||
return state
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -18,7 +18,8 @@ import { enrichDataBindings } from "./enrichDataBinding"
|
|||
import { Helpers } from "@budibase/bbui"
|
||||
|
||||
const saveRowHandler = async (action, context) => {
|
||||
const { fields, providerId, tableId } = action.parameters
|
||||
const { fields, providerId, tableId, notificationOverride } =
|
||||
action.parameters
|
||||
let payload
|
||||
if (providerId) {
|
||||
payload = { ...context[providerId] }
|
||||
|
@ -35,7 +36,10 @@ const saveRowHandler = async (action, context) => {
|
|||
}
|
||||
try {
|
||||
const row = await API.saveRow(payload)
|
||||
notificationStore.actions.success("Row saved")
|
||||
|
||||
if (!notificationOverride) {
|
||||
notificationStore.actions.success("Row saved")
|
||||
}
|
||||
|
||||
// Refresh related datasources
|
||||
await dataSourceStore.actions.invalidateDataSource(row.tableId, {
|
||||
|
@ -50,7 +54,8 @@ const saveRowHandler = async (action, context) => {
|
|||
}
|
||||
|
||||
const duplicateRowHandler = async (action, context) => {
|
||||
const { fields, providerId, tableId } = action.parameters
|
||||
const { fields, providerId, tableId, notificationOverride } =
|
||||
action.parameters
|
||||
if (providerId) {
|
||||
let payload = { ...context[providerId] }
|
||||
if (fields) {
|
||||
|
@ -65,7 +70,9 @@ const duplicateRowHandler = async (action, context) => {
|
|||
delete payload._rev
|
||||
try {
|
||||
const row = await API.saveRow(payload)
|
||||
notificationStore.actions.success("Row saved")
|
||||
if (!notificationOverride) {
|
||||
notificationStore.actions.success("Row saved")
|
||||
}
|
||||
|
||||
// Refresh related datasources
|
||||
await dataSourceStore.actions.invalidateDataSource(row.tableId, {
|
||||
|
@ -81,11 +88,13 @@ const duplicateRowHandler = async (action, context) => {
|
|||
}
|
||||
|
||||
const deleteRowHandler = async action => {
|
||||
const { tableId, revId, rowId } = action.parameters
|
||||
const { tableId, revId, rowId, notificationOverride } = action.parameters
|
||||
if (tableId && rowId) {
|
||||
try {
|
||||
await API.deleteRow({ tableId, rowId, revId })
|
||||
notificationStore.actions.success("Row deleted")
|
||||
if (!notificationOverride) {
|
||||
notificationStore.actions.success("Row deleted")
|
||||
}
|
||||
|
||||
// Refresh related datasources
|
||||
await dataSourceStore.actions.invalidateDataSource(tableId, {
|
||||
|
@ -99,14 +108,16 @@ const deleteRowHandler = async action => {
|
|||
}
|
||||
|
||||
const triggerAutomationHandler = async action => {
|
||||
const { fields } = action.parameters
|
||||
const { fields, notificationOverride } = action.parameters
|
||||
if (fields) {
|
||||
try {
|
||||
await API.triggerAutomation({
|
||||
automationId: action.parameters.automationId,
|
||||
fields,
|
||||
})
|
||||
notificationStore.actions.success("Automation triggered")
|
||||
if (!notificationOverride) {
|
||||
notificationStore.actions.success("Automation triggered")
|
||||
}
|
||||
} catch (error) {
|
||||
// Abort next actions
|
||||
return false
|
||||
|
@ -120,7 +131,8 @@ const navigationHandler = action => {
|
|||
}
|
||||
|
||||
const queryExecutionHandler = async action => {
|
||||
const { datasourceId, queryId, queryParams } = action.parameters
|
||||
const { datasourceId, queryId, queryParams, notificationOverride } =
|
||||
action.parameters
|
||||
try {
|
||||
const query = await API.fetchQueryDefinition(queryId)
|
||||
if (query?.datasourceId == null) {
|
||||
|
@ -136,7 +148,9 @@ const queryExecutionHandler = async action => {
|
|||
// Trigger a notification and invalidate the datasource as long as this
|
||||
// was not a readable query
|
||||
if (!query.readable) {
|
||||
notificationStore.actions.success("Query executed successfully")
|
||||
if (!notificationOverride) {
|
||||
notificationStore.actions.success("Query executed successfully")
|
||||
}
|
||||
await dataSourceStore.actions.invalidateDataSource(query.datasourceId)
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@budibase/frontend-core",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"description": "Budibase frontend core libraries used in builder and client",
|
||||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.1.40-alpha.7",
|
||||
"@budibase/bbui": "2.1.46-alpha.1",
|
||||
"lodash": "^4.17.21",
|
||||
"svelte": "^3.46.2"
|
||||
}
|
||||
|
|
|
@ -28,11 +28,11 @@ export const OperatorOptions = {
|
|||
},
|
||||
MoreThan: {
|
||||
value: "rangeLow",
|
||||
label: "More than",
|
||||
label: "More than or equal to",
|
||||
},
|
||||
LessThan: {
|
||||
value: "rangeHigh",
|
||||
label: "Less than",
|
||||
label: "Less than or equal to",
|
||||
},
|
||||
Contains: {
|
||||
value: "contains",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/sdk",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"description": "Budibase Public API SDK",
|
||||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "2.1.40-alpha.7",
|
||||
"version": "2.1.46-alpha.1",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -43,11 +43,11 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "10.0.3",
|
||||
"@budibase/backend-core": "2.1.40-alpha.7",
|
||||
"@budibase/client": "2.1.40-alpha.7",
|
||||
"@budibase/pro": "2.1.40-alpha.7",
|
||||
"@budibase/string-templates": "2.1.40-alpha.7",
|
||||
"@budibase/types": "2.1.40-alpha.7",
|
||||
"@budibase/backend-core": "2.1.46-alpha.1",
|
||||
"@budibase/client": "2.1.46-alpha.1",
|
||||
"@budibase/pro": "2.1.46-alpha.0",
|
||||
"@budibase/string-templates": "2.1.46-alpha.1",
|
||||
"@budibase/types": "2.1.46-alpha.1",
|
||||
"@bull-board/api": "3.7.0",
|
||||
"@bull-board/koa": "3.9.4",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
|
|
|
@ -11,7 +11,7 @@ GO
|
|||
CREATE TABLE products
|
||||
(
|
||||
id int IDENTITY(1,1),
|
||||
name varchar (20),
|
||||
name varchar (20) NOT NULL,
|
||||
description varchar(30),
|
||||
CONSTRAINT pk_products PRIMARY KEY NONCLUSTERED (id)
|
||||
);
|
||||
|
@ -22,7 +22,7 @@ GO
|
|||
CREATE TABLE tasks
|
||||
(
|
||||
taskid int IDENTITY(1,1),
|
||||
taskname varchar (20),
|
||||
taskname varchar (20) NOT NULL,
|
||||
productid int,
|
||||
CONSTRAINT pk_tasks PRIMARY KEY NONCLUSTERED (taskid),
|
||||
CONSTRAINT fk_products FOREIGN KEY (productid) REFERENCES products (id),
|
||||
|
@ -33,7 +33,7 @@ IF OBJECT_ID ('dbo.people', 'U') IS NOT NULL
|
|||
GO
|
||||
CREATE TABLE people
|
||||
(
|
||||
name varchar(30),
|
||||
name varchar(30) NOT NULL,
|
||||
age varchar(20),
|
||||
CONSTRAINT pk_people PRIMARY KEY NONCLUSTERED (name, age)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
SELECT 'CREATE DATABASE main'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'main')\gexec
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public."Employee"
|
||||
(
|
||||
id integer NOT NULL,
|
||||
name text COLLATE pg_catalog."default",
|
||||
CONSTRAINT "Employee_pkey" PRIMARY KEY (id)
|
||||
)
|
||||
WITH (
|
||||
OIDS = FALSE
|
||||
);
|
||||
|
||||
INSERT INTO public."Employee" ("id", "name") VALUES (1, 'Alice');
|
||||
INSERT INTO public."Employee" ("id", "name") VALUES (2, 'Bob');
|
||||
CREATE TABLE IF NOT EXISTS public."Skills"
|
||||
(
|
||||
id integer NOT NULL,
|
||||
name text COLLATE pg_catalog."default",
|
||||
CONSTRAINT "Skills_pkey" PRIMARY KEY (id)
|
||||
)
|
||||
WITH (
|
||||
OIDS = FALSE
|
||||
);
|
||||
|
||||
INSERT INTO public."Skills" ("id", "name") VALUES (1, 'Docker');
|
||||
INSERT INTO public."Skills" ("id", "name") VALUES (2, 'Microservices');
|
||||
INSERT INTO public."Skills" ("id", "name") VALUES (3, 'Kubernetes');
|
||||
INSERT INTO public."Skills" ("id", "name") VALUES (4, 'Spring');
|
||||
CREATE TABLE IF NOT EXISTS public."jt_employee_skills_Skills_employee"
|
||||
(
|
||||
employee_id integer,
|
||||
skills_id integer,
|
||||
id integer NOT NULL,
|
||||
CONSTRAINT "jt_employee_skills_Skills_employee_pkey" PRIMARY KEY (id)
|
||||
)
|
||||
WITH (
|
||||
OIDS = FALSE
|
||||
);
|
||||
|
||||
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (1, 1, 1);
|
||||
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (2, 1, 2);
|
||||
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (3, 1, 3);
|
||||
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (4, 2, 2);
|
||||
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (5, 2, 3);
|
||||
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (6, 2, 4);
|
|
@ -26,7 +26,6 @@ import {
|
|||
} from "@budibase/backend-core"
|
||||
import { USERS_TABLE_SCHEMA } from "../../constants"
|
||||
import { buildDefaultDocs } from "../../db/defaultData/datasource_bb_default"
|
||||
|
||||
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||
import {
|
||||
clientLibraryPath,
|
||||
|
@ -39,18 +38,22 @@ import {
|
|||
backupClientLibrary,
|
||||
revertClientLibrary,
|
||||
} from "../../utilities/fileSystem/clientLibrary"
|
||||
import { syncGlobalUsers } from "./user"
|
||||
import { cleanupAutomations } from "../../automations/utils"
|
||||
import { checkAppMetadata } from "../../automations/logging"
|
||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||
import { quotas, groups } from "@budibase/pro"
|
||||
import { App, Layout, Screen, MigrationType } from "@budibase/types"
|
||||
import {
|
||||
App,
|
||||
Layout,
|
||||
Screen,
|
||||
MigrationType,
|
||||
BBContext,
|
||||
Database,
|
||||
} from "@budibase/types"
|
||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||
import { enrichPluginURLs } from "../../utilities/plugins"
|
||||
import sdk from "../../sdk"
|
||||
|
||||
const URL_REGEX_SLASH = /\/|\\/g
|
||||
|
||||
// utility function, need to do away with this
|
||||
async function getLayouts() {
|
||||
const db = context.getAppDB()
|
||||
|
@ -74,29 +77,18 @@ async function getScreens() {
|
|||
).rows.map((row: any) => row.doc)
|
||||
}
|
||||
|
||||
function getUserRoleId(ctx: any) {
|
||||
return !ctx.user.role || !ctx.user.role._id
|
||||
function getUserRoleId(ctx: BBContext) {
|
||||
return !ctx.user?.role || !ctx.user.role._id
|
||||
? roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||
: ctx.user.role._id
|
||||
}
|
||||
|
||||
export const getAppUrl = (ctx: any) => {
|
||||
// construct the url
|
||||
let url
|
||||
if (ctx.request.body.url) {
|
||||
// if the url is provided, use that
|
||||
url = encodeURI(ctx.request.body.url)
|
||||
} else if (ctx.request.body.name) {
|
||||
// otherwise use the name
|
||||
url = encodeURI(`${ctx.request.body.name}`)
|
||||
}
|
||||
if (url) {
|
||||
url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const checkAppUrl = (ctx: any, apps: any, url: any, currentAppId?: string) => {
|
||||
function checkAppUrl(
|
||||
ctx: BBContext,
|
||||
apps: App[],
|
||||
url: string,
|
||||
currentAppId?: string
|
||||
) {
|
||||
if (currentAppId) {
|
||||
apps = apps.filter((app: any) => app.appId !== currentAppId)
|
||||
}
|
||||
|
@ -105,12 +97,12 @@ const checkAppUrl = (ctx: any, apps: any, url: any, currentAppId?: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const checkAppName = (
|
||||
ctx: any,
|
||||
apps: any,
|
||||
name: any,
|
||||
function checkAppName(
|
||||
ctx: BBContext,
|
||||
apps: App[],
|
||||
name: string,
|
||||
currentAppId?: string
|
||||
) => {
|
||||
) {
|
||||
// TODO: Replace with Joi
|
||||
if (!name) {
|
||||
ctx.throw(400, "Name is required")
|
||||
|
@ -165,14 +157,14 @@ async function createInstance(template: any, includeSampleData: boolean) {
|
|||
return { _id: appId }
|
||||
}
|
||||
|
||||
const addDefaultTables = async (db: any) => {
|
||||
async function addDefaultTables(db: Database) {
|
||||
const defaultDbDocs = buildDefaultDocs()
|
||||
|
||||
// add in the default db data docs - tables, datasource, rows and links
|
||||
await db.bulkDocs([...defaultDbDocs])
|
||||
}
|
||||
|
||||
export const fetch = async (ctx: any) => {
|
||||
export async function fetch(ctx: BBContext) {
|
||||
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
||||
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
||||
const apps = (await dbCore.getAllApps({ dev, all })) as App[]
|
||||
|
@ -197,7 +189,7 @@ export const fetch = async (ctx: any) => {
|
|||
ctx.body = await checkAppMetadata(apps)
|
||||
}
|
||||
|
||||
export const fetchAppDefinition = async (ctx: any) => {
|
||||
export async function fetchAppDefinition(ctx: BBContext) {
|
||||
const layouts = await getLayouts()
|
||||
const userRoleId = getUserRoleId(ctx)
|
||||
const accessController = new roles.AccessController()
|
||||
|
@ -212,7 +204,7 @@ export const fetchAppDefinition = async (ctx: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const fetchAppPackage = async (ctx: any) => {
|
||||
export async function fetchAppPackage(ctx: BBContext) {
|
||||
const db = context.getAppDB()
|
||||
let application = await db.get(DocumentType.APP_METADATA)
|
||||
const layouts = await getLayouts()
|
||||
|
@ -222,7 +214,7 @@ export const fetchAppPackage = async (ctx: any) => {
|
|||
application.usedPlugins = enrichPluginURLs(application.usedPlugins)
|
||||
|
||||
// Only filter screens if the user is not a builder
|
||||
if (!(ctx.user.builder && ctx.user.builder.global)) {
|
||||
if (!(ctx.user?.builder && ctx.user.builder.global)) {
|
||||
const userRoleId = getUserRoleId(ctx)
|
||||
const accessController = new roles.AccessController()
|
||||
screens = await accessController.checkScreensAccess(screens, userRoleId)
|
||||
|
@ -236,11 +228,12 @@ export const fetchAppPackage = async (ctx: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
const performAppCreate = async (ctx: any) => {
|
||||
const apps = await dbCore.getAllApps({ dev: true })
|
||||
const name = ctx.request.body.name
|
||||
async function performAppCreate(ctx: BBContext) {
|
||||
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
|
||||
const name = ctx.request.body.name,
|
||||
possibleUrl = ctx.request.body.url
|
||||
checkAppName(ctx, apps, name)
|
||||
const url = getAppUrl(ctx)
|
||||
const url = sdk.applications.getAppUrl({ name, url: possibleUrl })
|
||||
checkAppUrl(ctx, apps, url)
|
||||
|
||||
const { useTemplate, templateKey, templateString } = ctx.request.body
|
||||
|
@ -331,7 +324,7 @@ const performAppCreate = async (ctx: any) => {
|
|||
return newApplication
|
||||
}
|
||||
|
||||
const creationEvents = async (request: any, app: App) => {
|
||||
async function creationEvents(request: any, app: App) {
|
||||
let creationFns: ((app: App) => Promise<void>)[] = []
|
||||
|
||||
const body = request.body
|
||||
|
@ -356,7 +349,7 @@ const creationEvents = async (request: any, app: App) => {
|
|||
}
|
||||
}
|
||||
|
||||
const appPostCreate = async (ctx: any, app: App) => {
|
||||
async function appPostCreate(ctx: BBContext, app: App) {
|
||||
const tenantId = tenancy.getTenantId()
|
||||
await migrations.backPopulateMigrations({
|
||||
type: MigrationType.APP,
|
||||
|
@ -377,7 +370,7 @@ const appPostCreate = async (ctx: any, app: App) => {
|
|||
if (err.code && err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||
// this import resulted in row usage exceeding the quota
|
||||
// delete the app
|
||||
// skip pre and post steps as no rows have been added to quotas yet
|
||||
// skip pre- and post-steps as no rows have been added to quotas yet
|
||||
ctx.params.appId = app.appId
|
||||
await destroyApp(ctx)
|
||||
}
|
||||
|
@ -387,7 +380,7 @@ const appPostCreate = async (ctx: any, app: App) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const create = async (ctx: any) => {
|
||||
export async function create(ctx: BBContext) {
|
||||
const newApplication = await quotas.addApp(() => performAppCreate(ctx))
|
||||
await appPostCreate(ctx, newApplication)
|
||||
await cache.bustCache(cache.CacheKey.CHECKLIST)
|
||||
|
@ -397,14 +390,15 @@ export const create = async (ctx: any) => {
|
|||
|
||||
// This endpoint currently operates as a PATCH rather than a PUT
|
||||
// Thus name and url fields are handled only if present
|
||||
export const update = async (ctx: any) => {
|
||||
const apps = await dbCore.getAllApps({ dev: true })
|
||||
export async function update(ctx: BBContext) {
|
||||
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
|
||||
// validation
|
||||
const name = ctx.request.body.name
|
||||
const name = ctx.request.body.name,
|
||||
possibleUrl = ctx.request.body.url
|
||||
if (name) {
|
||||
checkAppName(ctx, apps, name, ctx.params.appId)
|
||||
}
|
||||
const url = getAppUrl(ctx)
|
||||
const url = sdk.applications.getAppUrl({ name, url: possibleUrl })
|
||||
if (url) {
|
||||
checkAppUrl(ctx, apps, url, ctx.params.appId)
|
||||
ctx.request.body.url = url
|
||||
|
@ -416,7 +410,7 @@ export const update = async (ctx: any) => {
|
|||
ctx.body = app
|
||||
}
|
||||
|
||||
export const updateClient = async (ctx: any) => {
|
||||
export async function updateClient(ctx: BBContext) {
|
||||
// Get current app version
|
||||
const db = context.getAppDB()
|
||||
const application = await db.get(DocumentType.APP_METADATA)
|
||||
|
@ -440,7 +434,7 @@ export const updateClient = async (ctx: any) => {
|
|||
ctx.body = app
|
||||
}
|
||||
|
||||
export const revertClient = async (ctx: any) => {
|
||||
export async function revertClient(ctx: BBContext) {
|
||||
// Check app can be reverted
|
||||
const db = context.getAppDB()
|
||||
const application = await db.get(DocumentType.APP_METADATA)
|
||||
|
@ -466,12 +460,15 @@ export const revertClient = async (ctx: any) => {
|
|||
ctx.body = app
|
||||
}
|
||||
|
||||
const destroyApp = async (ctx: any) => {
|
||||
async function destroyApp(ctx: BBContext) {
|
||||
let appId = ctx.params.appId
|
||||
let isUnpublish = ctx.query && ctx.query.unpublish
|
||||
|
||||
if (isUnpublish) {
|
||||
appId = dbCore.getProdAppID(appId)
|
||||
const devAppId = dbCore.getDevAppID(appId)
|
||||
// sync before removing the published app
|
||||
await sdk.applications.syncApp(devAppId)
|
||||
}
|
||||
|
||||
const db = isUnpublish ? context.getProdAppDB() : context.getAppDB()
|
||||
|
@ -501,12 +498,12 @@ const destroyApp = async (ctx: any) => {
|
|||
return result
|
||||
}
|
||||
|
||||
const preDestroyApp = async (ctx: any) => {
|
||||
async function preDestroyApp(ctx: BBContext) {
|
||||
const { rows } = await getUniqueRows([ctx.params.appId])
|
||||
ctx.rowCount = rows.length
|
||||
}
|
||||
|
||||
const postDestroyApp = async (ctx: any) => {
|
||||
async function postDestroyApp(ctx: BBContext) {
|
||||
const rowCount = ctx.rowCount
|
||||
await groups.cleanupApp(ctx.params.appId)
|
||||
if (rowCount) {
|
||||
|
@ -514,7 +511,7 @@ const postDestroyApp = async (ctx: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const destroy = async (ctx: any) => {
|
||||
export async function destroy(ctx: BBContext) {
|
||||
await preDestroyApp(ctx)
|
||||
const result = await destroyApp(ctx)
|
||||
await postDestroyApp(ctx)
|
||||
|
@ -522,62 +519,16 @@ export const destroy = async (ctx: any) => {
|
|||
ctx.body = result
|
||||
}
|
||||
|
||||
export const sync = async (ctx: any, next: any) => {
|
||||
if (env.DISABLE_AUTO_PROD_APP_SYNC) {
|
||||
ctx.status = 200
|
||||
ctx.body = {
|
||||
message:
|
||||
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable.",
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
export async function sync(ctx: BBContext) {
|
||||
const appId = ctx.params.appId
|
||||
if (!dbCore.isDevAppID(appId)) {
|
||||
ctx.throw(400, "This action cannot be performed for production apps")
|
||||
}
|
||||
|
||||
// replicate prod to dev
|
||||
const prodAppId = dbCore.getProdAppID(appId)
|
||||
|
||||
// specific case, want to make sure setup is skipped
|
||||
const prodDb = context.getProdAppDB({ skip_setup: true })
|
||||
const exists = await prodDb.exists()
|
||||
if (!exists) {
|
||||
// the database doesn't exist. Don't replicate
|
||||
ctx.status = 200
|
||||
ctx.body = {
|
||||
message: "App sync not required, app not deployed.",
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
const replication = new dbCore.Replication({
|
||||
source: prodAppId,
|
||||
target: appId,
|
||||
})
|
||||
let error
|
||||
try {
|
||||
await replication.replicate(replication.appReplicateOpts())
|
||||
} catch (err) {
|
||||
error = err
|
||||
} finally {
|
||||
await replication.close()
|
||||
}
|
||||
|
||||
// sync the users
|
||||
await syncGlobalUsers()
|
||||
|
||||
if (error) {
|
||||
ctx.throw(400, error)
|
||||
} else {
|
||||
ctx.body = {
|
||||
message: "App sync completed successfully.",
|
||||
}
|
||||
ctx.body = await sdk.applications.syncApp(appId)
|
||||
} catch (err: any) {
|
||||
ctx.throw(err.status || 400, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const updateAppPackage = async (appPackage: any, appId: any) => {
|
||||
export async function updateAppPackage(appPackage: any, appId: any) {
|
||||
return context.doInAppContext(appId, async () => {
|
||||
const db = context.getAppDB()
|
||||
const application = await db.get(DocumentType.APP_METADATA)
|
||||
|
@ -598,7 +549,7 @@ export const updateAppPackage = async (appPackage: any, appId: any) => {
|
|||
})
|
||||
}
|
||||
|
||||
const migrateAppNavigation = async () => {
|
||||
async function migrateAppNavigation() {
|
||||
const db = context.getAppDB()
|
||||
const existing: App = await db.get(DocumentType.APP_METADATA)
|
||||
const layouts: Layout[] = await getLayouts()
|
||||
|
|
|
@ -22,6 +22,7 @@ async function createApp(appName: string, appDirectory: string) {
|
|||
},
|
||||
},
|
||||
}
|
||||
// @ts-ignore
|
||||
return create(ctx)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "../../../automations/utils"
|
||||
import { backups } from "@budibase/pro"
|
||||
import { AppBackupTrigger } from "@budibase/types"
|
||||
import sdk from "../../../sdk"
|
||||
|
||||
// the max time we can wait for an invalidation to complete before considering it failed
|
||||
const MAX_PENDING_TIME_MS = 30 * 60000
|
||||
|
@ -86,6 +87,11 @@ async function initDeployedApp(prodAppId: any) {
|
|||
}
|
||||
await Promise.all(promises)
|
||||
console.log("Enabled cron triggers for deployed app..")
|
||||
// sync the automations back to the dev DB - since there is now cron
|
||||
// information attached
|
||||
await sdk.applications.syncApp(dbCore.getDevAppID(prodAppId), {
|
||||
automationOnly: true,
|
||||
})
|
||||
}
|
||||
|
||||
async function deployApp(deployment: any, userId: string) {
|
||||
|
|
|
@ -5,9 +5,18 @@ import {
|
|||
saveGlobalUser,
|
||||
} from "../../../utilities/workerRequests"
|
||||
import { publicApiUserFix } from "../../../utilities/users"
|
||||
import { db as dbCore } from "@budibase/backend-core"
|
||||
import { search as stringSearch } from "./utils"
|
||||
import { BBContext, User } from "@budibase/types"
|
||||
|
||||
function getUser(ctx: any, userId?: string) {
|
||||
function isLoggedInUser(ctx: BBContext, user: User) {
|
||||
const loggedInId = ctx.user?._id
|
||||
const globalUserId = dbCore.getGlobalIDFromUserMetadataID(loggedInId!)
|
||||
// check both just incase
|
||||
return globalUserId === user._id || loggedInId === user._id
|
||||
}
|
||||
|
||||
function getUser(ctx: BBContext, userId?: string) {
|
||||
if (userId) {
|
||||
ctx.params = { userId }
|
||||
} else if (!ctx.params?.userId) {
|
||||
|
@ -16,37 +25,47 @@ function getUser(ctx: any, userId?: string) {
|
|||
return readGlobalUser(ctx)
|
||||
}
|
||||
|
||||
export async function search(ctx: any, next: any) {
|
||||
export async function search(ctx: BBContext, next: any) {
|
||||
const { name } = ctx.request.body
|
||||
const users = await allGlobalUsers(ctx)
|
||||
ctx.body = stringSearch(users, name, "email")
|
||||
await next()
|
||||
}
|
||||
|
||||
export async function create(ctx: any, next: any) {
|
||||
export async function create(ctx: BBContext, next: any) {
|
||||
const response = await saveGlobalUser(publicApiUserFix(ctx))
|
||||
ctx.body = await getUser(ctx, response._id)
|
||||
await next()
|
||||
}
|
||||
|
||||
export async function read(ctx: any, next: any) {
|
||||
export async function read(ctx: BBContext, next: any) {
|
||||
ctx.body = await readGlobalUser(ctx)
|
||||
await next()
|
||||
}
|
||||
|
||||
export async function update(ctx: any, next: any) {
|
||||
export async function update(ctx: BBContext, next: any) {
|
||||
const user = await readGlobalUser(ctx)
|
||||
ctx.request.body = {
|
||||
...ctx.request.body,
|
||||
_rev: user._rev,
|
||||
}
|
||||
// disallow updating your own role - always overwrite with DB roles
|
||||
if (isLoggedInUser(ctx, user)) {
|
||||
ctx.request.body.builder = user.builder
|
||||
ctx.request.body.admin = user.admin
|
||||
ctx.request.body.roles = user.roles
|
||||
}
|
||||
const response = await saveGlobalUser(publicApiUserFix(ctx))
|
||||
ctx.body = await getUser(ctx, response._id)
|
||||
await next()
|
||||
}
|
||||
|
||||
export async function destroy(ctx: any, next: any) {
|
||||
export async function destroy(ctx: BBContext, next: any) {
|
||||
const user = await getUser(ctx)
|
||||
// disallow deleting yourself
|
||||
if (isLoggedInUser(ctx, user)) {
|
||||
ctx.throw(405, "Cannot delete user using its own API key.")
|
||||
}
|
||||
await deleteGlobalUser(ctx)
|
||||
ctx.body = user
|
||||
await next()
|
||||
|
|
|
@ -588,7 +588,10 @@ export class ExternalRequest {
|
|||
for (let [colName, { isMany, rows, tableId }] of Object.entries(related)) {
|
||||
const table: Table | undefined = this.getTable(tableId)
|
||||
// if its not the foreign key skip it, nothing to do
|
||||
if (!table || (table.primary && table.primary.indexOf(colName) !== -1)) {
|
||||
if (
|
||||
!table ||
|
||||
(!isMany && table.primary && table.primary.indexOf(colName) !== -1)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
for (let row of rows) {
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import {
|
||||
generateUserMetadataID,
|
||||
getUserMetadataParams,
|
||||
generateUserFlagID,
|
||||
} from "../../db/utils"
|
||||
import { generateUserMetadataID, generateUserFlagID } from "../../db/utils"
|
||||
import { InternalTables } from "../../db/utils"
|
||||
import { getGlobalUsers, getRawGlobalUser } from "../../utilities/global"
|
||||
import { getFullUser } from "../../utilities/users"
|
||||
import { isEqual } from "lodash"
|
||||
import {
|
||||
context,
|
||||
constants,
|
||||
|
@ -14,59 +9,7 @@ import {
|
|||
db as dbCore,
|
||||
} from "@budibase/backend-core"
|
||||
import { BBContext, User } from "@budibase/types"
|
||||
|
||||
async function rawMetadata() {
|
||||
const db = context.getAppDB()
|
||||
return (
|
||||
await db.allDocs(
|
||||
getUserMetadataParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
).rows.map(row => row.doc)
|
||||
}
|
||||
|
||||
function combineMetadataAndUser(user: any, metadata: any) {
|
||||
// skip users with no access
|
||||
if (user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC) {
|
||||
return null
|
||||
}
|
||||
delete user._rev
|
||||
const metadataId = generateUserMetadataID(user._id)
|
||||
const newDoc = {
|
||||
...user,
|
||||
_id: metadataId,
|
||||
tableId: InternalTables.USER_METADATA,
|
||||
}
|
||||
const found = Array.isArray(metadata)
|
||||
? metadata.find(doc => doc._id === metadataId)
|
||||
: metadata
|
||||
// copy rev over for the purposes of equality check
|
||||
if (found) {
|
||||
newDoc._rev = found._rev
|
||||
}
|
||||
if (found == null || !isEqual(newDoc, found)) {
|
||||
return {
|
||||
...found,
|
||||
...newDoc,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function syncGlobalUsers() {
|
||||
// sync user metadata
|
||||
const db = context.getAppDB()
|
||||
const [users, metadata] = await Promise.all([getGlobalUsers(), rawMetadata()])
|
||||
const toWrite = []
|
||||
for (let user of users) {
|
||||
const combined = await combineMetadataAndUser(user, metadata)
|
||||
if (combined) {
|
||||
toWrite.push(combined)
|
||||
}
|
||||
}
|
||||
await db.bulkDocs(toWrite)
|
||||
}
|
||||
import sdk from "../../sdk"
|
||||
|
||||
export async function syncUser(ctx: BBContext) {
|
||||
let deleting = false,
|
||||
|
@ -123,7 +66,7 @@ export async function syncUser(ctx: BBContext) {
|
|||
metadata.roleId = roleId
|
||||
}
|
||||
let combined = !deleting
|
||||
? combineMetadataAndUser(user, metadata)
|
||||
? sdk.users.combineMetadataAndUser(user, metadata)
|
||||
: {
|
||||
...metadata,
|
||||
status: constants.UserStatus.INACTIVE,
|
||||
|
@ -143,7 +86,7 @@ export async function syncUser(ctx: BBContext) {
|
|||
|
||||
export async function fetchMetadata(ctx: BBContext) {
|
||||
const global = await getGlobalUsers()
|
||||
const metadata = await rawMetadata()
|
||||
const metadata = await sdk.users.rawUserMetadata()
|
||||
const users = []
|
||||
for (let user of global) {
|
||||
// find the metadata that matches up to the global ID
|
||||
|
|
|
@ -1,41 +1,23 @@
|
|||
const jestOpenAPI = require("jest-openapi").default
|
||||
const generateSchema = require("../../../../../specs/generate")
|
||||
const setup = require("../../tests/utilities")
|
||||
const { checkSlashesInUrl } = require("../../../../utilities")
|
||||
const { generateMakeRequest } = require("./utils")
|
||||
|
||||
const yamlPath = generateSchema()
|
||||
jestOpenAPI(yamlPath)
|
||||
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let apiKey, table, app
|
||||
let apiKey, table, app, makeRequest
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await config.init()
|
||||
table = await config.updateTable()
|
||||
apiKey = await config.generateApiKey()
|
||||
makeRequest = generateMakeRequest(apiKey, setup)
|
||||
})
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
async function makeRequest(method, endpoint, body, appId = config.getAppId()) {
|
||||
const extraHeaders = {
|
||||
"x-budibase-api-key": apiKey,
|
||||
}
|
||||
if (appId) {
|
||||
extraHeaders["x-budibase-app-id"] = appId
|
||||
}
|
||||
const req = request
|
||||
[method](checkSlashesInUrl(`/api/public/v1/${endpoint}`))
|
||||
.set(config.defaultHeaders(extraHeaders))
|
||||
if (body) {
|
||||
req.send(body)
|
||||
}
|
||||
const res = await req
|
||||
expect(res.body).toBeDefined()
|
||||
return res
|
||||
}
|
||||
|
||||
describe("check the applications endpoints", () => {
|
||||
it("should allow retrieving applications through search", async () => {
|
||||
const res = await makeRequest("post", "/applications/search")
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
const setup = require("../../tests/utilities")
|
||||
const { generateMakeRequest } = require("./utils")
|
||||
|
||||
const workerRequests = require("../../../../utilities/workerRequests")
|
||||
|
||||
let config = setup.getConfig()
|
||||
let apiKey, globalUser, makeRequest
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.init()
|
||||
globalUser = await config.globalUser()
|
||||
apiKey = await config.generateApiKey(globalUser._id)
|
||||
makeRequest = generateMakeRequest(apiKey, setup)
|
||||
workerRequests.readGlobalUser.mockReturnValue(globalUser)
|
||||
})
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
describe("check user endpoints", () => {
|
||||
it("should not allow a user to update their own roles", async () => {
|
||||
const res = await makeRequest("put", `/users/${globalUser._id}`, {
|
||||
...globalUser,
|
||||
roles: {
|
||||
"app_1": "ADMIN",
|
||||
}
|
||||
})
|
||||
expect(workerRequests.saveGlobalUser.mock.lastCall[0].body.data.roles["app_1"]).toBeUndefined()
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body.data.roles["app_1"]).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should not allow a user to delete themselves", async () => {
|
||||
const res = await makeRequest("delete", `/users/${globalUser._id}`)
|
||||
expect(res.status).toBe(405)
|
||||
expect(workerRequests.deleteGlobalUser.mock.lastCall).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { checkSlashesInUrl } from "../../../../utilities"
|
||||
|
||||
export function generateMakeRequest(apiKey: string, setup: any) {
|
||||
const request = setup.getRequest()
|
||||
const config = setup.getConfig()
|
||||
return async (
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body?: any,
|
||||
intAppId: string = config.getAppId()
|
||||
) => {
|
||||
const extraHeaders: any = {
|
||||
"x-budibase-api-key": apiKey,
|
||||
}
|
||||
if (intAppId) {
|
||||
extraHeaders["x-budibase-app-id"] = intAppId
|
||||
}
|
||||
const req = request[method](
|
||||
checkSlashesInUrl(`/api/public/v1/${endpoint}`)
|
||||
).set(config.defaultHeaders(extraHeaders))
|
||||
if (body) {
|
||||
req.send(body)
|
||||
}
|
||||
const res = await req
|
||||
expect(res.body).toBeDefined()
|
||||
return res
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ const {
|
|||
checkBuilderEndpoint,
|
||||
} = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
const { basicScreen, basicLayout } = setup.structures
|
||||
const { AppStatus } = require("../../../db/utils")
|
||||
const { events } = require("@budibase/backend-core")
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
export interface MSSQLTablesResponse {
|
||||
TABLE_CATALOG: string
|
||||
TABLE_SCHEMA: string
|
||||
TABLE_NAME: string
|
||||
TABLE_TYPE: string
|
||||
}
|
||||
|
||||
export interface MSSQLColumn {
|
||||
IS_COMPUTED: number
|
||||
IS_IDENTITY: number
|
||||
TABLE_CATALOG: string
|
||||
TABLE_SCHEMA: string
|
||||
TABLE_NAME: string
|
||||
COLUMN_NAME: string
|
||||
ORDINAL_POSITION: number
|
||||
COLUMN_DEFAULT: null | any
|
||||
IS_NULLABLE: "NO" | "YES"
|
||||
DATA_TYPE: string
|
||||
CHARACTER_MAXIMUM_LENGTH: null | number
|
||||
CHARACTER_OCTET_LENGTH: null | number
|
||||
NUMERIC_PRECISION: null | number
|
||||
NUMERIC_PRECISION_RADIX: null | number
|
||||
NUMERIC_SCALE: null | number
|
||||
DATETIME_PRECISION: null | string
|
||||
CHARACTER_SET_CATALOG: null | string
|
||||
CHARACTER_SET_SCHEMA: null | string
|
||||
CHARACTER_SET_NAME: null | string
|
||||
COLLATION_CATALOG: null | string
|
||||
COLLATION_SCHEMA: null | string
|
||||
COLLATION_NAME: null | string
|
||||
DOMAIN_CATALOG: null | string
|
||||
DOMAIN_SCHEMA: null | string
|
||||
DOMAIN_NAME: null | string
|
||||
}
|
||||
|
||||
export interface PostgresColumn {
|
||||
table_catalog: string
|
||||
table_schema: string
|
||||
table_name: string
|
||||
column_name: string
|
||||
ordinal_position: number
|
||||
column_default: null | any
|
||||
is_nullable: "NO" | "YES"
|
||||
data_type: string
|
||||
character_maximum_length: null | number
|
||||
character_octet_length: null | number
|
||||
numeric_precision: null | number
|
||||
numeric_precision_radix: null | number
|
||||
numeric_scale: null | number
|
||||
datetime_precision: null | string
|
||||
interval_type: null | string
|
||||
interval_precision: null | string
|
||||
character_set_catalog: null | string
|
||||
character_set_schema: null | string
|
||||
character_set_name: null | string
|
||||
collation_catalog: null | string
|
||||
collation_schema: null | string
|
||||
collation_name: null | string
|
||||
domain_catalog: null | string
|
||||
domain_schema: null | string
|
||||
domain_name: null | string
|
||||
udt_catalog: string
|
||||
udt_schema: string
|
||||
udt_name: string
|
||||
scope_catalog: null | string
|
||||
scope_schema: null | string
|
||||
scope_name: null | string
|
||||
maximum_cardinality: null | string
|
||||
dtd_identifier: string
|
||||
is_self_referencing: "NO" | "YES"
|
||||
is_identity: "NO" | "YES"
|
||||
identity_generation: null | number
|
||||
identity_start: null | number
|
||||
identity_increment: null | number
|
||||
identity_maximum: null | number
|
||||
identity_minimum: null | number
|
||||
identity_cycle: "NO" | "YES"
|
||||
is_generated: "NEVER"
|
||||
generation_expression: null | string
|
||||
is_updatable: "NO" | "YES"
|
||||
}
|
||||
|
||||
export interface MySQLColumn {
|
||||
Field: string
|
||||
Type: string
|
||||
Null: "NO" | "YES"
|
||||
Key: "PRI" | "MUL" | ""
|
||||
Default: null | any
|
||||
Extra: null | string
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw query response
|
||||
*/
|
||||
export interface OracleColumnsResponse {
|
||||
TABLE_NAME: string
|
||||
COLUMN_NAME: string
|
||||
DATA_TYPE: string
|
||||
DATA_DEFAULT: null | string
|
||||
COLUMN_ID: number
|
||||
CONSTRAINT_NAME: null | string
|
||||
CONSTRAINT_TYPE: null | string
|
||||
R_CONSTRAINT_NAME: null | string
|
||||
SEARCH_CONDITION: null | string
|
||||
}
|
||||
|
||||
/**
|
||||
* An oracle constraint
|
||||
*/
|
||||
export interface OracleConstraint {
|
||||
name: string
|
||||
type: string
|
||||
relatedConstraintName: null | string
|
||||
searchCondition: null | string
|
||||
}
|
||||
|
||||
/**
|
||||
* An oracle column and it's related constraints
|
||||
*/
|
||||
export interface OracleColumn {
|
||||
name: string
|
||||
type: string
|
||||
default: null | string
|
||||
id: number
|
||||
constraints: { [key: string]: OracleConstraint }
|
||||
}
|
||||
|
||||
/**
|
||||
* An oracle table and it's related columns
|
||||
*/
|
||||
export interface OracleTable {
|
||||
name: string
|
||||
columns: { [key: string]: OracleColumn }
|
||||
}
|
|
@ -17,6 +17,7 @@ import {
|
|||
SqlClient,
|
||||
} from "./utils"
|
||||
import Sql from "./base/sql"
|
||||
import { MSSQLTablesResponse, MSSQLColumn } from "./base/types"
|
||||
|
||||
const sqlServer = require("mssql")
|
||||
const DEFAULT_SCHEMA = "dbo"
|
||||
|
@ -31,13 +32,6 @@ interface MSSQLConfig {
|
|||
encrypt?: boolean
|
||||
}
|
||||
|
||||
interface TablesResponse {
|
||||
TABLE_CATALOG: string
|
||||
TABLE_SCHEMA: string
|
||||
TABLE_NAME: string
|
||||
TABLE_TYPE: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://github.com/tediousjs/node-mssql",
|
||||
plus: true,
|
||||
|
@ -210,7 +204,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
|||
*/
|
||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
||||
await this.connect()
|
||||
let tableInfo: TablesResponse[] = await this.runSQL(this.TABLES_SQL)
|
||||
let tableInfo: MSSQLTablesResponse[] = await this.runSQL(this.TABLES_SQL)
|
||||
if (tableInfo == null || !Array.isArray(tableInfo)) {
|
||||
throw "Unable to get list of tables in database"
|
||||
}
|
||||
|
@ -228,15 +222,20 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
|||
// find primary key constraints
|
||||
const constraints = await this.runSQL(this.getConstraintsSQL(tableName))
|
||||
// find the computed and identity columns (auto columns)
|
||||
const columns = await this.runSQL(this.getAutoColumnsSQL(tableName))
|
||||
const columns: MSSQLColumn[] = await this.runSQL(
|
||||
this.getAutoColumnsSQL(tableName)
|
||||
)
|
||||
const primaryKeys = constraints
|
||||
.filter(
|
||||
(constraint: any) => constraint.CONSTRAINT_TYPE === "PRIMARY KEY"
|
||||
)
|
||||
.map((constraint: any) => constraint.COLUMN_NAME)
|
||||
const autoColumns = columns
|
||||
.filter((col: any) => col.IS_COMPUTED || col.IS_IDENTITY)
|
||||
.map((col: any) => col.COLUMN_NAME)
|
||||
.filter(col => col.IS_COMPUTED || col.IS_IDENTITY)
|
||||
.map(col => col.COLUMN_NAME)
|
||||
const requiredColumns = columns
|
||||
.filter(col => col.IS_NULLABLE === "NO")
|
||||
.map(col => col.COLUMN_NAME)
|
||||
|
||||
let schema: TableSchema = {}
|
||||
for (let def of definition) {
|
||||
|
@ -245,8 +244,11 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
|||
continue
|
||||
}
|
||||
schema[name] = {
|
||||
autocolumn: !!autoColumns.find((col: string) => col === name),
|
||||
autocolumn: !!autoColumns.find(col => col === name),
|
||||
name: name,
|
||||
constraints: {
|
||||
presence: requiredColumns.find(col => col === name),
|
||||
},
|
||||
...convertSqlType(def.DATA_TYPE),
|
||||
externalType: def.DATA_TYPE,
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
import dayjs from "dayjs"
|
||||
const { NUMBER_REGEX } = require("../utilities")
|
||||
import Sql from "./base/sql"
|
||||
import { MySQLColumn } from "./base/types"
|
||||
|
||||
const mysql = require("mysql2/promise")
|
||||
|
||||
|
@ -203,11 +204,11 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
|||
|
||||
try {
|
||||
// get the tables first
|
||||
const tablesResp = await this.internalQuery(
|
||||
const tablesResp: Record<string, string>[] = await this.internalQuery(
|
||||
{ sql: "SHOW TABLES;" },
|
||||
{ connect: false }
|
||||
)
|
||||
const tableNames = tablesResp.map(
|
||||
const tableNames: string[] = tablesResp.map(
|
||||
(obj: any) =>
|
||||
obj[`Tables_in_${database}`] ||
|
||||
obj[`Tables_in_${database.toLowerCase()}`]
|
||||
|
@ -215,7 +216,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
|||
for (let tableName of tableNames) {
|
||||
const primaryKeys = []
|
||||
const schema: TableSchema = {}
|
||||
const descResp = await this.internalQuery(
|
||||
const descResp: MySQLColumn[] = await this.internalQuery(
|
||||
{ sql: `DESCRIBE \`${tableName}\`;` },
|
||||
{ connect: false }
|
||||
)
|
||||
|
|
|
@ -24,6 +24,12 @@ import {
|
|||
ExecuteOptions,
|
||||
Result,
|
||||
} from "oracledb"
|
||||
import {
|
||||
OracleTable,
|
||||
OracleColumn,
|
||||
OracleColumnsResponse,
|
||||
OracleConstraint,
|
||||
} from "./base/types"
|
||||
let oracledb: any
|
||||
try {
|
||||
oracledb = require("oracledb")
|
||||
|
@ -89,50 +95,6 @@ const SCHEMA: Integration = {
|
|||
|
||||
const UNSUPPORTED_TYPES = ["BLOB", "CLOB", "NCLOB"]
|
||||
|
||||
/**
|
||||
* Raw query response
|
||||
*/
|
||||
interface ColumnsResponse {
|
||||
TABLE_NAME: string
|
||||
COLUMN_NAME: string
|
||||
DATA_TYPE: string
|
||||
DATA_DEFAULT: string | null
|
||||
COLUMN_ID: number
|
||||
CONSTRAINT_NAME: string | null
|
||||
CONSTRAINT_TYPE: string | null
|
||||
R_CONSTRAINT_NAME: string | null
|
||||
SEARCH_CONDITION: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* An oracle constraint
|
||||
*/
|
||||
interface OracleConstraint {
|
||||
name: string
|
||||
type: string
|
||||
relatedConstraintName: string | null
|
||||
searchCondition: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* An oracle column and it's related constraints
|
||||
*/
|
||||
interface OracleColumn {
|
||||
name: string
|
||||
type: string
|
||||
default: string | null
|
||||
id: number
|
||||
constraints: { [key: string]: OracleConstraint }
|
||||
}
|
||||
|
||||
/**
|
||||
* An oracle table and it's related columns
|
||||
*/
|
||||
interface OracleTable {
|
||||
name: string
|
||||
columns: { [key: string]: OracleColumn }
|
||||
}
|
||||
|
||||
const OracleContraintTypes = {
|
||||
PRIMARY: "P",
|
||||
NOT_NULL_OR_CHECK: "C",
|
||||
|
@ -195,7 +157,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
|||
/**
|
||||
* Map the flat tabular columns and constraints data into a nested object
|
||||
*/
|
||||
private mapColumns(result: Result<ColumnsResponse>): {
|
||||
private mapColumns(result: Result<OracleColumnsResponse>): {
|
||||
[key: string]: OracleTable
|
||||
} {
|
||||
const oracleTables: { [key: string]: OracleTable } = {}
|
||||
|
@ -299,7 +261,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
|||
* @param entities - the tables that are to be built
|
||||
*/
|
||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
||||
const columnsResponse = await this.internalQuery<ColumnsResponse>({
|
||||
const columnsResponse = await this.internalQuery<OracleColumnsResponse>({
|
||||
sql: this.COLUMNS_SQL,
|
||||
})
|
||||
const oracleTables = this.mapColumns(columnsResponse)
|
||||
|
@ -334,6 +296,9 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
|||
fieldSchema = {
|
||||
autocolumn: OracleIntegration.isAutoColumn(oracleColumn),
|
||||
name: columnName,
|
||||
constraints: {
|
||||
presence: false,
|
||||
},
|
||||
...this.internalConvertType(oracleColumn),
|
||||
}
|
||||
table.schema[columnName] = fieldSchema
|
||||
|
@ -343,6 +308,12 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
|||
Object.values(oracleColumn.constraints).forEach(oracleConstraint => {
|
||||
if (oracleConstraint.type === OracleContraintTypes.PRIMARY) {
|
||||
table.primary!.push(columnName)
|
||||
} else if (
|
||||
oracleConstraint.type === OracleContraintTypes.NOT_NULL_OR_CHECK
|
||||
) {
|
||||
table.schema[columnName].constraints = {
|
||||
presence: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -15,9 +15,10 @@ import {
|
|||
SqlClient,
|
||||
} from "./utils"
|
||||
import Sql from "./base/sql"
|
||||
import { PostgresColumn } from "./base/types"
|
||||
import { escapeDangerousCharacters } from "../utilities"
|
||||
|
||||
const { Client, types } = require("pg")
|
||||
const { escapeDangerousCharacters } = require("../utilities")
|
||||
|
||||
// Return "date" and "timestamp" types as plain strings.
|
||||
// This lets us reference the original stored timezone.
|
||||
|
@ -237,7 +238,8 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
|||
}
|
||||
|
||||
try {
|
||||
const columnsResponse = await this.client.query(this.COLUMNS_SQL)
|
||||
const columnsResponse: { rows: PostgresColumn[] } =
|
||||
await this.client.query(this.COLUMNS_SQL)
|
||||
|
||||
const tables: { [key: string]: Table } = {}
|
||||
|
||||
|
@ -260,6 +262,9 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
|||
column.identity_start ||
|
||||
column.identity_increment
|
||||
)
|
||||
const constraints = {
|
||||
presence: column.is_nullable === "NO",
|
||||
}
|
||||
const hasDefault =
|
||||
typeof column.column_default === "string" &&
|
||||
column.column_default.startsWith("nextval")
|
||||
|
@ -269,6 +274,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
|||
tables[tableName].schema[columnName] = {
|
||||
autocolumn: isAuto,
|
||||
name: columnName,
|
||||
constraints,
|
||||
...convertSqlType(column.data_type),
|
||||
externalType: column.data_type,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { db as dbCore } from "@budibase/backend-core"
|
||||
import { getAppUrl } from "../../api/controllers/application"
|
||||
import sdk from "../../sdk"
|
||||
|
||||
/**
|
||||
* Date:
|
||||
|
@ -20,14 +20,7 @@ export const run = async (appDb: any) => {
|
|||
}
|
||||
|
||||
if (!metadata.url) {
|
||||
const context = {
|
||||
request: {
|
||||
body: {
|
||||
name: metadata.name,
|
||||
},
|
||||
},
|
||||
}
|
||||
metadata.url = getAppUrl(context)
|
||||
metadata.url = sdk.applications.getAppUrl({ name: metadata.name })
|
||||
console.log(`Adding url to app: ${metadata.url}`)
|
||||
await appDb.put(metadata)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import * as sync from "./sync"
|
||||
import * as utils from "./utils"
|
||||
|
||||
export default {
|
||||
...sync,
|
||||
...utils,
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import env from "../../../environment"
|
||||
import { db as dbCore, context } from "@budibase/backend-core"
|
||||
import sdk from "../../"
|
||||
|
||||
export async function syncApp(
|
||||
appId: string,
|
||||
opts?: { automationOnly?: boolean }
|
||||
) {
|
||||
if (env.DISABLE_AUTO_PROD_APP_SYNC) {
|
||||
return {
|
||||
message:
|
||||
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable.",
|
||||
}
|
||||
}
|
||||
|
||||
if (dbCore.isProdAppID(appId)) {
|
||||
throw new Error("This action cannot be performed for production apps")
|
||||
}
|
||||
|
||||
// replicate prod to dev
|
||||
const prodAppId = dbCore.getProdAppID(appId)
|
||||
|
||||
// specific case, want to make sure setup is skipped
|
||||
const prodDb = context.getProdAppDB({ skip_setup: true })
|
||||
const exists = await prodDb.exists()
|
||||
if (!exists) {
|
||||
// the database doesn't exist. Don't replicate
|
||||
return {
|
||||
message: "App sync not required, app not deployed.",
|
||||
}
|
||||
}
|
||||
|
||||
const replication = new dbCore.Replication({
|
||||
source: prodAppId,
|
||||
target: appId,
|
||||
})
|
||||
let error
|
||||
try {
|
||||
const replOpts = replication.appReplicateOpts()
|
||||
if (opts?.automationOnly) {
|
||||
replOpts.filter = (doc: any) =>
|
||||
doc._id.startsWith(dbCore.DocumentType.AUTOMATION)
|
||||
}
|
||||
await replication.replicate(replOpts)
|
||||
} catch (err) {
|
||||
error = err
|
||||
} finally {
|
||||
await replication.close()
|
||||
}
|
||||
|
||||
// sync the users
|
||||
await sdk.users.syncGlobalUsers()
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
} else {
|
||||
return {
|
||||
message: "App sync completed successfully.",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
const URL_REGEX_SLASH = /\/|\\/g
|
||||
|
||||
export function getAppUrl(opts?: { name?: string; url?: string }) {
|
||||
// construct the url
|
||||
let url
|
||||
if (opts?.url) {
|
||||
// if the url is provided, use that
|
||||
url = encodeURI(opts?.url)
|
||||
} else if (opts?.name) {
|
||||
// otherwise use the name
|
||||
url = encodeURI(`${opts?.name}`)
|
||||
}
|
||||
if (url) {
|
||||
url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
|
||||
}
|
||||
return url as string
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
import { default as backups } from "./app/backups"
|
||||
import { default as tables } from "./app/tables"
|
||||
import { default as automations } from "./app/automations"
|
||||
import { default as applications } from "./app/applications"
|
||||
import { default as users } from "./users"
|
||||
|
||||
const sdk = {
|
||||
backups,
|
||||
tables,
|
||||
automations,
|
||||
applications,
|
||||
users,
|
||||
}
|
||||
|
||||
// default export for TS
|
||||
export default sdk
|
||||
|
||||
// default export for JS
|
||||
module.exports = sdk
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import * as utils from "./utils"
|
||||
|
||||
export default {
|
||||
...utils,
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { getGlobalUsers } from "../../utilities/global"
|
||||
import { context, roles as rolesCore } from "@budibase/backend-core"
|
||||
import {
|
||||
generateUserMetadataID,
|
||||
getUserMetadataParams,
|
||||
InternalTables,
|
||||
} from "../../db/utils"
|
||||
import { isEqual } from "lodash"
|
||||
|
||||
export function combineMetadataAndUser(user: any, metadata: any) {
|
||||
// skip users with no access
|
||||
if (user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC) {
|
||||
return null
|
||||
}
|
||||
delete user._rev
|
||||
const metadataId = generateUserMetadataID(user._id)
|
||||
const newDoc = {
|
||||
...user,
|
||||
_id: metadataId,
|
||||
tableId: InternalTables.USER_METADATA,
|
||||
}
|
||||
const found = Array.isArray(metadata)
|
||||
? metadata.find(doc => doc._id === metadataId)
|
||||
: metadata
|
||||
// copy rev over for the purposes of equality check
|
||||
if (found) {
|
||||
newDoc._rev = found._rev
|
||||
}
|
||||
if (found == null || !isEqual(newDoc, found)) {
|
||||
return {
|
||||
...found,
|
||||
...newDoc,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function rawUserMetadata() {
|
||||
const db = context.getAppDB()
|
||||
return (
|
||||
await db.allDocs(
|
||||
getUserMetadataParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
).rows.map(row => row.doc)
|
||||
}
|
||||
|
||||
export async function syncGlobalUsers() {
|
||||
// sync user metadata
|
||||
const db = context.getAppDB()
|
||||
const [users, metadata] = await Promise.all([
|
||||
getGlobalUsers(),
|
||||
rawUserMetadata(),
|
||||
])
|
||||
const toWrite = []
|
||||
for (let user of users) {
|
||||
const combined = await combineMetadataAndUser(user, metadata)
|
||||
if (combined) {
|
||||
toWrite.push(combined)
|
||||
}
|
||||
}
|
||||
await db.bulkDocs(toWrite)
|
||||
}
|
|
@ -3,7 +3,7 @@ import env from "../environment"
|
|||
import { checkSlashesInUrl } from "./index"
|
||||
import { db as dbCore, constants, tenancy } from "@budibase/backend-core"
|
||||
import { updateAppRole } from "./global"
|
||||
import { BBContext, Automation } from "@budibase/types"
|
||||
import { BBContext, User } from "@budibase/types"
|
||||
|
||||
export function request(ctx?: BBContext, request?: any) {
|
||||
if (!request.headers) {
|
||||
|
@ -138,7 +138,7 @@ export async function deleteGlobalUser(ctx: BBContext) {
|
|||
return checkResponse(response, "delete user", { ctx })
|
||||
}
|
||||
|
||||
export async function readGlobalUser(ctx: BBContext) {
|
||||
export async function readGlobalUser(ctx: BBContext): Promise<User> {
|
||||
const response = await fetch(
|
||||
checkSlashesInUrl(
|
||||
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`
|
||||
|
|
|
@ -189,9 +189,9 @@
|
|||
integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==
|
||||
|
||||
"@babel/compat-data@^7.20.0":
|
||||
version "7.20.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30"
|
||||
integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733"
|
||||
integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==
|
||||
|
||||
"@babel/core@7.17.4":
|
||||
version "7.17.4"
|
||||
|
@ -236,20 +236,20 @@
|
|||
semver "^6.3.0"
|
||||
|
||||
"@babel/core@^7.11.6":
|
||||
version "7.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92"
|
||||
integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113"
|
||||
integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==
|
||||
dependencies:
|
||||
"@ampproject/remapping" "^2.1.0"
|
||||
"@babel/code-frame" "^7.18.6"
|
||||
"@babel/generator" "^7.20.2"
|
||||
"@babel/generator" "^7.20.5"
|
||||
"@babel/helper-compilation-targets" "^7.20.0"
|
||||
"@babel/helper-module-transforms" "^7.20.2"
|
||||
"@babel/helpers" "^7.20.1"
|
||||
"@babel/parser" "^7.20.2"
|
||||
"@babel/helpers" "^7.20.5"
|
||||
"@babel/parser" "^7.20.5"
|
||||
"@babel/template" "^7.18.10"
|
||||
"@babel/traverse" "^7.20.1"
|
||||
"@babel/types" "^7.20.2"
|
||||
"@babel/traverse" "^7.20.5"
|
||||
"@babel/types" "^7.20.5"
|
||||
convert-source-map "^1.7.0"
|
||||
debug "^4.1.0"
|
||||
gensync "^1.0.0-beta.2"
|
||||
|
@ -265,12 +265,12 @@
|
|||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/generator@^7.20.1", "@babel/generator@^7.20.2":
|
||||
version "7.20.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8"
|
||||
integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==
|
||||
"@babel/generator@^7.20.5":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95"
|
||||
integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.20.2"
|
||||
"@babel/types" "^7.20.5"
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
|
@ -551,14 +551,14 @@
|
|||
"@babel/traverse" "^7.18.2"
|
||||
"@babel/types" "^7.18.2"
|
||||
|
||||
"@babel/helpers@^7.20.1":
|
||||
version "7.20.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9"
|
||||
integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==
|
||||
"@babel/helpers@^7.20.5":
|
||||
version "7.20.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763"
|
||||
integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==
|
||||
dependencies:
|
||||
"@babel/template" "^7.18.10"
|
||||
"@babel/traverse" "^7.20.1"
|
||||
"@babel/types" "^7.20.0"
|
||||
"@babel/traverse" "^7.20.5"
|
||||
"@babel/types" "^7.20.5"
|
||||
|
||||
"@babel/highlight@^7.16.7":
|
||||
version "7.17.12"
|
||||
|
@ -583,10 +583,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
|
||||
integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
|
||||
|
||||
"@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2":
|
||||
version "7.20.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2"
|
||||
integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==
|
||||
"@babel/parser@^7.18.10", "@babel/parser@^7.20.5":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
|
||||
integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7":
|
||||
version "7.17.12"
|
||||
|
@ -1235,19 +1235,19 @@
|
|||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/traverse@^7.20.1":
|
||||
version "7.20.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8"
|
||||
integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==
|
||||
"@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133"
|
||||
integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.18.6"
|
||||
"@babel/generator" "^7.20.1"
|
||||
"@babel/generator" "^7.20.5"
|
||||
"@babel/helper-environment-visitor" "^7.18.9"
|
||||
"@babel/helper-function-name" "^7.19.0"
|
||||
"@babel/helper-hoist-variables" "^7.18.6"
|
||||
"@babel/helper-split-export-declaration" "^7.18.6"
|
||||
"@babel/parser" "^7.20.1"
|
||||
"@babel/types" "^7.20.0"
|
||||
"@babel/parser" "^7.20.5"
|
||||
"@babel/types" "^7.20.5"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
|
@ -1259,10 +1259,10 @@
|
|||
"@babel/helper-validator-identifier" "^7.16.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2":
|
||||
version "7.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842"
|
||||
integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==
|
||||
"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84"
|
||||
integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.19.4"
|
||||
"@babel/helper-validator-identifier" "^7.19.1"
|
||||
|
@ -1273,12 +1273,12 @@
|
|||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@budibase/backend-core@2.1.40-alpha.7":
|
||||
version "2.1.40-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.40-alpha.7.tgz#383ce7b679395bcbd5a2dbe52c73915e2a8e456f"
|
||||
integrity sha512-JWKm47ypnBiLLbdZxPc/Ca08vT14kh2yk3o9f30zH8Uq7O5STu/bbffBVuS/wxgLD8CSKQS6Hbocz3/nADWbIQ==
|
||||
"@budibase/backend-core@2.1.46-alpha.0":
|
||||
version "2.1.46-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.46-alpha.0.tgz#d9063b055cb3c4edb3994a13664c6a070e701a71"
|
||||
integrity sha512-ulpK2u+5+yIjIyMWhr2cre2ncdZsq5uPt1meNO+kvsetmv37ZnLtmM2gqIe1RNjYkf+mIZlUDA9QDkZowIiTUg==
|
||||
dependencies:
|
||||
"@budibase/types" "2.1.40-alpha.7"
|
||||
"@budibase/types" "2.1.46-alpha.0"
|
||||
"@shopify/jest-koa-mocks" "5.0.1"
|
||||
"@techpass/passport-openidconnect" "0.3.2"
|
||||
aws-sdk "2.1030.0"
|
||||
|
@ -1360,13 +1360,13 @@
|
|||
svelte-flatpickr "^3.2.3"
|
||||
svelte-portal "^1.0.0"
|
||||
|
||||
"@budibase/pro@2.1.40-alpha.7":
|
||||
version "2.1.40-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.40-alpha.7.tgz#697fc833de5efd84c94ed316d010683112394dd1"
|
||||
integrity sha512-S7UJNZMdN3PebVmGI7MNl41C9jkgJ1dt9F3tIWQsogRA2bJKjaGJzHWWcQ2qFPNNFgMBdYvVk77UQerLS2HfpA==
|
||||
"@budibase/pro@2.1.46-alpha.0":
|
||||
version "2.1.46-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.46-alpha.0.tgz#69bf09007edc0f2be275824ba09f27422081c490"
|
||||
integrity sha512-04iCZVU6BN0afei0wFITgvjRhQ46G/vI51FCBZ9ZiuyTsWBPuAhNqXVAQ/Y48+OVDBMtIvsvJ/S1kCbWUa6x3g==
|
||||
dependencies:
|
||||
"@budibase/backend-core" "2.1.40-alpha.7"
|
||||
"@budibase/types" "2.1.40-alpha.7"
|
||||
"@budibase/backend-core" "2.1.46-alpha.0"
|
||||
"@budibase/types" "2.1.46-alpha.0"
|
||||
"@koa/router" "8.0.8"
|
||||
bull "4.10.1"
|
||||
joi "17.6.0"
|
||||
|
@ -1390,10 +1390,10 @@
|
|||
svelte-apexcharts "^1.0.2"
|
||||
svelte-flatpickr "^3.1.0"
|
||||
|
||||
"@budibase/types@2.1.40-alpha.7":
|
||||
version "2.1.40-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.40-alpha.7.tgz#96b01c934d43dd0467ed3ab2b19676a65e97dd68"
|
||||
integrity sha512-3t4MOHlgHywGiz9qZEd7Zs0PtWgSSVpwNvmEEOe/vSL2xPptFJuT5M90KKgdlkND6vUTJ8wiLA9+OToegk0ndw==
|
||||
"@budibase/types@2.1.46-alpha.0":
|
||||
version "2.1.46-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.46-alpha.0.tgz#83ecd2c1a26e933fbf27ba6b7a7ef1fed82a8840"
|
||||
integrity sha512-TTpyax8U0EAsaXqCLACHcfuejZ2x2isyHiM/ia8Ua6R+1rDK9kjYPsiTlJ169/Jq2hv65TzScyaqnHX+g+eJnQ==
|
||||
|
||||
"@bull-board/api@3.7.0":
|
||||
version "3.7.0"
|
||||
|
@ -3167,10 +3167,10 @@
|
|||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0":
|
||||
version "18.11.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
|
||||
integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
|
||||
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0":
|
||||
version "17.0.41"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b"
|
||||
integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==
|
||||
|
||||
"@types/node@14.18.20":
|
||||
version "14.18.20"
|
||||
|
@ -3182,6 +3182,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708"
|
||||
integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==
|
||||
|
||||
"@types/node@>=10.0.0":
|
||||
version "18.7.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154"
|
||||
integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==
|
||||
|
||||
"@types/node@>=8.0.0 <15":
|
||||
version "14.18.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.21.tgz#0155ee46f6be28b2ff0342ca1a9b9fd4468bef41"
|
||||
|
@ -3464,9 +3469,9 @@
|
|||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yargs@^17.0.8":
|
||||
version "17.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76"
|
||||
integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==
|
||||
version "17.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.15.tgz#5b62c89fb049e2fc8378394a2861a593055f0866"
|
||||
integrity sha512-ZHc4W2dnEQPfhn06TBEdWaiUHEZAocYaiVMfwOipY5jcJt/251wVrKCBWBetGZWO5CF8tdb7L3DmdxVlZ2BOIg==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
|
@ -4919,9 +4924,9 @@ caniuse-lite@^1.0.30001349:
|
|||
integrity sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==
|
||||
|
||||
caniuse-lite@^1.0.30001400:
|
||||
version "1.0.30001431"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz#e7c59bd1bc518fae03a4656be442ce6c4887a795"
|
||||
integrity sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==
|
||||
version "1.0.30001435"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001435.tgz#502c93dbd2f493bee73a408fe98e98fb1dad10b2"
|
||||
integrity sha512-kdCkUTjR+v4YAJelyiDTqiu82BDr4W4CP5sgTA0ZBmqn30XfS2ZghPLMowik9TPhS+psWJiUNxsqLyurDbmutA==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -5568,9 +5573,9 @@ decamelize@^1.2.0:
|
|||
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
|
||||
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
|
||||
|
||||
decompress-response@^3.3.0:
|
||||
version "3.3.0"
|
||||
|
@ -6073,9 +6078,9 @@ engine.io-parser@~5.0.3:
|
|||
integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
|
||||
|
||||
engine.io@~6.2.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.1.tgz#e3f7826ebc4140db9bbaa9021ad6b1efb175878f"
|
||||
integrity sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0"
|
||||
integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==
|
||||
dependencies:
|
||||
"@types/cookie" "^0.4.1"
|
||||
"@types/cors" "^2.8.12"
|
||||
|
@ -10049,9 +10054,9 @@ loader-runner@^4.2.0:
|
|||
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
|
||||
|
||||
loader-utils@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
|
||||
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
|
||||
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
|
||||
dependencies:
|
||||
big.js "^5.2.2"
|
||||
emojis-list "^3.0.0"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue