Merge branch 'master' into refactor/remove-field-types
This commit is contained in:
commit
50b3138acb
|
@ -33,13 +33,13 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -50,14 +50,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -80,7 +80,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -92,14 +92,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -116,14 +116,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -140,14 +140,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -165,14 +165,14 @@ jobs:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo and submodules
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -189,13 +189,13 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -219,7 +219,7 @@ jobs:
|
||||||
if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase')
|
if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase')
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo and submodules
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
@ -249,7 +249,7 @@ jobs:
|
||||||
|
|
||||||
- name: Check submodule merged to base branch
|
- name: Check submodule merged to base branch
|
||||||
if: ${{ steps.get_pro_commits.outputs.base_commit != '' }}
|
if: ${{ steps.get_pro_commits.outputs.base_commit != '' }}
|
||||||
uses: actions/github-script@v4
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
@ -269,7 +269,7 @@ jobs:
|
||||||
if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase')
|
if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase')
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo and submodules
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
@ -299,7 +299,7 @@ jobs:
|
||||||
|
|
||||||
- name: Check submodule merged to base branch
|
- name: Check submodule merged to base branch
|
||||||
if: ${{ steps.get_accountportal_commits.outputs.base_commit != '' }}
|
if: ${{ steps.get_accountportal_commits.outputs.base_commit != '' }}
|
||||||
uses: actions/github-script@v4
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
github.event.label.name == 'feature-branch'
|
github.event.label.name == 'feature-branch'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: passeidireto/trigger-external-workflow-action@main
|
- uses: passeidireto/trigger-external-workflow-action@main
|
||||||
env:
|
env:
|
||||||
PAYLOAD_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.BRANCH || github.head_ref }}
|
PAYLOAD_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.BRANCH || github.head_ref }}
|
||||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
contains(github.event.pull_request.labels.*.name, 'feature-branch')
|
contains(github.event.pull_request.labels.*.name, 'feature-branch')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: passeidireto/trigger-external-workflow-action@main
|
- uses: passeidireto/trigger-external-workflow-action@main
|
||||||
env:
|
env:
|
||||||
PAYLOAD_BRANCH: ${{ github.head_ref }}
|
PAYLOAD_BRANCH: ${{ github.head_ref }}
|
||||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "Ref is not master, you must run this job from master."
|
echo "Ref is not master, you must run this job from master."
|
||||||
exit 1
|
exit 1
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
@ -53,7 +53,7 @@ jobs:
|
||||||
needs: [tag-release]
|
needs: [tag-release]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: peter-evans/repository-dispatch@v2
|
- uses: peter-evans/repository-dispatch@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.15.5",
|
"version": "2.15.7",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 05c90ce55144e260da6688335c16783eab79bf96
|
Subproject commit e9af6686ba135c367e9145a53d26c68325b9bf68
|
|
@ -179,6 +179,7 @@ const environment = {
|
||||||
...getPackageJsonFields(),
|
...getPackageJsonFields(),
|
||||||
DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER,
|
DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER,
|
||||||
OFFLINE_MODE: process.env.OFFLINE_MODE,
|
OFFLINE_MODE: process.env.OFFLINE_MODE,
|
||||||
|
SESSION_EXPIRY_SECONDS: process.env.SESSION_EXPIRY_SECONDS,
|
||||||
_set(key: any, value: any) {
|
_set(key: any, value: any) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -2,6 +2,7 @@ export * as configs from "./configs"
|
||||||
export * as events from "./events"
|
export * as events from "./events"
|
||||||
export * as migrations from "./migrations"
|
export * as migrations from "./migrations"
|
||||||
export * as users from "./users"
|
export * as users from "./users"
|
||||||
|
export * as userUtils from "./users/utils"
|
||||||
export * as roles from "./security/roles"
|
export * as roles from "./security/roles"
|
||||||
export * as permissions from "./security/permissions"
|
export * as permissions from "./security/permissions"
|
||||||
export * as accounts from "./accounts"
|
export * as accounts from "./accounts"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
const redis = require("../redis/init")
|
import * as redis from "../redis/init"
|
||||||
const { v4: uuidv4 } = require("uuid")
|
import { v4 as uuidv4 } from "uuid"
|
||||||
const { logWarn } = require("../logging")
|
import { logWarn } from "../logging"
|
||||||
|
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
import { Duration } from "../utils"
|
||||||
import {
|
import {
|
||||||
Session,
|
Session,
|
||||||
ScannedSession,
|
ScannedSession,
|
||||||
|
@ -10,8 +10,10 @@ import {
|
||||||
CreateSession,
|
CreateSession,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
// a week in seconds
|
// a week expiry is the default
|
||||||
const EXPIRY_SECONDS = 86400 * 7
|
const EXPIRY_SECONDS = env.SESSION_EXPIRY_SECONDS
|
||||||
|
? parseInt(env.SESSION_EXPIRY_SECONDS)
|
||||||
|
: Duration.fromDays(7).toSeconds()
|
||||||
|
|
||||||
function makeSessionID(userId: string, sessionId: string) {
|
function makeSessionID(userId: string, sessionId: string) {
|
||||||
return `${userId}/${sessionId}`
|
return `${userId}/${sessionId}`
|
||||||
|
|
|
@ -251,7 +251,8 @@ export class UserDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = dbUser ? 0 : 1 // no change if there is existing user
|
const change = dbUser ? 0 : 1 // no change if there is existing user
|
||||||
const creatorsChange = isCreator(dbUser) !== isCreator(user) ? 1 : 0
|
const creatorsChange =
|
||||||
|
(await isCreator(dbUser)) !== (await isCreator(user)) ? 1 : 0
|
||||||
return UserDB.quotas.addUsers(change, creatorsChange, async () => {
|
return UserDB.quotas.addUsers(change, creatorsChange, async () => {
|
||||||
await validateUniqueUser(email, tenantId)
|
await validateUniqueUser(email, tenantId)
|
||||||
|
|
||||||
|
@ -335,7 +336,7 @@ export class UserDB {
|
||||||
}
|
}
|
||||||
newUser.userGroups = groups || []
|
newUser.userGroups = groups || []
|
||||||
newUsers.push(newUser)
|
newUsers.push(newUser)
|
||||||
if (isCreator(newUser)) {
|
if (await isCreator(newUser)) {
|
||||||
newCreators.push(newUser)
|
newCreators.push(newUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,12 +433,16 @@ export class UserDB {
|
||||||
_deleted: true,
|
_deleted: true,
|
||||||
}))
|
}))
|
||||||
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
|
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
|
||||||
const creatorsToDelete = usersToDelete.filter(isCreator)
|
|
||||||
|
const creatorsEval = await Promise.all(usersToDelete.map(isCreator))
|
||||||
|
const creatorsToDeleteCount = creatorsEval.filter(
|
||||||
|
creator => !!creator
|
||||||
|
).length
|
||||||
|
|
||||||
for (let user of usersToDelete) {
|
for (let user of usersToDelete) {
|
||||||
await bulkDeleteProcessing(user)
|
await bulkDeleteProcessing(user)
|
||||||
}
|
}
|
||||||
await UserDB.quotas.removeUsers(toDelete.length, creatorsToDelete.length)
|
await UserDB.quotas.removeUsers(toDelete.length, creatorsToDeleteCount)
|
||||||
|
|
||||||
// Build Response
|
// Build Response
|
||||||
// index users by id
|
// index users by id
|
||||||
|
@ -486,7 +491,7 @@ export class UserDB {
|
||||||
|
|
||||||
await db.remove(userId, dbUser._rev)
|
await db.remove(userId, dbUser._rev)
|
||||||
|
|
||||||
const creatorsToDelete = isCreator(dbUser) ? 1 : 0
|
const creatorsToDelete = (await isCreator(dbUser)) ? 1 : 0
|
||||||
await UserDB.quotas.removeUsers(1, creatorsToDelete)
|
await UserDB.quotas.removeUsers(1, creatorsToDelete)
|
||||||
await eventHelpers.handleDeleteEvents(dbUser)
|
await eventHelpers.handleDeleteEvents(dbUser)
|
||||||
await cache.user.invalidateUser(userId)
|
await cache.user.invalidateUser(userId)
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { User, UserGroup } from "@budibase/types"
|
||||||
|
import { generator, structures } from "../../../tests"
|
||||||
|
import { DBTestConfiguration } from "../../../tests/extra"
|
||||||
|
import { getGlobalDB } from "../../context"
|
||||||
|
import { isCreator } from "../utils"
|
||||||
|
|
||||||
|
const config = new DBTestConfiguration()
|
||||||
|
|
||||||
|
describe("Users", () => {
|
||||||
|
it("User is a creator if it is configured as a global builder", async () => {
|
||||||
|
const user: User = structures.users.user({ builder: { global: true } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it is configured as a global admin", async () => {
|
||||||
|
const user: User = structures.users.user({ admin: { global: true } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it is configured with creator permission", async () => {
|
||||||
|
const user: User = structures.users.user({ builder: { creator: true } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it is a builder in some application", async () => {
|
||||||
|
const user: User = structures.users.user({ builder: { apps: ["app1"] } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it has CREATOR permission in some application", async () => {
|
||||||
|
const user: User = structures.users.user({ roles: { app1: "CREATOR" } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it has ADMIN permission in some application", async () => {
|
||||||
|
const user: User = structures.users.user({ roles: { app1: "ADMIN" } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it remains to a group with ADMIN permissions", async () => {
|
||||||
|
const usersInGroup = 10
|
||||||
|
const groupId = "gr_17abffe89e0b40268e755b952f101a59"
|
||||||
|
const group: UserGroup = {
|
||||||
|
...structures.userGroups.userGroup(),
|
||||||
|
...{ _id: groupId, roles: { app1: "ADMIN" } },
|
||||||
|
}
|
||||||
|
const users: User[] = []
|
||||||
|
for (const _ of Array.from({ length: usersInGroup })) {
|
||||||
|
const userId = `us_${generator.guid()}`
|
||||||
|
const user: User = structures.users.user({
|
||||||
|
_id: userId,
|
||||||
|
userGroups: [groupId],
|
||||||
|
})
|
||||||
|
users.push(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
await config.doInTenant(async () => {
|
||||||
|
const db = getGlobalDB()
|
||||||
|
await db.put(group)
|
||||||
|
for (let user of users) {
|
||||||
|
await db.put(user)
|
||||||
|
const creator = await isCreator(user)
|
||||||
|
expect(creator).toBe(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -309,7 +309,8 @@ export async function getCreatorCount() {
|
||||||
let creators = 0
|
let creators = 0
|
||||||
async function iterate(startPage?: string) {
|
async function iterate(startPage?: string) {
|
||||||
const page = await paginatedUsers({ bookmark: startPage })
|
const page = await paginatedUsers({ bookmark: startPage })
|
||||||
creators += page.data.filter(isCreator).length
|
const creatorsEval = await Promise.all(page.data.map(isCreator))
|
||||||
|
creators += creatorsEval.filter(creator => !!creator).length
|
||||||
if (page.hasNextPage) {
|
if (page.hasNextPage) {
|
||||||
await iterate(page.nextPage)
|
await iterate(page.nextPage)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CloudAccount } from "@budibase/types"
|
import { CloudAccount, ContextUser, User, UserGroup } from "@budibase/types"
|
||||||
import * as accountSdk from "../accounts"
|
import * as accountSdk from "../accounts"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { getPlatformUser } from "./lookup"
|
import { getPlatformUser } from "./lookup"
|
||||||
|
@ -6,17 +6,48 @@ import { EmailUnavailableError } from "../errors"
|
||||||
import { getTenantId } from "../context"
|
import { getTenantId } from "../context"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { getAccountByTenantId } from "../accounts"
|
import { getAccountByTenantId } from "../accounts"
|
||||||
|
import { BUILTIN_ROLE_IDS } from "../security/roles"
|
||||||
|
import * as context from "../context"
|
||||||
|
|
||||||
// extract from shared-core to make easily accessible from backend-core
|
// extract from shared-core to make easily accessible from backend-core
|
||||||
export const isBuilder = sdk.users.isBuilder
|
export const isBuilder = sdk.users.isBuilder
|
||||||
export const isAdmin = sdk.users.isAdmin
|
export const isAdmin = sdk.users.isAdmin
|
||||||
export const isCreator = sdk.users.isCreator
|
|
||||||
export const isGlobalBuilder = sdk.users.isGlobalBuilder
|
export const isGlobalBuilder = sdk.users.isGlobalBuilder
|
||||||
export const isAdminOrBuilder = sdk.users.isAdminOrBuilder
|
export const isAdminOrBuilder = sdk.users.isAdminOrBuilder
|
||||||
export const hasAdminPermissions = sdk.users.hasAdminPermissions
|
export const hasAdminPermissions = sdk.users.hasAdminPermissions
|
||||||
export const hasBuilderPermissions = sdk.users.hasBuilderPermissions
|
export const hasBuilderPermissions = sdk.users.hasBuilderPermissions
|
||||||
export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions
|
export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions
|
||||||
|
|
||||||
|
export async function isCreator(user?: User | ContextUser) {
|
||||||
|
const isCreatorByUserDefinition = sdk.users.isCreator(user)
|
||||||
|
if (!isCreatorByUserDefinition && user) {
|
||||||
|
return await isCreatorByGroupMembership(user)
|
||||||
|
}
|
||||||
|
return isCreatorByUserDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isCreatorByGroupMembership(user?: User | ContextUser) {
|
||||||
|
const userGroups = user?.userGroups || []
|
||||||
|
if (userGroups.length > 0) {
|
||||||
|
const db = context.getGlobalDB()
|
||||||
|
const groups: UserGroup[] = []
|
||||||
|
for (let groupId of userGroups) {
|
||||||
|
try {
|
||||||
|
const group = await db.get<UserGroup>(groupId)
|
||||||
|
groups.push(group)
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.error !== "not_found") {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups.some(group =>
|
||||||
|
Object.values(group.roles || {}).includes(BUILTIN_ROLE_IDS.ADMIN)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
export async function validateUniqueUser(email: string, tenantId: string) {
|
export async function validateUniqueUser(email: string, tenantId: string) {
|
||||||
// check budibase users in other tenants
|
// check budibase users in other tenants
|
||||||
if (env.MULTI_TENANCY) {
|
if (env.MULTI_TENANCY) {
|
||||||
|
|
|
@ -18,7 +18,6 @@ export default function positionDropdown(element, opts) {
|
||||||
useAnchorWidth,
|
useAnchorWidth,
|
||||||
offset = 5,
|
offset = 5,
|
||||||
customUpdate,
|
customUpdate,
|
||||||
offsetBelow,
|
|
||||||
} = opts
|
} = opts
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
return
|
return
|
||||||
|
@ -48,7 +47,7 @@ export default function positionDropdown(element, opts) {
|
||||||
styles.top = anchorBounds.top - elementBounds.height - offset
|
styles.top = anchorBounds.top - elementBounds.height - offset
|
||||||
styles.maxHeight = maxHeight || 240
|
styles.maxHeight = maxHeight || 240
|
||||||
} else {
|
} else {
|
||||||
styles.top = anchorBounds.bottom + (offsetBelow || offset)
|
styles.top = anchorBounds.bottom + offset
|
||||||
styles.maxHeight =
|
styles.maxHeight =
|
||||||
maxHeight || window.innerHeight - anchorBounds.bottom - 20
|
maxHeight || window.innerHeight - anchorBounds.bottom - 20
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
export let autoWidth = false
|
export let autoWidth = false
|
||||||
export let searchTerm = null
|
export let searchTerm = null
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
export let customPopoverOffsetBelow
|
|
||||||
export let customPopoverMaxHeight
|
|
||||||
export let open = false
|
export let open = false
|
||||||
export let loading
|
export let loading
|
||||||
|
|
||||||
|
@ -98,7 +96,5 @@
|
||||||
{sort}
|
{sort}
|
||||||
{autoWidth}
|
{autoWidth}
|
||||||
{customPopoverHeight}
|
{customPopoverHeight}
|
||||||
{customPopoverOffsetBelow}
|
|
||||||
{customPopoverMaxHeight}
|
|
||||||
{loading}
|
{loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -37,8 +37,6 @@
|
||||||
export let sort = false
|
export let sort = false
|
||||||
export let searchTerm = null
|
export let searchTerm = null
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
export let customPopoverOffsetBelow
|
|
||||||
export let customPopoverMaxHeight
|
|
||||||
export let align = "left"
|
export let align = "left"
|
||||||
export let footer = null
|
export let footer = null
|
||||||
export let customAnchor = null
|
export let customAnchor = null
|
||||||
|
@ -156,9 +154,7 @@
|
||||||
on:close={() => (open = false)}
|
on:close={() => (open = false)}
|
||||||
useAnchorWidth={!autoWidth}
|
useAnchorWidth={!autoWidth}
|
||||||
maxWidth={autoWidth ? 400 : null}
|
maxWidth={autoWidth ? 400 : null}
|
||||||
maxHeight={customPopoverMaxHeight}
|
|
||||||
customHeight={customPopoverHeight}
|
customHeight={customPopoverHeight}
|
||||||
offsetBelow={customPopoverOffsetBelow}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="popover-content"
|
class="popover-content"
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
export let footer = null
|
export let footer = null
|
||||||
export let open = false
|
export let open = false
|
||||||
export let tag = null
|
export let tag = null
|
||||||
export let customPopoverOffsetBelow
|
|
||||||
export let customPopoverMaxHeight
|
|
||||||
export let searchTerm = null
|
export let searchTerm = null
|
||||||
export let loading
|
export let loading
|
||||||
|
|
||||||
|
@ -35,15 +33,20 @@
|
||||||
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
||||||
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
||||||
|
|
||||||
|
function compareOptionAndValue(option, value) {
|
||||||
|
return typeof compare === "function"
|
||||||
|
? compare(option, value)
|
||||||
|
: option === value
|
||||||
|
}
|
||||||
|
|
||||||
const getFieldAttribute = (getAttribute, value, options) => {
|
const getFieldAttribute = (getAttribute, value, options) => {
|
||||||
// Wait for options to load if there is a value but no options
|
// Wait for options to load if there is a value but no options
|
||||||
if (!options?.length) {
|
if (!options?.length) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
const index = options.findIndex((option, idx) => {
|
const index = options.findIndex((option, idx) =>
|
||||||
const opt = getOptionValue(option, idx)
|
compareOptionAndValue(getOptionValue(option, idx), value)
|
||||||
return typeof compare === "function" ? compare(opt, value) : opt === value
|
)
|
||||||
})
|
|
||||||
return index !== -1 ? getAttribute(options[index], index) : null
|
return index !== -1 ? getAttribute(options[index], index) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,11 +95,9 @@
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{sort}
|
{sort}
|
||||||
{tag}
|
{tag}
|
||||||
{customPopoverOffsetBelow}
|
|
||||||
{customPopoverMaxHeight}
|
|
||||||
isPlaceholder={value == null || value === ""}
|
isPlaceholder={value == null || value === ""}
|
||||||
placeholderOption={placeholder === false ? null : placeholder}
|
placeholderOption={placeholder === false ? null : placeholder}
|
||||||
isOptionSelected={option => compare(option, value)}
|
isOptionSelected={option => compareOptionAndValue(option, value)}
|
||||||
onSelectOption={selectOption}
|
onSelectOption={selectOption}
|
||||||
{loading}
|
{loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
export let useAnchorWidth = false
|
export let useAnchorWidth = false
|
||||||
export let dismissible = true
|
export let dismissible = true
|
||||||
export let offset = 5
|
export let offset = 5
|
||||||
export let offsetBelow
|
|
||||||
export let customHeight
|
export let customHeight
|
||||||
export let animate = true
|
export let animate = true
|
||||||
export let customZindex
|
export let customZindex
|
||||||
|
@ -89,7 +88,6 @@
|
||||||
maxWidth,
|
maxWidth,
|
||||||
useAnchorWidth,
|
useAnchorWidth,
|
||||||
offset,
|
offset,
|
||||||
offsetBelow,
|
|
||||||
customUpdate: handlePostionUpdate,
|
customUpdate: handlePostionUpdate,
|
||||||
}}
|
}}
|
||||||
use:clickOutside={{
|
use:clickOutside={{
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { findComponent, findComponentPath } from "./componentUtils"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { createHistoryStore } from "builderStore/store/history"
|
import { createHistoryStore } from "builderStore/store/history"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { getHoverStore } from "./store/hover"
|
||||||
|
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
|
@ -16,6 +17,7 @@ export const themeStore = getThemeStore()
|
||||||
export const temporalStore = getTemporalStore()
|
export const temporalStore = getTemporalStore()
|
||||||
export const userStore = getUserStore()
|
export const userStore = getUserStore()
|
||||||
export const deploymentStore = getDeploymentStore()
|
export const deploymentStore = getDeploymentStore()
|
||||||
|
export const hoverStore = getHoverStore()
|
||||||
|
|
||||||
// Setup history for screens
|
// Setup history for screens
|
||||||
export const screenHistoryStore = createHistoryStore({
|
export const screenHistoryStore = createHistoryStore({
|
||||||
|
|
|
@ -92,9 +92,6 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
// Onboarding
|
// Onboarding
|
||||||
onboarding: false,
|
onboarding: false,
|
||||||
tourNodes: null,
|
tourNodes: null,
|
||||||
|
|
||||||
// UI state
|
|
||||||
hoveredComponentId: null,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendStore = () => {
|
export const getFrontendStore = () => {
|
||||||
|
@ -1415,18 +1412,6 @@ export const getFrontendStore = () => {
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
hover: (componentId, notifyClient = true) => {
|
|
||||||
if (componentId === get(store).hoveredComponentId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
store.update(state => {
|
|
||||||
state.hoveredComponentId = componentId
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
if (notifyClient) {
|
|
||||||
store.actions.preview.sendEvent("hover-component", componentId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
links: {
|
links: {
|
||||||
save: async (url, title) => {
|
save: async (url, title) => {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { get, writable } from "svelte/store"
|
||||||
|
import { store as builder } from "builderStore"
|
||||||
|
|
||||||
|
export const getHoverStore = () => {
|
||||||
|
const initialValue = {
|
||||||
|
componentId: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = writable(initialValue)
|
||||||
|
|
||||||
|
const update = (componentId, notifyClient = true) => {
|
||||||
|
if (componentId === get(store).componentId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.update(state => {
|
||||||
|
state.componentId = componentId
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
if (notifyClient) {
|
||||||
|
builder.actions.preview.sendEvent("hover-component", componentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
actions: { update },
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,8 +88,12 @@
|
||||||
hasValidated = false
|
hasValidated = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$: valid =
|
$: valid =
|
||||||
getErrorCount(errors) === 0 && allRequiredAttributesSet(relationshipType)
|
getErrorCount(errors) === 0 &&
|
||||||
|
allRequiredAttributesSet(relationshipType) &&
|
||||||
|
fromId &&
|
||||||
|
toId
|
||||||
$: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY
|
$: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
$: isManyToOne =
|
$: isManyToOne =
|
||||||
relationshipType === RelationshipType.MANY_TO_ONE ||
|
relationshipType === RelationshipType.MANY_TO_ONE ||
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { getEventContextBindings } from "builderStore/dataBinding"
|
import { getEventContextBindings } from "builderStore/dataBinding"
|
||||||
|
import { cloneDeep, isEqual } from "lodash/fp"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let componentBindings
|
export let componentBindings
|
||||||
|
@ -17,8 +18,13 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let focusItem
|
let focusItem
|
||||||
|
let cachedValue
|
||||||
|
|
||||||
$: buttonList = sanitizeValue(value) || []
|
$: if (!isEqual(value, cachedValue)) {
|
||||||
|
cachedValue = cloneDeep(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
$: buttonList = sanitizeValue(cachedValue) || []
|
||||||
$: buttonCount = buttonList.length
|
$: buttonCount = buttonList.length
|
||||||
$: eventContextBindings = getEventContextBindings({
|
$: eventContextBindings = getEventContextBindings({
|
||||||
componentInstance,
|
componentInstance,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { store, selectedScreen, currentAsset } from "builderStore"
|
import { store, selectedScreen, currentAsset, hoverStore } from "builderStore"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import {
|
import {
|
||||||
ProgressCircle,
|
ProgressCircle,
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
} else if (type === "select-component" && data.id) {
|
} else if (type === "select-component" && data.id) {
|
||||||
$store.selectedComponentId = data.id
|
$store.selectedComponentId = data.id
|
||||||
} else if (type === "hover-component") {
|
} else if (type === "hover-component") {
|
||||||
store.actions.components.hover(data.id, false)
|
hoverStore.actions.update(data.id, false)
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
await store.actions.components.updateSetting(data.prop, data.value)
|
await store.actions.components.updateSetting(data.prop, data.value)
|
||||||
} else if (type === "update-styles") {
|
} else if (type === "update-styles") {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
selectedComponentPath,
|
selectedComponentPath,
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
selectedScreen,
|
selectedScreen,
|
||||||
|
hoverStore,
|
||||||
} from "builderStore"
|
} from "builderStore"
|
||||||
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
@ -90,7 +91,7 @@
|
||||||
return findComponentPath($selectedComponent, component._id)?.length > 0
|
return findComponentPath($selectedComponent, component._id)?.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const hover = store.actions.components.hover
|
const hover = hoverStore.actions.update
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -111,7 +112,7 @@
|
||||||
on:dragover={dragover(component, index)}
|
on:dragover={dragover(component, index)}
|
||||||
on:iconClick={() => toggleNodeOpen(component._id)}
|
on:iconClick={() => toggleNodeOpen(component._id)}
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
hovering={$store.hoveredComponentId === component._id}
|
hovering={$hoverStore.componentId === component._id}
|
||||||
on:mouseenter={() => hover(component._id)}
|
on:mouseenter={() => hover(component._id)}
|
||||||
on:mouseleave={() => hover(null)}
|
on:mouseleave={() => hover(null)}
|
||||||
text={getComponentText(component)}
|
text={getComponentText(component)}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { notifications, Icon, Body } from "@budibase/bbui"
|
import { notifications, Icon, Body } from "@budibase/bbui"
|
||||||
import { isActive, goto } from "@roxi/routify"
|
import { isActive, goto } from "@roxi/routify"
|
||||||
import { store, selectedScreen, userSelectedResourceMap } from "builderStore"
|
import {
|
||||||
|
store,
|
||||||
|
selectedScreen,
|
||||||
|
userSelectedResourceMap,
|
||||||
|
hoverStore,
|
||||||
|
} from "builderStore"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import ComponentTree from "./ComponentTree.svelte"
|
import ComponentTree from "./ComponentTree.svelte"
|
||||||
import { dndStore, DropPosition } from "./dndStore.js"
|
import { dndStore, DropPosition } from "./dndStore.js"
|
||||||
|
@ -36,7 +41,7 @@
|
||||||
scrolling = e.target.scrollTop !== 0
|
scrolling = e.target.scrollTop !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const hover = store.actions.components.hover
|
const hover = hoverStore.actions.update
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="components">
|
<div class="components">
|
||||||
|
@ -60,7 +65,7 @@
|
||||||
icon="WebPage"
|
icon="WebPage"
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
on:click={() => ($store.selectedComponentId = screenComponentId)}
|
on:click={() => ($store.selectedComponentId = screenComponentId)}
|
||||||
hovering={$store.hoveredComponentId === screenComponentId}
|
hovering={$hoverStore.componentId === screenComponentId}
|
||||||
on:mouseenter={() => hover(screenComponentId)}
|
on:mouseenter={() => hover(screenComponentId)}
|
||||||
on:mouseleave={() => hover(null)}
|
on:mouseleave={() => hover(null)}
|
||||||
id="component-screen"
|
id="component-screen"
|
||||||
|
@ -79,7 +84,7 @@
|
||||||
: "VisibilityOff"}
|
: "VisibilityOff"}
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
on:click={() => ($store.selectedComponentId = navComponentId)}
|
on:click={() => ($store.selectedComponentId = navComponentId)}
|
||||||
hovering={$store.hoveredComponentId === navComponentId}
|
hovering={$hoverStore.componentId === navComponentId}
|
||||||
on:mouseenter={() => hover(navComponentId)}
|
on:mouseenter={() => hover(navComponentId)}
|
||||||
on:mouseleave={() => hover(null)}
|
on:mouseleave={() => hover(null)}
|
||||||
id="component-nav"
|
id="component-nav"
|
||||||
|
|
|
@ -3969,6 +3969,12 @@
|
||||||
"key": "allowManualEntry",
|
"key": "allowManualEntry",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Auto confirm",
|
||||||
|
"key": "autoConfirm",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Play sound on scan",
|
"label": "Play sound on scan",
|
||||||
|
|
|
@ -14,11 +14,13 @@
|
||||||
export let value
|
export let value
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let allowManualEntry = false
|
export let allowManualEntry = false
|
||||||
|
export let autoConfirm = false
|
||||||
export let scanButtonText = "Scan code"
|
export let scanButtonText = "Scan code"
|
||||||
export let beepOnScan = false
|
export let beepOnScan = false
|
||||||
export let beepFrequency = 2637
|
export let beepFrequency = 2637
|
||||||
export let customFrequency = 1046
|
export let customFrequency = 1046
|
||||||
export let preferredCamera = "environment"
|
export let preferredCamera = "environment"
|
||||||
|
export let validator
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -41,6 +43,9 @@
|
||||||
beep()
|
beep()
|
||||||
}
|
}
|
||||||
dispatch("change", decodedText)
|
dispatch("change", decodedText)
|
||||||
|
if (autoConfirm && !validator?.(decodedText)) {
|
||||||
|
camModal?.hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +132,11 @@
|
||||||
<div class="scanner-video-wrapper">
|
<div class="scanner-video-wrapper">
|
||||||
{#if value && !manualMode}
|
{#if value && !manualMode}
|
||||||
<div class="scanner-value field-display">
|
<div class="scanner-value field-display">
|
||||||
|
{#if validator?.(value)}
|
||||||
|
<StatusLight negative />
|
||||||
|
{:else}
|
||||||
<StatusLight positive />
|
<StatusLight positive />
|
||||||
|
{/if}
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -183,11 +192,16 @@
|
||||||
</div>
|
</div>
|
||||||
{#if cameraEnabled === true}
|
{#if cameraEnabled === true}
|
||||||
<div class="code-wrap">
|
<div class="code-wrap">
|
||||||
{#if value}
|
{#if value && !validator?.(value)}
|
||||||
<div class="scanner-value">
|
<div class="scanner-value">
|
||||||
<StatusLight positive />
|
<StatusLight positive />
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
|
{:else if value && validator?.(value)}
|
||||||
|
<div class="scanner-value">
|
||||||
|
<StatusLight negative />
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="scanner-value">
|
<div class="scanner-value">
|
||||||
<StatusLight neutral />
|
<StatusLight neutral />
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let defaultValue = ""
|
export let defaultValue = ""
|
||||||
export let onChange
|
export let onChange
|
||||||
export let allowManualEntry
|
export let allowManualEntry
|
||||||
|
export let autoConfirm
|
||||||
export let scanButtonText
|
export let scanButtonText
|
||||||
export let beepOnScan
|
export let beepOnScan
|
||||||
export let beepFrequency
|
export let beepFrequency
|
||||||
|
@ -49,11 +50,13 @@
|
||||||
on:change={handleUpdate}
|
on:change={handleUpdate}
|
||||||
disabled={fieldState.disabled || fieldState.readonly}
|
disabled={fieldState.disabled || fieldState.readonly}
|
||||||
{allowManualEntry}
|
{allowManualEntry}
|
||||||
|
{autoConfirm}
|
||||||
scanButtonText={scanText}
|
scanButtonText={scanText}
|
||||||
{beepOnScan}
|
{beepOnScan}
|
||||||
{beepFrequency}
|
{beepFrequency}
|
||||||
{customFrequency}
|
{customFrequency}
|
||||||
{preferredCamera}
|
{preferredCamera}
|
||||||
|
validator={fieldState.validator}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -114,7 +114,7 @@
|
||||||
const forceFetchRows = async () => {
|
const forceFetchRows = async () => {
|
||||||
// if the filter has changed, then we need to reset the options, clear the selection, and re-fetch
|
// if the filter has changed, then we need to reset the options, clear the selection, and re-fetch
|
||||||
optionsObj = {}
|
optionsObj = {}
|
||||||
fieldApi.setValue([])
|
fieldApi?.setValue([])
|
||||||
selectedValue = []
|
selectedValue = []
|
||||||
debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
|
debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,6 @@
|
||||||
bind:searchTerm
|
bind:searchTerm
|
||||||
loading={$fetch.loading}
|
loading={$fetch.loading}
|
||||||
bind:open
|
bind:open
|
||||||
customPopoverMaxHeight={400}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit ce7722ed4474718596b465dcfd49bef36cab2e42
|
Subproject commit eb9565f568cfef14b336b14eee753119acfdd43b
|
|
@ -60,6 +60,7 @@ const environment = {
|
||||||
PLUGINS_DIR: process.env.PLUGINS_DIR || "/plugins",
|
PLUGINS_DIR: process.env.PLUGINS_DIR || "/plugins",
|
||||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
||||||
MAX_IMPORT_SIZE_MB: process.env.MAX_IMPORT_SIZE_MB,
|
MAX_IMPORT_SIZE_MB: process.env.MAX_IMPORT_SIZE_MB,
|
||||||
|
SESSION_EXPIRY_SECONDS: process.env.SESSION_EXPIRY_SECONDS,
|
||||||
// flags
|
// flags
|
||||||
ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS,
|
ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS,
|
||||||
DISABLE_THREADING: process.env.DISABLE_THREADING,
|
DISABLE_THREADING: process.env.DISABLE_THREADING,
|
||||||
|
|
|
@ -34,7 +34,7 @@ const checkAuthorized = async (
|
||||||
const isCreatorApi = permType === PermissionType.CREATOR
|
const isCreatorApi = permType === PermissionType.CREATOR
|
||||||
const isBuilderApi = permType === PermissionType.BUILDER
|
const isBuilderApi = permType === PermissionType.BUILDER
|
||||||
const isGlobalBuilder = users.isGlobalBuilder(ctx.user)
|
const isGlobalBuilder = users.isGlobalBuilder(ctx.user)
|
||||||
const isCreator = users.isCreator(ctx.user)
|
const isCreator = await users.isCreator(ctx.user)
|
||||||
const isBuilder = appId
|
const isBuilder = appId
|
||||||
? users.isBuilder(ctx.user, appId)
|
? users.isBuilder(ctx.user, appId)
|
||||||
: users.hasBuilderPermissions(ctx.user)
|
: users.hasBuilderPermissions(ctx.user)
|
||||||
|
|
|
@ -84,7 +84,7 @@ describe("syncGlobalUsers", () => {
|
||||||
await syncGlobalUsers()
|
await syncGlobalUsers()
|
||||||
|
|
||||||
const metadata = await rawUserMetadata()
|
const metadata = await rawUserMetadata()
|
||||||
expect(metadata).toHaveLength(3)
|
expect(metadata).toHaveLength(2)
|
||||||
expect(metadata).toContainEqual(
|
expect(metadata).toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
_id: db.generateUserMetadataID(user1._id!),
|
_id: db.generateUserMetadataID(user1._id!),
|
||||||
|
@ -121,7 +121,7 @@ describe("syncGlobalUsers", () => {
|
||||||
await syncGlobalUsers()
|
await syncGlobalUsers()
|
||||||
|
|
||||||
const metadata = await rawUserMetadata()
|
const metadata = await rawUserMetadata()
|
||||||
expect(metadata).toHaveLength(0)
|
expect(metadata).toHaveLength(1) //ADMIN user created in test bootstrap still in the application
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function hasAppCreatorPermissions(user?: User | ContextUser): boolean {
|
||||||
return _.flow(
|
return _.flow(
|
||||||
_.get("roles"),
|
_.get("roles"),
|
||||||
_.values,
|
_.values,
|
||||||
_.find(x => x === "CREATOR"),
|
_.find(x => ["CREATOR", "ADMIN"].includes(x)),
|
||||||
x => !!x
|
x => !!x
|
||||||
)(user)
|
)(user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,9 @@ export async function getSelf(ctx: any) {
|
||||||
id: userId,
|
id: userId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust creators quotas (prevents wrong creators count if user has changed the plan)
|
||||||
|
await groups.adjustGroupCreatorsQuotas()
|
||||||
|
|
||||||
// get the main body of the user
|
// get the main body of the user
|
||||||
const user = await userSdk.db.getUser(userId)
|
const user = await userSdk.db.getUser(userId)
|
||||||
ctx.body = await groups.enrichUserRolesFromGroups(user)
|
ctx.body = await groups.enrichUserRolesFromGroups(user)
|
||||||
|
|
|
@ -55,6 +55,7 @@ const environment = {
|
||||||
CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600,
|
CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600,
|
||||||
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
||||||
ENCRYPTED_TEST_PUBLIC_API_KEY: process.env.ENCRYPTED_TEST_PUBLIC_API_KEY,
|
ENCRYPTED_TEST_PUBLIC_API_KEY: process.env.ENCRYPTED_TEST_PUBLIC_API_KEY,
|
||||||
|
SESSION_EXPIRY_SECONDS: process.env.SESSION_EXPIRY_SECONDS,
|
||||||
/**
|
/**
|
||||||
* Mock the email service in use - links to ethereal hosted emails are logged instead.
|
* Mock the email service in use - links to ethereal hosted emails are logged instead.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue