diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 6e04ca6f67..cb713c93ac 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -99,11 +99,6 @@ jobs: else yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore=@budibase/pro fi - - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - name: codecov-umbrella - verbose: true test-worker: runs-on: ubuntu-latest @@ -129,12 +124,6 @@ jobs: yarn test --scope=@budibase/worker fi - - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN || github.token }} # not required for public repos - name: codecov-umbrella - verbose: true - test-server: runs-on: ubuntu-latest steps: @@ -159,12 +148,6 @@ jobs: yarn test --scope=@budibase/server fi - - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN || github.token }} # not required for public repos - name: codecov-umbrella - verbose: true - test-pro: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' diff --git a/charts/budibase/templates/alb-ingress.yaml b/charts/budibase/templates/alb-ingress.yaml index 6cd1cf2cba..fd38364ba6 100644 --- a/charts/budibase/templates/alb-ingress.yaml +++ b/charts/budibase/templates/alb-ingress.yaml @@ -7,8 +7,8 @@ metadata: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/success-codes: 200,301 - alb.ingress.kubernetes.io/healthcheck-path: / + alb.ingress.kubernetes.io/success-codes: '200' + alb.ingress.kubernetes.io/healthcheck-path: '/health' {{- if .Values.ingress.certificateArn }} alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' diff --git a/hosting/couchdb/Dockerfile b/hosting/couchdb/Dockerfile index 792856cac7..f83df7038b 100644 --- a/hosting/couchdb/Dockerfile +++ b/hosting/couchdb/Dockerfile @@ -29,7 +29,6 @@ WORKDIR /opt/couchdb ADD couch/vm.args couch/local.ini ./etc/ WORKDIR / -ADD build-target-paths.sh . ADD runner.sh ./bbcouch-runner.sh -RUN chmod +x ./bbcouch-runner.sh /opt/clouseau/bin/clouseau ./build-target-paths.sh +RUN chmod +x ./bbcouch-runner.sh /opt/clouseau/bin/clouseau CMD ["./bbcouch-runner.sh"] diff --git a/hosting/couchdb/build-target-paths.sh b/hosting/couchdb/build-target-paths.sh deleted file mode 100644 index 34227011f4..0000000000 --- a/hosting/couchdb/build-target-paths.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -echo ${TARGETBUILD} > /buildtarget.txt -if [[ "${TARGETBUILD}" = "aas" ]]; then - # Azure AppService uses /home for persistent data & SSH on port 2222 - DATA_DIR="${DATA_DIR:-/home}" - WEBSITES_ENABLE_APP_SERVICE_STORAGE=true - mkdir -p $DATA_DIR/{search,minio,couch} - mkdir -p $DATA_DIR/couch/{dbs,views} - chown -R couchdb:couchdb $DATA_DIR/couch/ - apt update - apt-get install -y openssh-server - echo "root:Docker!" | chpasswd - mkdir -p /tmp - chmod +x /tmp/ssh_setup.sh \ - && (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null) - cp /etc/sshd_config /etc/ssh/sshd_config - /etc/init.d/ssh restart - sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini - sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini -else - sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini - sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini -fi \ No newline at end of file diff --git a/hosting/couchdb/runner.sh b/hosting/couchdb/runner.sh index 4102d2a751..b576c886c2 100644 --- a/hosting/couchdb/runner.sh +++ b/hosting/couchdb/runner.sh @@ -1,14 +1,73 @@ #!/bin/bash DATA_DIR=${DATA_DIR:-/data} + mkdir -p ${DATA_DIR} mkdir -p ${DATA_DIR}/couch/{dbs,views} mkdir -p ${DATA_DIR}/search chown -R couchdb:couchdb ${DATA_DIR}/couch -/build-target-paths.sh + +echo ${TARGETBUILD} > /buildtarget.txt +if [[ "${TARGETBUILD}" = "aas" ]]; then + # Azure AppService uses /home for persistent data & SSH on port 2222 + DATA_DIR="${DATA_DIR:-/home}" + WEBSITES_ENABLE_APP_SERVICE_STORAGE=true + mkdir -p $DATA_DIR/{search,minio,couch} + mkdir -p $DATA_DIR/couch/{dbs,views} + chown -R couchdb:couchdb $DATA_DIR/couch/ + apt update + apt-get install -y openssh-server + echo "root:Docker!" | chpasswd + mkdir -p /tmp + chmod +x /tmp/ssh_setup.sh \ + && (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null) + cp /etc/sshd_config /etc/ssh/sshd_config + /etc/init.d/ssh restart + sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini +elif [[ "${TARGETBUILD}" = "single" ]]; then + # In the single image build, the Dockerfile specifies /data as a volume + # mount, so we use that for all persistent data. + sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini +elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then + # In Kubernetes the directory /opt/couchdb/data has a persistent volume + # mount for storing database data. + sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/clouseau/clouseau.ini + + # We remove the database_dir and view_index_dir settings from the local.ini + # in Kubernetes because it will default to /opt/couchdb/data which is what + # our Helm chart was using prior to us switching to using our own CouchDB + # image. + sed -i "s#^database_dir.*\$##g" /opt/couchdb/etc/local.ini + sed -i "s#^view_index_dir.*\$##g" /opt/couchdb/etc/local.ini + + # We remove the -name setting from the vm.args file in Kubernetes because + # it will default to the pod FQDN, which is what's required for clustering + # to work. + sed -i "s/^-name .*$//g" /opt/couchdb/etc/vm.args +else + # For all other builds, we use /data for persistent data. + sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini +fi + +# Start Clouseau. Budibase won't function correctly without Clouseau running, it +# powers the search API endpoints which are used to do all sorts, including +# populating app grids. /opt/clouseau/bin/clouseau > /dev/stdout 2>&1 & + +# Start CouchDB. /docker-entrypoint.sh /opt/couchdb/bin/couchdb & -sleep 10 + +# Wati for CouchDB to start up. +while [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/_up -o /dev/null) -ne 200 ]]; do + echo 'Waiting for CouchDB to start...'; + sleep 5; +done + +# CouchDB needs the `_users` and `_replicator` databases to exist before it will +# function correctly, so we create them here. curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_users curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_replicator sleep infinity \ No newline at end of file diff --git a/hosting/docker-compose.build.yaml b/hosting/docker-compose.build.yaml index 7ead001a1c..dbc3613599 100644 --- a/hosting/docker-compose.build.yaml +++ b/hosting/docker-compose.build.yaml @@ -6,7 +6,7 @@ services: app-service: build: context: .. - dockerfile: packages/server/Dockerfile.v2 + dockerfile: packages/server/Dockerfile args: - BUDIBASE_VERSION=0.0.0+dev-docker container_name: build-bbapps @@ -36,7 +36,7 @@ services: worker-service: build: context: .. - dockerfile: packages/worker/Dockerfile.v2 + dockerfile: packages/worker/Dockerfile args: - BUDIBASE_VERSION=0.0.0+dev-docker container_name: build-bbworker diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index c7b90dbdc4..e9ff6c6596 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -1,44 +1,59 @@ FROM node:18-slim as build # install node-gyp dependencies -RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends apt-utils cron g++ make python3 +RUN apt-get update && apt-get install -y --no-install-recommends g++ make python3 jq -# add pin script -WORKDIR / -ADD scripts/cleanup.sh ./ -RUN chmod +x /cleanup.sh -# build server +# copy and install dependencies WORKDIR /app -ADD packages/server . +COPY package.json . COPY yarn.lock . -RUN yarn install --production=true --network-timeout 1000000 -RUN /cleanup.sh +COPY lerna.json . +COPY .yarnrc . -# build worker -WORKDIR /worker -ADD packages/worker . -COPY yarn.lock . -RUN yarn install --production=true --network-timeout 1000000 -RUN /cleanup.sh +COPY packages/server/package.json packages/server/package.json +COPY packages/worker/package.json packages/worker/package.json +# string-templates does not get bundled during the esbuild process, so we want to use the local version +COPY packages/string-templates/package.json packages/string-templates/package.json -FROM budibase/couchdb + +COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh +RUN chmod +x ./scripts/removeWorkspaceDependencies.sh +RUN ./scripts/removeWorkspaceDependencies.sh packages/server/package.json +RUN ./scripts/removeWorkspaceDependencies.sh packages/worker/package.json + + +# We will never want to sync pro, but the script is still required +RUN echo '' > scripts/syncProPackage.js +RUN jq 'del(.scripts.postinstall)' package.json > temp.json && mv temp.json package.json +RUN ./scripts/removeWorkspaceDependencies.sh package.json +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production + +# copy the actual code +COPY packages/server/dist packages/server/dist +COPY packages/server/pm2.config.js packages/server/pm2.config.js +COPY packages/server/client packages/server/client +COPY packages/server/builder packages/server/builder +COPY packages/worker/dist packages/worker/dist +COPY packages/worker/pm2.config.js packages/worker/pm2.config.js +COPY packages/string-templates packages/string-templates + + +FROM budibase/couchdb as runner ARG TARGETARCH ENV TARGETARCH $TARGETARCH +ENV NODE_MAJOR 18 #TARGETBUILD can be set to single (for single docker image) or aas (for azure app service) # e.g. docker build --build-arg TARGETBUILD=aas .... ARG TARGETBUILD=single ENV TARGETBUILD $TARGETBUILD -COPY --from=build /app /app -COPY --from=build /worker /worker - # install base dependencies RUN apt-get update && \ - apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server + apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server libaio1 # Install postgres client for pg_dump utils -RUN apt install software-properties-common apt-transport-https gpg -y \ +RUN apt install -y software-properties-common apt-transport-https ca-certificates gnupg \ && curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \ && echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \ && apt update -y \ @@ -47,14 +62,12 @@ RUN apt install software-properties-common apt-transport-https gpg -y \ # install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx WORKDIR /nodejs -RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh && \ - bash /tmp/nodesource_setup.sh && \ - apt-get install -y --no-install-recommends libaio1 nodejs && \ - npm install --global yarn pm2 +COPY scripts/install-node.sh ./install.sh +RUN chmod +x install.sh && ./install.sh # setup nginx -ADD hosting/single/nginx/nginx.conf /etc/nginx -ADD hosting/single/nginx/nginx-default-site.conf /etc/nginx/sites-enabled/default +COPY hosting/single/nginx/nginx.conf /etc/nginx +COPY 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 && \ @@ -62,29 +75,39 @@ RUN mkdir -p /var/log/nginx && \ WORKDIR / RUN mkdir -p scripts/integrations/oracle -ADD packages/server/scripts/integrations/oracle scripts/integrations/oracle +COPY packages/server/scripts/integrations/oracle scripts/integrations/oracle RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh # setup minio WORKDIR /minio -ADD scripts/install-minio.sh ./install.sh +COPY scripts/install-minio.sh ./install.sh RUN chmod +x install.sh && ./install.sh # setup runner file WORKDIR / -ADD hosting/single/runner.sh . +COPY hosting/single/runner.sh . RUN chmod +x ./runner.sh -ADD hosting/single/healthcheck.sh . +COPY hosting/single/healthcheck.sh . RUN chmod +x ./healthcheck.sh # Script below sets the path for storing data based on $DATA_DIR # For Azure App Service install SSH & point data locations to /home -ADD hosting/single/ssh/sshd_config /etc/ -ADD hosting/single/ssh/ssh_setup.sh /tmp -RUN /build-target-paths.sh +COPY hosting/single/ssh/sshd_config /etc/ +COPY hosting/single/ssh/ssh_setup.sh /tmp + +# setup letsencrypt certificate +RUN apt-get install -y certbot python3-certbot-nginx +COPY hosting/letsencrypt /app/letsencrypt +RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh + +COPY --from=build /app/node_modules /node_modules +COPY --from=build /app/package.json /package.json +COPY --from=build /app/packages/server /app +COPY --from=build /app/packages/worker /worker +COPY --from=build /app/packages/string-templates /string-templates + +RUN cd /string-templates && yarn link && cd ../app && yarn link @budibase/string-templates && cd ../worker && yarn link @budibase/string-templates -# cleanup cache -RUN yarn cache clean -f EXPOSE 80 EXPOSE 443 @@ -92,20 +115,10 @@ EXPOSE 443 EXPOSE 2222 VOLUME /data -# setup letsencrypt certificate -RUN apt-get install -y certbot python3-certbot-nginx -ADD hosting/letsencrypt /app/letsencrypt -RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh -# Remove cached files -RUN rm -rf \ - /root/.cache \ - /root/.npm \ - /root/.pip \ - /usr/local/share/doc \ - /usr/share/doc \ - /usr/share/man \ - /var/lib/apt/lists/* \ - /tmp/* +ARG BUDIBASE_VERSION +# Ensuring the version argument is sent +RUN test -n "$BUDIBASE_VERSION" +ENV BUDIBASE_VERSION=$BUDIBASE_VERSION HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh" diff --git a/hosting/single/Dockerfile.v2 b/hosting/single/Dockerfile.v2 deleted file mode 100644 index ec03a1b5a2..0000000000 --- a/hosting/single/Dockerfile.v2 +++ /dev/null @@ -1,131 +0,0 @@ -FROM node:18-slim as build - -# install node-gyp dependencies -RUN apt-get update && apt-get install -y --no-install-recommends g++ make python3 jq - - -# copy and install dependencies -WORKDIR /app -COPY package.json . -COPY yarn.lock . -COPY lerna.json . -COPY .yarnrc . - -COPY packages/server/package.json packages/server/package.json -COPY packages/worker/package.json packages/worker/package.json -# string-templates does not get bundled during the esbuild process, so we want to use the local version -COPY packages/string-templates/package.json packages/string-templates/package.json - - -COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh -RUN chmod +x ./scripts/removeWorkspaceDependencies.sh -RUN ./scripts/removeWorkspaceDependencies.sh packages/server/package.json -RUN ./scripts/removeWorkspaceDependencies.sh packages/worker/package.json - - -# We will never want to sync pro, but the script is still required -RUN echo '' > scripts/syncProPackage.js -RUN jq 'del(.scripts.postinstall)' package.json > temp.json && mv temp.json package.json -RUN ./scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production - -# copy the actual code -COPY packages/server/dist packages/server/dist -COPY packages/server/pm2.config.js packages/server/pm2.config.js -COPY packages/server/client packages/server/client -COPY packages/server/builder packages/server/builder -COPY packages/worker/dist packages/worker/dist -COPY packages/worker/pm2.config.js packages/worker/pm2.config.js -COPY packages/string-templates packages/string-templates - - -FROM budibase/couchdb as runner -ARG TARGETARCH -ENV TARGETARCH $TARGETARCH -ENV NODE_MAJOR 18 -#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service) -# e.g. docker build --build-arg TARGETBUILD=aas .... -ARG TARGETBUILD=single -ENV TARGETBUILD $TARGETBUILD - -# install base dependencies -RUN apt-get update && \ - apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server libaio1 - -# Install postgres client for pg_dump utils -RUN apt install -y software-properties-common apt-transport-https ca-certificates gnupg \ - && curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \ - && echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \ - && apt update -y \ - && apt install postgresql-client-15 -y \ - && apt remove software-properties-common apt-transport-https gpg -y - -# install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx -WORKDIR /nodejs -COPY scripts/install-node.sh ./install.sh -RUN chmod +x install.sh && ./install.sh - -# setup nginx -COPY hosting/single/nginx/nginx.conf /etc/nginx -COPY 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 && \ - usermod -a -G tty www-data - -WORKDIR / -RUN mkdir -p scripts/integrations/oracle -COPY packages/server/scripts/integrations/oracle scripts/integrations/oracle -RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh - -# setup minio -WORKDIR /minio -COPY scripts/install-minio.sh ./install.sh -RUN chmod +x install.sh && ./install.sh - -# setup runner file -WORKDIR / -COPY hosting/single/runner.sh . -RUN chmod +x ./runner.sh -COPY hosting/single/healthcheck.sh . -RUN chmod +x ./healthcheck.sh - -# Script below sets the path for storing data based on $DATA_DIR -# For Azure App Service install SSH & point data locations to /home -COPY hosting/single/ssh/sshd_config /etc/ -COPY hosting/single/ssh/ssh_setup.sh /tmp -RUN /build-target-paths.sh - - -# setup letsencrypt certificate -RUN apt-get install -y certbot python3-certbot-nginx -COPY hosting/letsencrypt /app/letsencrypt -RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh - -COPY --from=build /app/node_modules /node_modules -COPY --from=build /app/package.json /package.json -COPY --from=build /app/packages/server /app -COPY --from=build /app/packages/worker /worker -COPY --from=build /app/packages/string-templates /string-templates - -RUN cd /string-templates && yarn link && cd ../app && yarn link @budibase/string-templates && cd ../worker && yarn link @budibase/string-templates - - -EXPOSE 80 -EXPOSE 443 -# Expose port 2222 for SSH on Azure App Service build -EXPOSE 2222 -VOLUME /data - -ARG BUDIBASE_VERSION -# Ensuring the version argument is sent -RUN test -n "$BUDIBASE_VERSION" -ENV BUDIBASE_VERSION=$BUDIBASE_VERSION - -HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh" - -# must set this just before running -ENV NODE_ENV=production -WORKDIR / - -CMD ["./runner.sh"] diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index 87201c95c0..f4b2b5b127 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -22,11 +22,11 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME # Azure App Service customisations if [[ "${TARGETBUILD}" = "aas" ]]; then - DATA_DIR="${DATA_DIR:-/home}" + export DATA_DIR="${DATA_DIR:-/home}" WEBSITES_ENABLE_APP_SERVICE_STORAGE=true /etc/init.d/ssh start else - DATA_DIR=${DATA_DIR:-/data} + export DATA_DIR=${DATA_DIR:-/data} fi mkdir -p ${DATA_DIR} # Mount NFS or GCP Filestore if env vars exist for it diff --git a/lerna.json b/lerna.json index faba64ce90..491de0dd97 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.15", + "version": "2.13.23", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/CODEOWNERS b/packages/backend-core/CODEOWNERS new file mode 100644 index 0000000000..84313fb9cf --- /dev/null +++ b/packages/backend-core/CODEOWNERS @@ -0,0 +1 @@ +* @Budibase/backend \ No newline at end of file diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts index cdaf19fa55..1971c09e9d 100644 --- a/packages/backend-core/src/objectStore/objectStore.ts +++ b/packages/backend-core/src/objectStore/objectStore.ts @@ -260,12 +260,12 @@ export async function listAllObjects(bucketName: string, path: string) { } /** - * Generate a presigned url with a default TTL of 1 hour + * Generate a presigned url with a default TTL of 36 hours */ export function getPresignedUrl( bucketName: string, key: string, - durationSeconds: number = 3600 + durationSeconds: number = 129600 ) { const objectStore = ObjectStore(bucketName, { presigning: true }) const params = { diff --git a/packages/backend-core/src/security/permissions.ts b/packages/backend-core/src/security/permissions.ts index fe4095d210..98704f16c6 100644 --- a/packages/backend-core/src/security/permissions.ts +++ b/packages/backend-core/src/security/permissions.ts @@ -160,4 +160,5 @@ export function isPermissionLevelHigherThanRead(level: PermissionLevel) { // utility as a lot of things need simply the builder permission export const BUILDER = PermissionType.BUILDER +export const CREATOR = PermissionType.CREATOR export const GLOBAL_BUILDER = PermissionType.GLOBAL_BUILDER diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index 2b6fd52d44..326bed3cc5 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -146,12 +146,12 @@ export class UserDB { static async allUsers() { const db = getGlobalDB() - const response = await db.allDocs( + const response = await db.allDocs( dbUtils.getGlobalUserParams(null, { include_docs: true, }) ) - return response.rows.map((row: any) => row.doc) + return response.rows.map(row => row.doc!) } static async countUsersByApp(appId: string) { @@ -209,13 +209,6 @@ export class UserDB { throw new Error("_id or email is required") } - if ( - user.builder?.apps?.length && - !(await UserDB.features.isAppBuildersEnabled()) - ) { - throw new Error("Unable to update app builders, please check license") - } - let dbUser: User | undefined if (_id) { // try to get existing user from db diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts index 6aed45371a..cc2b4fc27f 100644 --- a/packages/backend-core/src/users/users.ts +++ b/packages/backend-core/src/users/users.ts @@ -25,6 +25,7 @@ import { import { getGlobalDB } from "../context" import * as context from "../context" import { isCreator } from "./utils" +import { UserDB } from "./db" type GetOpts = { cleanup?: boolean } @@ -336,3 +337,20 @@ export function cleanseUserObject(user: User | ContextUser, base?: User) { } return user } + +export async function addAppBuilder(user: User, appId: string) { + const prodAppId = getProdAppID(appId) + user.builder ??= {} + user.builder.creator = true + user.builder.apps ??= [] + user.builder.apps.push(prodAppId) + await UserDB.save(user, { hashPassword: false }) +} + +export async function removeAppBuilder(user: User, appId: string) { + const prodAppId = getProdAppID(appId) + if (user.builder && user.builder.apps?.includes(prodAppId)) { + user.builder.apps = user.builder.apps.filter(id => id !== prodAppId) + } + await UserDB.save(user, { hashPassword: false }) +} diff --git a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte index 3768fc1954..7c53f642b0 100644 --- a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte +++ b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte @@ -2,7 +2,7 @@ import "@spectrum-css/buttongroup/dist/index-vars.css" export let vertical = false - export let gap = "" + export let gap = "M" $: gapStyle = gap === "L" diff --git a/packages/bbui/src/FancyForm/FancySelect.svelte b/packages/bbui/src/FancyForm/FancySelect.svelte index e015f51570..14911f10ab 100644 --- a/packages/bbui/src/FancyForm/FancySelect.svelte +++ b/packages/bbui/src/FancyForm/FancySelect.svelte @@ -12,11 +12,13 @@ export let error = null export let validate = null export let options = [] + export let footer = null export let isOptionEnabled = () => true export let getOptionLabel = option => extractProperty(option, "label") export let getOptionValue = option => extractProperty(option, "value") export let getOptionSubtitle = option => extractProperty(option, "subtitle") export let getOptionColour = () => null + const dispatch = createEventDispatcher() let open = false @@ -100,6 +102,7 @@ {error} {disabled} {options} + {footer} {getOptionLabel} {getOptionValue} {getOptionSubtitle} diff --git a/packages/bbui/src/Form/Core/InputDropdown.svelte b/packages/bbui/src/Form/Core/InputDropdown.svelte index 153501ce5a..128353b7b9 100644 --- a/packages/bbui/src/Form/Core/InputDropdown.svelte +++ b/packages/bbui/src/Form/Core/InputDropdown.svelte @@ -17,7 +17,7 @@ export let options = [] export let getOptionLabel = option => extractProperty(option, "label") export let getOptionValue = option => extractProperty(option, "value") - + export let getOptionSubtitle = option => option?.subtitle export let isOptionSelected = () => false const dispatch = createEventDispatcher() @@ -135,7 +135,7 @@ class="spectrum-Textfield-input spectrum-InputGroup-input" /> -
+
- {#if open} -
-
    - {#each options as option, idx} -
  • onPick(getOptionValue(option, idx))} - > - - {getOptionLabel(option, idx)} - - -
  • - {/each} -
-
- {/if}
+ {#if open} +
+
    + {#each options as option, idx} +
  • onPick(getOptionValue(option, idx))} + > + + {getOptionLabel(option, idx)} + {#if getOptionSubtitle(option, idx)} + + {getOptionSubtitle(option, idx)} + + {/if} + + +
  • + {/each} +
+
+ {/if}
diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index 6361b345d1..94fbe73cf2 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -224,13 +224,12 @@ {/if} - {#if getOptionSubtitle(option, idx)} - {getOptionSubtitle(option, idx)} - {/if} - {getOptionLabel(option, idx)} + {#if getOptionSubtitle(option, idx)} + + {getOptionSubtitle(option, idx)} + + {/if} {#if option.tag} @@ -275,10 +274,9 @@ font-size: 12px; line-height: 15px; font-weight: 500; - top: 10px; color: var(--spectrum-global-color-gray-600); display: block; - margin-bottom: var(--spacing-s); + margin-top: var(--spacing-s); } .spectrum-Picker-label.auto-width { diff --git a/packages/bbui/src/Form/Core/Select.svelte b/packages/bbui/src/Form/Core/Select.svelte index e928ae689d..be22000f0b 100644 --- a/packages/bbui/src/Form/Core/Select.svelte +++ b/packages/bbui/src/Form/Core/Select.svelte @@ -10,8 +10,9 @@ export let getOptionLabel = option => option export let getOptionValue = option => option export let getOptionIcon = () => null - export let useOptionIconImage = false export let getOptionColour = () => null + export let getOptionSubtitle = () => null + export let useOptionIconImage = false export let isOptionEnabled export let readonly = false export let quiet = false @@ -82,8 +83,9 @@ {getOptionLabel} {getOptionValue} {getOptionIcon} - {useOptionIconImage} {getOptionColour} + {getOptionSubtitle} + {useOptionIconImage} {isOptionEnabled} {autocomplete} {sort} diff --git a/packages/bbui/src/Form/Core/Switch.svelte b/packages/bbui/src/Form/Core/Switch.svelte index ffbef6b27d..deffc19167 100644 --- a/packages/bbui/src/Form/Core/Switch.svelte +++ b/packages/bbui/src/Form/Core/Switch.svelte @@ -18,6 +18,7 @@ checked={value} {disabled} on:change={onChange} + on:click {id} type="checkbox" class="spectrum-Switch-input" diff --git a/packages/bbui/src/Form/Core/TextField.svelte b/packages/bbui/src/Form/Core/TextField.svelte index b10997c904..3335d3567b 100644 --- a/packages/bbui/src/Form/Core/TextField.svelte +++ b/packages/bbui/src/Form/Core/TextField.svelte @@ -20,7 +20,7 @@ let focus = false const updateValue = newValue => { - if (readonly) { + if (readonly || disabled) { return } if (type === "number") { @@ -31,14 +31,14 @@ } const onFocus = () => { - if (readonly) { + if (readonly || disabled) { return } focus = true } const onBlur = event => { - if (readonly) { + if (readonly || disabled) { return } focus = false @@ -46,14 +46,14 @@ } const onInput = event => { - if (readonly || !updateOnChange) { + if (readonly || !updateOnChange || disabled) { return } updateValue(event.target.value) } const updateValueOnEnter = event => { - if (readonly) { + if (readonly || disabled) { return } if (event.key === "Enter") { @@ -69,6 +69,7 @@ } onMount(() => { + if (disabled) return focus = autofocus if (focus) field.focus() }) @@ -108,4 +109,16 @@ .spectrum-Textfield { width: 100%; } + + input::placeholder { + color: var(--grey-7); + } + + input:hover::placeholder { + color: var(--grey-7) !important; + } + + input:focus::placeholder { + color: var(--grey-7) !important; + } diff --git a/packages/bbui/src/Form/InputDropdown.svelte b/packages/bbui/src/Form/InputDropdown.svelte index 93766495b8..54b07cf0d7 100644 --- a/packages/bbui/src/Form/InputDropdown.svelte +++ b/packages/bbui/src/Form/InputDropdown.svelte @@ -43,6 +43,7 @@ {quiet} {autofocus} {options} + isOptionSelected={option => option === dropdownValue} on:change={onChange} on:pick={onPick} on:click diff --git a/packages/bbui/src/Form/Select.svelte b/packages/bbui/src/Form/Select.svelte index f9d9532ba5..8235b68faf 100644 --- a/packages/bbui/src/Form/Select.svelte +++ b/packages/bbui/src/Form/Select.svelte @@ -13,9 +13,10 @@ export let options = [] export let getOptionLabel = option => extractProperty(option, "label") export let getOptionValue = option => extractProperty(option, "value") + export let getOptionSubtitle = option => option?.subtitle export let getOptionIcon = option => option?.icon - export let useOptionIconImage = false export let getOptionColour = option => option?.colour + export let useOptionIconImage = false export let isOptionEnabled export let quiet = false export let autoWidth = false @@ -58,6 +59,7 @@ {getOptionValue} {getOptionIcon} {getOptionColour} + {getOptionSubtitle} {useOptionIconImage} {isOptionEnabled} {autocomplete} diff --git a/packages/bbui/src/Form/Toggle.svelte b/packages/bbui/src/Form/Toggle.svelte index d452aa8f80..1fbad949ca 100644 --- a/packages/bbui/src/Form/Toggle.svelte +++ b/packages/bbui/src/Form/Toggle.svelte @@ -19,5 +19,5 @@ - + diff --git a/packages/builder/src/builderStore/componentUtils.js b/packages/builder/src/builderStore/componentUtils.js index 522dbae416..a7ee3ff351 100644 --- a/packages/builder/src/builderStore/componentUtils.js +++ b/packages/builder/src/builderStore/componentUtils.js @@ -1,4 +1,5 @@ import { store } from "./index" +import { get } from "svelte/store" import { Helpers } from "@budibase/bbui" import { decodeJSBinding, @@ -238,6 +239,10 @@ export const makeComponentUnique = component => { } export const getComponentText = component => { + if (component == null) { + return "" + } + if (component?._instanceName) { return component._instanceName } @@ -246,3 +251,16 @@ export const getComponentText = component => { "component" return capitalise(type) } + +export const getComponentName = component => { + if (component == null) { + return "" + } + + const components = get(store)?.components || {} + const componentDefinition = components[component._component] || {} + const name = + componentDefinition.friendlyName || componentDefinition.name || "" + + return name +} diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 85ac822006..d86e94aba2 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -29,6 +29,12 @@ const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g const CAPTURE_HBS_TEMPLATE = /{{[\S\s]*?}}/g +const UpdateReferenceAction = { + ADD: "add", + DELETE: "delete", + MOVE: "move", +} + /** * Gets all bindable data context fields and instance fields. */ @@ -1226,3 +1232,81 @@ export const runtimeToReadableBinding = ( "readableBinding" ) } + +/** + * Used to update binding references for automation or action steps + * + * @param obj - The object to be updated + * @param originalIndex - The original index of the step being moved. Not applicable to add/delete. + * @param modifiedIndex - The new index of the step being modified + * @param action - Used to determine if a step is being added, deleted or moved + * @param label - The binding text that describes the steps + */ +export const updateReferencesInObject = ({ + obj, + modifiedIndex, + action, + label, + originalIndex, +}) => { + const stepIndexRegex = new RegExp(`{{\\s*${label}\\.(\\d+)\\.`, "g") + const updateActionStep = (str, index, replaceWith) => + str.replace(`{{ ${label}.${index}.`, `{{ ${label}.${replaceWith}.`) + for (const key in obj) { + if (typeof obj[key] === "string") { + let matches + while ((matches = stepIndexRegex.exec(obj[key])) !== null) { + const referencedStep = parseInt(matches[1]) + if ( + action === UpdateReferenceAction.ADD && + referencedStep >= modifiedIndex + ) { + obj[key] = updateActionStep( + obj[key], + referencedStep, + referencedStep + 1 + ) + } else if ( + action === UpdateReferenceAction.DELETE && + referencedStep > modifiedIndex + ) { + obj[key] = updateActionStep( + obj[key], + referencedStep, + referencedStep - 1 + ) + } else if (action === UpdateReferenceAction.MOVE) { + if (referencedStep === originalIndex) { + obj[key] = updateActionStep(obj[key], referencedStep, modifiedIndex) + } else if ( + modifiedIndex <= referencedStep && + modifiedIndex < originalIndex + ) { + obj[key] = updateActionStep( + obj[key], + referencedStep, + referencedStep + 1 + ) + } else if ( + modifiedIndex >= referencedStep && + modifiedIndex > originalIndex + ) { + obj[key] = updateActionStep( + obj[key], + referencedStep, + referencedStep - 1 + ) + } + } + } + } else if (typeof obj[key] === "object" && obj[key] !== null) { + updateReferencesInObject({ + obj: obj[key], + modifiedIndex, + action, + label, + originalIndex, + }) + } + } +} diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index 38b7cdf7c2..ece17cb46f 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -4,7 +4,7 @@ import { getTemporalStore } from "./store/temporal" import { getThemeStore } from "./store/theme" import { getUserStore } from "./store/users" import { getDeploymentStore } from "./store/deployments" -import { derived, writable, get } from "svelte/store" +import { derived, get } from "svelte/store" import { findComponent, findComponentPath } from "./componentUtils" import { RoleUtils } from "@budibase/frontend-core" import { createHistoryStore } from "builderStore/store/history" @@ -146,5 +146,3 @@ export const userSelectedResourceMap = derived(userStore, $userStore => { export const isOnlyUser = derived(userStore, $userStore => { return $userStore.length < 2 }) - -export const screensHeight = writable("210px") diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js index ba2458f414..af83f73dc6 100644 --- a/packages/builder/src/builderStore/store/automation/index.js +++ b/packages/builder/src/builderStore/store/automation/index.js @@ -4,6 +4,7 @@ import { cloneDeep } from "lodash/fp" import { generate } from "shortid" import { selectedAutomation } from "builderStore" import { notifications } from "@budibase/bbui" +import { updateReferencesInObject } from "builderStore/dataBinding" const initialAutomationState = { automations: [], @@ -22,34 +23,14 @@ export const getAutomationStore = () => { return store } -const updateReferencesInObject = (obj, modifiedIndex, action) => { - const regex = /{{\s*steps\.(\d+)\./g - for (const key in obj) { - if (typeof obj[key] === "string") { - let matches - while ((matches = regex.exec(obj[key])) !== null) { - const referencedStep = parseInt(matches[1]) - if (action === "add" && referencedStep >= modifiedIndex) { - obj[key] = obj[key].replace( - `{{ steps.${referencedStep}.`, - `{{ steps.${referencedStep + 1}.` - ) - } else if (action === "delete" && referencedStep > modifiedIndex) { - obj[key] = obj[key].replace( - `{{ steps.${referencedStep}.`, - `{{ steps.${referencedStep - 1}.` - ) - } - } - } else if (typeof obj[key] === "object" && obj[key] !== null) { - updateReferencesInObject(obj[key], modifiedIndex, action) - } - } -} - const updateStepReferences = (steps, modifiedIndex, action) => { steps.forEach(step => { - updateReferencesInObject(step.inputs, modifiedIndex, action) + updateReferencesInObject({ + obj: step.inputs, + modifiedIndex, + action, + label: "steps", + }) }) } diff --git a/packages/builder/src/builderStore/tests/dataBinding.test.js b/packages/builder/src/builderStore/tests/dataBinding.test.js index 47f6564749..039e33a94d 100644 --- a/packages/builder/src/builderStore/tests/dataBinding.test.js +++ b/packages/builder/src/builderStore/tests/dataBinding.test.js @@ -2,6 +2,7 @@ import { expect, describe, it, vi } from "vitest" import { runtimeToReadableBinding, readableToRuntimeBinding, + updateReferencesInObject, } from "../dataBinding" vi.mock("@budibase/frontend-core") @@ -84,3 +85,461 @@ describe("readableToRuntimeBinding", () => { ).toEqual(`Hello {{ [user].[firstName] }}! The count is {{ count }}.`) }) }) + +describe("updateReferencesInObject", () => { + it("should increment steps in sequence on 'add'", () => { + let obj = [ + { + id: "a0", + parameters: { + text: "Alpha", + }, + }, + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "b2", + parameters: { + text: "Banana {{ actions.1.row }}", + }, + }, + { + id: "c3", + parameters: { + text: "Carrot {{ actions.1.row }}", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.3.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.4.row }}", + }, + }, + ] + updateReferencesInObject({ + obj, + modifiedIndex: 0, + action: "add", + label: "actions", + }) + + expect(obj).toEqual([ + { + id: "a0", + parameters: { + text: "Alpha", + }, + }, + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "b2", + parameters: { + text: "Banana {{ actions.2.row }}", + }, + }, + { + id: "c3", + parameters: { + text: "Carrot {{ actions.2.row }}", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.4.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.5.row }}", + }, + }, + ]) + }) + + it("should decrement steps in sequence on 'delete'", () => { + let obj = [ + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "b2", + parameters: { + text: "Banana {{ actions.1.row }}", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.3.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.4.row }}", + }, + }, + ] + updateReferencesInObject({ + obj, + modifiedIndex: 2, + action: "delete", + label: "actions", + }) + + expect(obj).toEqual([ + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "b2", + parameters: { + text: "Banana {{ actions.1.row }}", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.2.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.3.row }}", + }, + }, + ]) + }) + + it("should handle on 'move' to a lower index", () => { + let obj = [ + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "b2", + parameters: { + text: "Banana {{ actions.0.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.3.row }}", + }, + }, + { + id: "c3", + parameters: { + text: "Carrot {{ actions.0.row }}", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.2.row }}", + }, + }, + ] + updateReferencesInObject({ + obj, + modifiedIndex: 2, + action: "move", + label: "actions", + originalIndex: 4, + }) + + expect(obj).toEqual([ + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "b2", + parameters: { + text: "Banana {{ actions.0.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.4.row }}", + }, + }, + { + id: "c3", + parameters: { + text: "Carrot {{ actions.0.row }}", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.3.row }}", + }, + }, + ]) + }) + + it("should handle on 'move' to a higher index", () => { + let obj = [ + { + id: "b2", + parameters: { + text: "Banana {{ actions.0.row }}", + }, + }, + { + id: "c3", + parameters: { + text: "Carrot {{ actions.0.row }}", + }, + }, + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.2.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.3.row }}", + }, + }, + ] + updateReferencesInObject({ + obj, + modifiedIndex: 2, + action: "move", + label: "actions", + originalIndex: 0, + }) + + expect(obj).toEqual([ + { + id: "b2", + parameters: { + text: "Banana {{ actions.2.row }}", + }, + }, + { + id: "c3", + parameters: { + text: "Carrot {{ actions.2.row }}", + }, + }, + { + id: "a1", + parameters: { + text: "Apple", + }, + }, + { + id: "d4", + parameters: { + text: "Dog {{ actions.1.row }}", + }, + }, + { + id: "e5", + parameters: { + text: "Eagle {{ actions.3.row }}", + }, + }, + ]) + }) + + it("should handle on 'move' of action being referenced, dragged to a higher index", () => { + let obj = [ + { + "##eventHandlerType": "Validate Form", + id: "cCD0Dwcnq", + }, + { + "##eventHandlerType": "Close Screen Modal", + id: "3fbbIOfN0H", + }, + { + "##eventHandlerType": "Save Row", + parameters: { + tableId: "ta_bb_employee", + }, + id: "aehg5cTmhR", + }, + { + "##eventHandlerType": "Close Side Panel", + id: "mzkpf86cxo", + }, + { + "##eventHandlerType": "Navigate To", + id: "h0uDFeJa8A", + }, + { + parameters: { + autoDismiss: true, + type: "success", + message: "{{ actions.1.row }}", + }, + "##eventHandlerType": "Show Notification", + id: "JEI5lAyJZ", + }, + ] + updateReferencesInObject({ + obj, + modifiedIndex: 2, + action: "move", + label: "actions", + originalIndex: 1, + }) + + expect(obj).toEqual([ + { + "##eventHandlerType": "Validate Form", + id: "cCD0Dwcnq", + }, + { + "##eventHandlerType": "Close Screen Modal", + id: "3fbbIOfN0H", + }, + { + "##eventHandlerType": "Save Row", + parameters: { + tableId: "ta_bb_employee", + }, + id: "aehg5cTmhR", + }, + { + "##eventHandlerType": "Close Side Panel", + id: "mzkpf86cxo", + }, + { + "##eventHandlerType": "Navigate To", + id: "h0uDFeJa8A", + }, + { + parameters: { + autoDismiss: true, + type: "success", + message: "{{ actions.2.row }}", + }, + "##eventHandlerType": "Show Notification", + id: "JEI5lAyJZ", + }, + ]) + }) + + it("should handle on 'move' of action being referenced, dragged to a lower index", () => { + let obj = [ + { + "##eventHandlerType": "Save Row", + parameters: { + tableId: "ta_bb_employee", + }, + id: "aehg5cTmhR", + }, + { + "##eventHandlerType": "Validate Form", + id: "cCD0Dwcnq", + }, + { + "##eventHandlerType": "Close Screen Modal", + id: "3fbbIOfN0H", + }, + { + "##eventHandlerType": "Close Side Panel", + id: "mzkpf86cxo", + }, + { + "##eventHandlerType": "Navigate To", + id: "h0uDFeJa8A", + }, + { + parameters: { + autoDismiss: true, + type: "success", + message: "{{ actions.4.row }}", + }, + "##eventHandlerType": "Show Notification", + id: "JEI5lAyJZ", + }, + ] + updateReferencesInObject({ + obj, + modifiedIndex: 0, + action: "move", + label: "actions", + originalIndex: 4, + }) + + expect(obj).toEqual([ + { + "##eventHandlerType": "Save Row", + parameters: { + tableId: "ta_bb_employee", + }, + id: "aehg5cTmhR", + }, + { + "##eventHandlerType": "Validate Form", + id: "cCD0Dwcnq", + }, + { + "##eventHandlerType": "Close Screen Modal", + id: "3fbbIOfN0H", + }, + { + "##eventHandlerType": "Close Side Panel", + id: "mzkpf86cxo", + }, + { + "##eventHandlerType": "Navigate To", + id: "h0uDFeJa8A", + }, + { + parameters: { + autoDismiss: true, + type: "success", + message: "{{ actions.0.row }}", + }, + "##eventHandlerType": "Show Notification", + id: "JEI5lAyJZ", + }, + ]) + }) +}) diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte index 86235297ae..3fd2708186 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte @@ -64,7 +64,7 @@ {:else if schema.type === "link"} onChange(e, field)} useLabel={false} diff --git a/packages/builder/src/components/automation/SetupPanel/TableSelector.svelte b/packages/builder/src/components/automation/SetupPanel/TableSelector.svelte index 3434219384..1645ded66b 100644 --- a/packages/builder/src/components/automation/SetupPanel/TableSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/TableSelector.svelte @@ -22,7 +22,7 @@ { if (e.key.toLowerCase() === "enter") { e.target.blur() diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte index 09f97302fd..46eda34c2b 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte @@ -25,6 +25,7 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte index 337b91a033..e3e24d7b13 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte @@ -1,108 +1,50 @@ - -
+
$goto("../new")} />
@@ -110,6 +52,7 @@ {#if filteredScreens?.length} {#each filteredScreens as screen (screen._id)} @@ -148,14 +93,12 @@ min-height: 147px; max-height: calc(100% - 147px); position: relative; - transition: height 300ms ease-out; + transition: height 300ms ease-out, max-height 300ms ease-out; + height: 210px; } - .screens.search { - max-height: none; - } - .screens.resizing { - user-select: none; - cursor: row-resize; + .screens.searching { + max-height: 100%; + height: 100% !important; } .header { @@ -177,9 +120,6 @@ overflow: auto; flex-grow: 1; } - .screens.resizing .content { - pointer-events: none; - } .screens :global(.nav-item) { padding-right: 8px !important; @@ -217,4 +157,10 @@ .divider:hover:after { background: var(--spectrum-global-color-gray-300); } + .divider.disabled { + cursor: auto; + } + .divider.disabled:after { + background: var(--spectrum-global-color-gray-200); + } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte index 0e630b4f39..ab29f2ea0d 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte @@ -40,6 +40,7 @@ } .content { + width: 100vw; display: flex; flex-direction: row; justify-content: flex-start; diff --git a/packages/builder/src/pages/builder/app/[application]/settings/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/settings/_layout.svelte index 801ddd1130..899b58ecf0 100644 --- a/packages/builder/src/pages/builder/app/[application]/settings/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/settings/_layout.svelte @@ -4,8 +4,6 @@ import { url, isActive } from "@roxi/routify" import DeleteModal from "components/deploy/DeleteModal.svelte" import { isOnlyUser } from "builderStore" - import { auth } from "stores/portal" - import { sdk } from "@budibase/shared-core" let deleteModal @@ -46,24 +44,22 @@ url={$url("./version")} active={$isActive("./version")} /> - {#if sdk.users.isGlobalBuilder($auth.user)} -
- - { - deleteModal.show() - }} - /> - -
- {/if} +
+ + { + deleteModal.show() + }} + /> + +
diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index ad172e34ca..aad33f852a 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -16,7 +16,7 @@ let activeTab = "Apps" $: $url(), updateActiveTab($menu) - $: isOnboarding = !$apps.length && sdk.users.isGlobalBuilder($auth.user) + $: isOnboarding = !$apps.length && sdk.users.hasBuilderPermissions($auth.user) const updateActiveTab = menu => { for (let entry of menu) { diff --git a/packages/builder/src/pages/builder/portal/apps/_layout.svelte b/packages/builder/src/pages/builder/portal/apps/_layout.svelte index 4067856bfa..8810edca9c 100644 --- a/packages/builder/src/pages/builder/portal/apps/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/apps/_layout.svelte @@ -14,7 +14,7 @@ import PortalSideBar from "./_components/PortalSideBar.svelte" // Don't block loading if we've already hydrated state - let loaded = $apps.length != null + let loaded = !!$apps?.length onMount(async () => { try { @@ -34,7 +34,7 @@ } // Go to new app page if no apps exists - if (!$apps.length && sdk.users.isGlobalBuilder($auth.user)) { + if (!$apps.length && sdk.users.hasBuilderPermissions($auth.user)) { $redirect("./onboarding") } } catch (error) { diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte index 59267f37e7..cf2c61b11d 100644 --- a/packages/builder/src/pages/builder/portal/apps/index.svelte +++ b/packages/builder/src/pages/builder/portal/apps/index.svelte @@ -1,5 +1,6 @@ diff --git a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte index ec10ec8316..368e45d83c 100644 --- a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte @@ -55,6 +55,7 @@ }, role: { width: "1fr", + displayName: "Access", }, } const customGroupTableRenderers = [ @@ -98,7 +99,7 @@ return y._id === userId }) }) - $: globalRole = sdk.users.isAdmin(user) ? "admin" : "appUser" + $: globalRole = users.getUserRole(user) const getAvailableApps = (appList, privileged, roles) => { let availableApps = appList.slice() @@ -177,12 +178,21 @@ } async function updateUserRole({ detail }) { - if (detail === "developer") { + if (detail === Constants.BudibaseRoles.Developer) { toggleFlags({ admin: { global: false }, builder: { global: true } }) - } else if (detail === "admin") { + } else if (detail === Constants.BudibaseRoles.Admin) { toggleFlags({ admin: { global: true }, builder: { global: true } }) - } else if (detail === "appUser") { + } else if (detail === Constants.BudibaseRoles.AppUser) { toggleFlags({ admin: { global: false }, builder: { global: false } }) + } else if (detail === Constants.BudibaseRoles.Creator) { + toggleFlags({ + admin: { global: false }, + builder: { + global: false, + creator: true, + apps: user?.builder?.apps || [], + }, + }) } } @@ -295,6 +305,7 @@