diff --git a/lerna.json b/lerna.json
index b648e6ca22..f34ab2a420 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "1.1.24",
+ "version": "1.1.29-alpha.2",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json
index d8885ce717..17436357e0 100644
--- a/packages/backend-core/package.json
+++ b/packages/backend-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
- "version": "1.1.24",
+ "version": "1.1.29-alpha.2",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@@ -20,7 +20,7 @@
"test:watch": "jest --watchAll"
},
"dependencies": {
- "@budibase/types": "^1.1.24",
+ "@budibase/types": "^1.1.29-alpha.2",
"@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0",
"bcrypt": "5.0.1",
diff --git a/packages/backend-core/src/auth.js b/packages/backend-core/src/auth.js
index b60144a0de..9ae29a3cbd 100644
--- a/packages/backend-core/src/auth.js
+++ b/packages/backend-core/src/auth.js
@@ -18,6 +18,8 @@ const {
ssoCallbackUrl,
csrf,
internalApi,
+ adminOnly,
+ joiValidator,
} = require("./middleware")
const { invalidateUser } = require("./cache/user")
@@ -173,4 +175,6 @@ module.exports = {
refreshOAuthToken,
updateUserOAuth,
ssoCallbackUrl,
+ adminOnly,
+ joiValidator,
}
diff --git a/packages/backend-core/src/db/constants.ts b/packages/backend-core/src/db/constants.ts
index 716762dd45..9c6be25424 100644
--- a/packages/backend-core/src/db/constants.ts
+++ b/packages/backend-core/src/db/constants.ts
@@ -11,6 +11,7 @@ export enum AutomationViewModes {
}
export enum ViewNames {
+ USER_BY_APP = "by_app",
USER_BY_EMAIL = "by_email2",
BY_API_KEY = "by_api_key",
USER_BY_BUILDERS = "by_builders",
@@ -28,6 +29,7 @@ export const DeprecatedViews = {
export enum DocumentTypes {
USER = "us",
+ GROUP = "gr",
WORKSPACE = "workspace",
CONFIG = "config",
TEMPLATE = "template",
diff --git a/packages/backend-core/src/db/conversions.js b/packages/backend-core/src/db/conversions.js
index 455cc712d8..90c04e9251 100644
--- a/packages/backend-core/src/db/conversions.js
+++ b/packages/backend-core/src/db/conversions.js
@@ -50,3 +50,8 @@ exports.getProdAppID = appId => {
const rest = split.join(APP_DEV_PREFIX)
return `${APP_PREFIX}${rest}`
}
+
+exports.extractAppUUID = id => {
+ const split = id?.split("_") || []
+ return split.length ? split[split.length - 1] : null
+}
diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts
index ba3f1dd3e9..8ab6fa6e98 100644
--- a/packages/backend-core/src/db/utils.ts
+++ b/packages/backend-core/src/db/utils.ts
@@ -8,7 +8,7 @@ import { doWithDB, allDbs } from "./index"
import { getCouchInfo } from "./pouch"
import { getAppMetadata } from "../cache/appMetadata"
import { checkSlashesInUrl } from "../helpers"
-import { isDevApp, isDevAppID } from "./conversions"
+import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
import { APP_PREFIX } from "./constants"
import * as events from "../events"
@@ -107,6 +107,15 @@ export function getGlobalUserParams(globalId: any, otherProps: any = {}) {
}
}
+export function getUsersByAppParams(appId: any, otherProps: any = {}) {
+ const prodAppId = getProdAppID(appId)
+ return {
+ ...otherProps,
+ startkey: prodAppId,
+ endkey: `${prodAppId}${UNICODE_MAX}`,
+ }
+}
+
/**
* Generates a template ID.
* @param ownerId The owner/user of the template, this could be global or a workspace level.
@@ -115,6 +124,10 @@ export function generateTemplateID(ownerId: any) {
return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}`
}
+export function generateAppUserID(prodAppId: string, userId: string) {
+ return `${prodAppId}${SEPARATOR}${userId}`
+}
+
/**
* Gets parameters for retrieving templates. Owner ID must be specified, either global or a workspace level.
*/
@@ -442,15 +455,29 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => {
export function pagination(
data: any[],
pageSize: number,
- { paginate, property } = { paginate: true, property: "_id" }
+ {
+ paginate,
+ property,
+ getKey,
+ }: {
+ paginate: boolean
+ property: string
+ getKey?: (doc: any) => string | undefined
+ } = {
+ paginate: true,
+ property: "_id",
+ }
) {
if (!paginate) {
return { data, hasNextPage: false }
}
const hasNextPage = data.length > pageSize
let nextPage = undefined
+ if (!getKey) {
+ getKey = (doc: any) => (property ? doc?.[property] : doc?._id)
+ }
if (hasNextPage) {
- nextPage = property ? data[pageSize]?.[property] : data[pageSize]?._id
+ nextPage = getKey(data[pageSize])
}
return {
data: data.slice(0, pageSize),
diff --git a/packages/backend-core/src/db/views.js b/packages/backend-core/src/db/views.js
index 1e8dd7ee77..baf1807ca5 100644
--- a/packages/backend-core/src/db/views.js
+++ b/packages/backend-core/src/db/views.js
@@ -56,6 +56,33 @@ exports.createNewUserEmailView = async () => {
await db.put(designDoc)
}
+exports.createUserAppView = async () => {
+ const db = getGlobalDB()
+ let designDoc
+ try {
+ designDoc = await db.get("_design/database")
+ } catch (err) {
+ // no design doc, make one
+ designDoc = DesignDoc()
+ }
+ const view = {
+ // if using variables in a map function need to inject them before use
+ map: `function(doc) {
+ if (doc._id.startsWith("${DocumentTypes.USER}${SEPARATOR}") && doc.roles) {
+ for (let prodAppId of Object.keys(doc.roles)) {
+ let emitted = prodAppId + "${SEPARATOR}" + doc._id
+ emit(emitted, null)
+ }
+ }
+ }`,
+ }
+ designDoc.views = {
+ ...designDoc.views,
+ [ViewNames.USER_BY_APP]: view,
+ }
+ await db.put(designDoc)
+}
+
exports.createApiKeyView = async () => {
const db = getGlobalDB()
let designDoc
@@ -106,6 +133,7 @@ exports.queryGlobalView = async (viewName, params, db = null) => {
[ViewNames.USER_BY_EMAIL]: exports.createNewUserEmailView,
[ViewNames.BY_API_KEY]: exports.createApiKeyView,
[ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView,
+ [ViewNames.USER_BY_APP]: exports.createUserAppView,
}
// can pass DB in if working with something specific
if (!db) {
diff --git a/packages/backend-core/src/errors/index.js b/packages/backend-core/src/errors/index.js
index 58b4eea8c5..31ffd739a0 100644
--- a/packages/backend-core/src/errors/index.js
+++ b/packages/backend-core/src/errors/index.js
@@ -37,6 +37,7 @@ module.exports = {
types,
errors: {
UsageLimitError: licensing.UsageLimitError,
+ FeatureDisabledError: licensing.FeatureDisabledError,
HTTPError: http.HTTPError,
},
getPublicError,
diff --git a/packages/backend-core/src/errors/licensing.js b/packages/backend-core/src/errors/licensing.js
index 0d8ce08146..85d207ac35 100644
--- a/packages/backend-core/src/errors/licensing.js
+++ b/packages/backend-core/src/errors/licensing.js
@@ -4,6 +4,7 @@ const type = "license_error"
const codes = {
USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded",
+ FEATURE_DISABLED: "feature_disabled",
}
const context = {
@@ -12,6 +13,11 @@ const context = {
limitName: err.limitName,
}
},
+ [codes.FEATURE_DISABLED]: err => {
+ return {
+ featureName: err.featureName,
+ }
+ },
}
class UsageLimitError extends HTTPError {
@@ -21,9 +27,17 @@ class UsageLimitError extends HTTPError {
}
}
+class FeatureDisabledError extends HTTPError {
+ constructor(message, featureName) {
+ super(message, 400, codes.FEATURE_DISABLED, type)
+ this.featureName = featureName
+ }
+}
+
module.exports = {
type,
codes,
context,
UsageLimitError,
+ FeatureDisabledError,
}
diff --git a/packages/backend-core/src/events/publishers/group.ts b/packages/backend-core/src/events/publishers/group.ts
new file mode 100644
index 0000000000..d300873725
--- /dev/null
+++ b/packages/backend-core/src/events/publishers/group.ts
@@ -0,0 +1,64 @@
+import { publishEvent } from "../events"
+import {
+ Event,
+ UserGroup,
+ GroupCreatedEvent,
+ GroupDeletedEvent,
+ GroupUpdatedEvent,
+ GroupUsersAddedEvent,
+ GroupUsersDeletedEvent,
+ GroupAddedOnboardingEvent,
+ UserGroupRoles,
+} from "@budibase/types"
+
+export async function created(group: UserGroup, timestamp?: number) {
+ const properties: GroupCreatedEvent = {
+ groupId: group._id as string,
+ }
+ await publishEvent(Event.USER_GROUP_CREATED, properties, timestamp)
+}
+
+export async function updated(group: UserGroup) {
+ const properties: GroupUpdatedEvent = {
+ groupId: group._id as string,
+ }
+ await publishEvent(Event.USER_GROUP_UPDATED, properties)
+}
+
+export async function deleted(group: UserGroup) {
+ const properties: GroupDeletedEvent = {
+ groupId: group._id as string,
+ }
+ await publishEvent(Event.USER_GROUP_DELETED, properties)
+}
+
+export async function usersAdded(count: number, group: UserGroup) {
+ const properties: GroupUsersAddedEvent = {
+ count,
+ groupId: group._id as string,
+ }
+ await publishEvent(Event.USER_GROUP_USERS_ADDED, properties)
+}
+
+export async function usersDeleted(emails: string[], group: UserGroup) {
+ const properties: GroupUsersDeletedEvent = {
+ count: emails.length,
+ groupId: group._id as string,
+ }
+ await publishEvent(Event.USER_GROUP_USERS_REMOVED, properties)
+}
+
+export async function createdOnboarding(groupId: string) {
+ const properties: GroupAddedOnboardingEvent = {
+ groupId: groupId,
+ onboarding: true,
+ }
+ await publishEvent(Event.USER_GROUP_ONBOARDING, properties)
+}
+
+export async function permissionsEdited(roles: UserGroupRoles) {
+ const properties: UserGroupRoles = {
+ ...roles,
+ }
+ await publishEvent(Event.USER_GROUP_PERMISSIONS_EDITED, properties)
+}
diff --git a/packages/backend-core/src/events/publishers/index.ts b/packages/backend-core/src/events/publishers/index.ts
index 65785d4d8b..57fd0bf8e2 100644
--- a/packages/backend-core/src/events/publishers/index.ts
+++ b/packages/backend-core/src/events/publishers/index.ts
@@ -17,3 +17,4 @@ export * as user from "./user"
export * as view from "./view"
export * as installation from "./installation"
export * as backfill from "./backfill"
+export * as group from "./group"
diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts
index ab89eed3b2..35777ae817 100644
--- a/packages/backend-core/src/index.ts
+++ b/packages/backend-core/src/index.ts
@@ -3,6 +3,7 @@ const errorClasses = errors.errors
import * as events from "./events"
import * as migrations from "./migrations"
import * as users from "./users"
+import * as roles from "./security/roles"
import * as accounts from "./cloud/accounts"
import * as installation from "./installation"
import env from "./environment"
@@ -51,6 +52,7 @@ const core = {
installation,
errors,
logging,
+ roles,
...errorClasses,
}
diff --git a/packages/backend-core/src/middleware/adminOnly.js b/packages/backend-core/src/middleware/adminOnly.js
new file mode 100644
index 0000000000..4bfdf83848
--- /dev/null
+++ b/packages/backend-core/src/middleware/adminOnly.js
@@ -0,0 +1,9 @@
+module.exports = async (ctx, next) => {
+ if (
+ !ctx.internal &&
+ (!ctx.user || !ctx.user.admin || !ctx.user.admin.global)
+ ) {
+ ctx.throw(403, "Admin user only endpoint.")
+ }
+ return next()
+}
diff --git a/packages/backend-core/src/middleware/authenticated.js b/packages/backend-core/src/middleware/authenticated.js
index 4e6e0b7ba2..d86af773c3 100644
--- a/packages/backend-core/src/middleware/authenticated.js
+++ b/packages/backend-core/src/middleware/authenticated.js
@@ -127,7 +127,7 @@ module.exports = (
}
if (!user && tenantId) {
user = { tenantId }
- } else {
+ } else if (user) {
delete user.password
}
// be explicit
diff --git a/packages/backend-core/src/middleware/index.js b/packages/backend-core/src/middleware/index.js
index 1721d56a3c..9d94bf5763 100644
--- a/packages/backend-core/src/middleware/index.js
+++ b/packages/backend-core/src/middleware/index.js
@@ -9,7 +9,8 @@ const tenancy = require("./tenancy")
const internalApi = require("./internalApi")
const datasourceGoogle = require("./passport/datasource/google")
const csrf = require("./csrf")
-
+const adminOnly = require("./adminOnly")
+const joiValidator = require("./joi-validator")
module.exports = {
google,
oidc,
@@ -25,4 +26,6 @@ module.exports = {
google: datasourceGoogle,
},
csrf,
+ adminOnly,
+ joiValidator,
}
diff --git a/packages/backend-core/src/middleware/joi-validator.js b/packages/backend-core/src/middleware/joi-validator.js
new file mode 100644
index 0000000000..1686b0e727
--- /dev/null
+++ b/packages/backend-core/src/middleware/joi-validator.js
@@ -0,0 +1,28 @@
+function validate(schema, property) {
+ // Return a Koa middleware function
+ return (ctx, next) => {
+ if (!schema) {
+ return next()
+ }
+ let params = null
+ if (ctx[property] != null) {
+ params = ctx[property]
+ } else if (ctx.request[property] != null) {
+ params = ctx.request[property]
+ }
+ const { error } = schema.validate(params)
+ if (error) {
+ ctx.throw(400, `Invalid ${property} - ${error.message}`)
+ return
+ }
+ return next()
+ }
+}
+
+module.exports.body = schema => {
+ return validate(schema, "body")
+}
+
+module.exports.params = schema => {
+ return validate(schema, "params")
+}
diff --git a/packages/backend-core/src/security/roles.js b/packages/backend-core/src/security/roles.js
index 7c57cadcbf..44dc4f2d3e 100644
--- a/packages/backend-core/src/security/roles.js
+++ b/packages/backend-core/src/security/roles.js
@@ -76,7 +76,7 @@ function isBuiltin(role) {
/**
* Works through the inheritance ranks to see how far up the builtin stack this ID is.
*/
-function builtinRoleToNumber(id) {
+exports.builtinRoleToNumber = id => {
const builtins = exports.getBuiltinRoles()
const MAX = Object.values(BUILTIN_IDS).length + 1
if (id === BUILTIN_IDS.ADMIN || id === BUILTIN_IDS.BUILDER) {
@@ -104,7 +104,8 @@ exports.lowerBuiltinRoleID = (roleId1, roleId2) => {
if (!roleId2) {
return roleId1
}
- return builtinRoleToNumber(roleId1) > builtinRoleToNumber(roleId2)
+ return exports.builtinRoleToNumber(roleId1) >
+ exports.builtinRoleToNumber(roleId2)
? roleId2
: roleId1
}
diff --git a/packages/backend-core/src/users.js b/packages/backend-core/src/users.js
index 0c1350a674..34d546a8bb 100644
--- a/packages/backend-core/src/users.js
+++ b/packages/backend-core/src/users.js
@@ -1,4 +1,9 @@
-const { ViewNames } = require("./db/utils")
+const {
+ ViewNames,
+ getUsersByAppParams,
+ getProdAppID,
+ generateAppUserID,
+} = require("./db/utils")
const { queryGlobalView } = require("./db/views")
const { UNICODE_MAX } = require("./db/constants")
@@ -13,12 +18,32 @@ exports.getGlobalUserByEmail = async email => {
throw "Must supply an email address to view"
}
- const response = await queryGlobalView(ViewNames.USER_BY_EMAIL, {
+ return await queryGlobalView(ViewNames.USER_BY_EMAIL, {
key: email.toLowerCase(),
include_docs: true,
})
+}
- return response
+exports.searchGlobalUsersByApp = async (appId, opts) => {
+ if (typeof appId !== "string") {
+ throw new Error("Must provide a string based app ID")
+ }
+ const params = getUsersByAppParams(appId, {
+ include_docs: true,
+ })
+ params.startkey = opts && opts.startkey ? opts.startkey : params.startkey
+ let response = await queryGlobalView(ViewNames.USER_BY_APP, params)
+ if (!response) {
+ response = []
+ }
+ return Array.isArray(response) ? response : [response]
+}
+
+exports.getGlobalUserByAppPage = (appId, user) => {
+ if (!user) {
+ return
+ }
+ return generateAppUserID(getProdAppID(appId), user._id)
}
/**
diff --git a/packages/backend-core/tests/utilities/mocks/events.js b/packages/backend-core/tests/utilities/mocks/events.js
index a4055cc5ea..415d59019d 100644
--- a/packages/backend-core/tests/utilities/mocks/events.js
+++ b/packages/backend-core/tests/utilities/mocks/events.js
@@ -89,6 +89,14 @@ jest.spyOn(events.user, "passwordUpdated")
jest.spyOn(events.user, "passwordResetRequested")
jest.spyOn(events.user, "passwordReset")
+jest.spyOn(events.group, "created")
+jest.spyOn(events.group, "updated")
+jest.spyOn(events.group, "deleted")
+jest.spyOn(events.group, "usersAdded")
+jest.spyOn(events.group, "usersDeleted")
+jest.spyOn(events.group, "createdOnboarding")
+jest.spyOn(events.group, "permissionsEdited")
+
jest.spyOn(events.serve, "servedBuilder")
jest.spyOn(events.serve, "servedApp")
jest.spyOn(events.serve, "servedAppPreview")
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index f2706bb76c..f9d152370b 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
- "version": "1.1.24",
+ "version": "1.1.29-alpha.2",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@@ -38,7 +38,7 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
- "@budibase/string-templates": "^1.1.24",
+ "@budibase/string-templates": "^1.1.29-alpha.2",
"@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2",
diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte
index 53ba6c7e51..cfc810807e 100644
--- a/packages/bbui/src/ActionButton/ActionButton.svelte
+++ b/packages/bbui/src/ActionButton/ActionButton.svelte
@@ -84,6 +84,7 @@
}
:global([dir="ltr"] .spectrum-ActionButton .spectrum-Icon) {
margin-left: 0;
+ transition: color ease-out 130ms;
}
.is-selected:not(.spectrum-ActionButton--emphasized) {
background: var(--spectrum-global-color-gray-300);
@@ -92,4 +93,10 @@
padding: 0;
min-width: 0;
}
+ .spectrum-ActionButton--quiet {
+ padding: 0 8px;
+ }
+ .is-selected:not(.emphasized) .spectrum-Icon {
+ color: var(--spectrum-global-color-gray-900);
+ }
diff --git a/packages/bbui/src/Avatar/Avatar.svelte b/packages/bbui/src/Avatar/Avatar.svelte
index f8acd9024c..136a4fe24b 100644
--- a/packages/bbui/src/Avatar/Avatar.svelte
+++ b/packages/bbui/src/Avatar/Avatar.svelte
@@ -4,7 +4,7 @@
["XXS", "--spectrum-alias-avatar-size-50"],
["XS", "--spectrum-alias-avatar-size-75"],
["S", "--spectrum-alias-avatar-size-200"],
- ["M", "--spectrum-alias-avatar-size-300"],
+ ["M", "--spectrum-alias-avatar-size-400"],
["L", "--spectrum-alias-avatar-size-500"],
["XL", "--spectrum-alias-avatar-size-600"],
["XXL", "--spectrum-alias-avatar-size-700"],
@@ -13,6 +13,19 @@
export let url = ""
export let disabled = false
export let initials = "JD"
+
+ const DefaultColor = "#3aab87"
+
+ $: color = getColor(initials)
+
+ const getColor = initials => {
+ if (!initials?.length) {
+ return DefaultColor
+ }
+ const code = initials[0].toLowerCase().charCodeAt(0)
+ const hue = ((code % 26) / 26) * 360
+ return `hsl(${hue}, 50%, 50%)`
+ }
{#if url}
@@ -25,10 +38,11 @@
/>
{:else}
{initials || ""}
@@ -40,7 +54,6 @@
display: grid;
place-items: center;
font-weight: 600;
- background: #3aab87;
border-radius: 50%;
overflow: hidden;
user-select: none;
diff --git a/packages/bbui/src/Form/Core/InputDropdown.svelte b/packages/bbui/src/Form/Core/InputDropdown.svelte
new file mode 100644
index 0000000000..723b8ba9b1
--- /dev/null
+++ b/packages/bbui/src/Form/Core/InputDropdown.svelte
@@ -0,0 +1,218 @@
+
+
+
+
+
diff --git a/packages/bbui/src/Form/Core/Multiselect.svelte b/packages/bbui/src/Form/Core/Multiselect.svelte
index 3eb1add267..9dd5a25a4f 100644
--- a/packages/bbui/src/Form/Core/Multiselect.svelte
+++ b/packages/bbui/src/Form/Core/Multiselect.svelte
@@ -13,6 +13,7 @@
export let readonly = false
export let autocomplete = false
export let sort = false
+ export let autoWidth = false
const dispatch = createEventDispatcher()
$: selectedLookupMap = getSelectedLookupMap(value)
@@ -85,4 +86,5 @@
{getOptionValue}
onSelectOption={toggleOption}
{sort}
+ {autoWidth}
/>
diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte
index fc9f801be2..cdaf00aded 100644
--- a/packages/bbui/src/Form/Core/Picker.svelte
+++ b/packages/bbui/src/Form/Core/Picker.svelte
@@ -87,10 +87,15 @@
on:mousedown={onClick}
>
{#if fieldIcon}
-
+
{/if}
+ {#if fieldColour}
+
+ {/if}
{/if}
- {#if fieldColour}
-
-
-
- {/if}
- {#if getOptionColour(option, idx)}
-
-
-
- {/if}
{/each}
{/if}
@@ -209,6 +209,9 @@
width: 100%;
box-shadow: none;
}
+ .spectrum-Picker-label.auto-width {
+ margin-right: var(--spacing-xs);
+ }
.spectrum-Picker-label:not(.auto-width) {
overflow: hidden;
text-overflow: ellipsis;
@@ -221,16 +224,16 @@
.spectrum-Picker-label.auto-width.is-placeholder {
padding-right: 2px;
}
+ .auto-width .spectrum-Menu-item {
+ padding-right: var(--spacing-xl);
+ }
/* Icon and colour alignment */
.spectrum-Menu-checkmark {
align-self: center;
margin-top: 0;
}
- .option-colour {
- padding-left: 8px;
- }
- .option-icon {
+ .option-extra {
padding-right: 8px;
}
diff --git a/packages/bbui/src/Form/Core/PickerDropdown.svelte b/packages/bbui/src/Form/Core/PickerDropdown.svelte
new file mode 100644
index 0000000000..863403ee0c
--- /dev/null
+++ b/packages/bbui/src/Form/Core/PickerDropdown.svelte
@@ -0,0 +1,430 @@
+
+
+
+
+
diff --git a/packages/bbui/src/Form/Core/Select.svelte b/packages/bbui/src/Form/Core/Select.svelte
index 81d7ec8e6c..f549f58d0c 100644
--- a/packages/bbui/src/Form/Core/Select.svelte
+++ b/packages/bbui/src/Form/Core/Select.svelte
@@ -17,7 +17,6 @@
export let autoWidth = false
export let autocomplete = false
export let sort = false
-
const dispatch = createEventDispatcher()
let open = false
$: fieldText = getFieldText(value, options, placeholder)
diff --git a/packages/bbui/src/Form/InputDropdown.svelte b/packages/bbui/src/Form/InputDropdown.svelte
new file mode 100644
index 0000000000..73516ea37c
--- /dev/null
+++ b/packages/bbui/src/Form/InputDropdown.svelte
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/packages/bbui/src/Form/Multiselect.svelte b/packages/bbui/src/Form/Multiselect.svelte
index 957dcccddf..7bcf22aa06 100644
--- a/packages/bbui/src/Form/Multiselect.svelte
+++ b/packages/bbui/src/Form/Multiselect.svelte
@@ -14,7 +14,7 @@
export let getOptionLabel = option => option
export let getOptionValue = option => option
export let sort = false
-
+ export let autoWidth = false
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
@@ -33,6 +33,7 @@
{sort}
{getOptionLabel}
{getOptionValue}
+ {autoWidth}
on:change={onChange}
on:click
/>
diff --git a/packages/bbui/src/Form/PickerDropdown.svelte b/packages/bbui/src/Form/PickerDropdown.svelte
new file mode 100644
index 0000000000..4ffb8248d0
--- /dev/null
+++ b/packages/bbui/src/Form/PickerDropdown.svelte
@@ -0,0 +1,125 @@
+
+
+
+
+
diff --git a/packages/bbui/src/IconPicker/IconPicker.svelte b/packages/bbui/src/IconPicker/IconPicker.svelte
new file mode 100644
index 0000000000..0e71be2c33
--- /dev/null
+++ b/packages/bbui/src/IconPicker/IconPicker.svelte
@@ -0,0 +1,177 @@
+
+
+
+
+ {#if open}
+
(open = false)}
+ transition:fly={{ y: -20, duration: 200 }}
+ class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
+ class:spectrum-Popover--align-right={alignRight}
+ >
+ {#each iconList as icon}
+
+
{icon.label}
+
+ {#each icon.icons as icon}
+
{
+ onChange(icon)
+ }}
+ >
+
+
+ {/each}
+
+
+ {/each}
+
+ {/if}
+
+
+
diff --git a/packages/bbui/src/List/Items/DetailSummary.svench b/packages/bbui/src/List/Items/DetailSummary.svench
deleted file mode 100644
index 48fb8f7df8..0000000000
--- a/packages/bbui/src/List/Items/DetailSummary.svench
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- 1
- 2
- 3
- 4
-
-
- 1
- 2
- 3
- 4
-
-
-
-
-
-
-
- 1
- 2
- 3
- 4
-
-
- 1
- 2
- 3
- 4
-
-
-
diff --git a/packages/bbui/src/List/List.svelte b/packages/bbui/src/List/List.svelte
new file mode 100644
index 0000000000..243b04da50
--- /dev/null
+++ b/packages/bbui/src/List/List.svelte
@@ -0,0 +1,28 @@
+
+
+
+ {#if title}
+
+ {title}
+
+ {/if}
+
+
+
+
+
+
diff --git a/packages/bbui/src/List/ListItem.svelte b/packages/bbui/src/List/ListItem.svelte
new file mode 100644
index 0000000000..76a83e7b08
--- /dev/null
+++ b/packages/bbui/src/List/ListItem.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+ {#if icon}
+
+
+
+ {/if}
+ {#if avatar}
+
+ {/if}
+ {#if title}
+ {title}
+ {/if}
+ {#if subtitle}
+
+ {/if}
+
+
+
+
+
+
+
diff --git a/packages/bbui/src/StatusLight/StatusLight.svelte b/packages/bbui/src/StatusLight/StatusLight.svelte
index a0c72443a6..5b7257891f 100644
--- a/packages/bbui/src/StatusLight/StatusLight.svelte
+++ b/packages/bbui/src/StatusLight/StatusLight.svelte
@@ -18,11 +18,16 @@
export let disabled = false
export let active = false
export let color = null
+ export let square = false
+ export let hoverable = false
diff --git a/packages/bbui/src/Table/AttachmentRenderer.svelte b/packages/bbui/src/Table/AttachmentRenderer.svelte
index 97ce1394cc..4dff22aef8 100644
--- a/packages/bbui/src/Table/AttachmentRenderer.svelte
+++ b/packages/bbui/src/Table/AttachmentRenderer.svelte
@@ -1,5 +1,4 @@
diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
index f77374985d..90e7ab661c 100644
--- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
@@ -1,6 +1,7 @@
+
+{#if schemaFields.length && isTestModal}
+
+ {#each schemaFields as [field, schema]}
+
+ {/each}
+
+{/if}
+
+
diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte
index b754f878ce..f19f2279d9 100644
--- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte
+++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte
@@ -211,7 +211,6 @@
bindings={getAuthBindings()}
on:change={e => {
form.bearer.token = e.detail
- console.log(e.detail)
onFieldChange()
}}
on:blur={() => {
diff --git a/packages/builder/src/components/common/NavItem.svelte b/packages/builder/src/components/common/NavItem.svelte
index 4cb67dc9c4..b319560ddd 100644
--- a/packages/builder/src/components/common/NavItem.svelte
+++ b/packages/builder/src/components/common/NavItem.svelte
@@ -1,5 +1,5 @@
+
+
- {#if showExpandIcon}
+ {#if expandable}
{/if}
+ {#if showCloseButton}
+
+ {/if}
diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ValidateForm.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ValidateForm.svelte
index e572dc6c1c..e7f3d91ec8 100644
--- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ValidateForm.svelte
+++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ValidateForm.svelte
@@ -1,5 +1,5 @@
+
+
+
+
+
+
+ {#each filtered as item}
+
{
+ select(item._id)
+ }}
+ style="padding-bottom: var(--spacing-m)"
+ class="selection"
+ >
+
+ {item[key]}
+
+
+ {#if selected.includes(item._id)}
+
+
+
+ {/if}
+
+ {/each}
+
+
+
+
diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte
index 2cf1ce7f6c..23f9f3f80c 100644
--- a/packages/builder/src/components/start/CreateAppModal.svelte
+++ b/packages/builder/src/components/start/CreateAppModal.svelte
@@ -111,7 +111,6 @@
await admin.init()
// Create user
- await API.updateOwnMetadata({ roleId: $values.roleId })
await auth.setInitInfo({})
// Create a default home screen if no template was selected
diff --git a/packages/builder/src/helpers/data/utils.js b/packages/builder/src/helpers/data/utils.js
index 23aeb314a0..647c2be33e 100644
--- a/packages/builder/src/helpers/data/utils.js
+++ b/packages/builder/src/helpers/data/utils.js
@@ -150,12 +150,31 @@ export function flipHeaderState(headersActivity) {
return enabled
}
+export const parseToCsv = (headers, rows) => {
+ let csv = headers?.map(key => `"${key}"`)?.join(",") || ""
+
+ for (let row of rows) {
+ csv = `${csv}\n${headers
+ .map(header => {
+ let val = row[header]
+ val =
+ typeof val === "object" && !(val instanceof Date)
+ ? `"${JSON.stringify(val).replace(/"/g, "'")}"`
+ : `"${val}"`
+ return val.trim()
+ })
+ .join(",")}`
+ }
+ return csv
+}
+
export default {
breakQueryString,
buildQueryString,
fieldsToSchema,
flipHeaderState,
keyValueToQueryParameters,
+ parseToCsv,
queryParametersToKeyValue,
schemaToFields,
}
diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte
index df84277142..28c5fe18c6 100644
--- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte
@@ -23,10 +23,6 @@
$layout.children.find(layout => $isActive(layout.path))?.title ?? "data"
)
- const previewApp = () => {
- window.open(`/${application}`)
- }
-
async function getPackage() {
try {
store.actions.reset()
@@ -108,14 +104,10 @@
@@ -183,4 +175,8 @@
align-items: center;
gap: var(--spacing-xl);
}
+
+ .version {
+ margin-right: var(--spacing-s);
+ }
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 76118cc9c8..c4b80dcc3a 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
@@ -1,10 +1,9 @@
@@ -15,24 +14,17 @@
options={$sortedScreens}
getOptionLabel={x => x.routing.route}
getOptionValue={x => x._id}
- getOptionIcon={x => (x.routing.homeScreen ? "Home" : "WebPage")}
getOptionColour={x => RoleUtils.getRoleColour(x.routing.roleId)}
value={$store.selectedScreenId}
on:change={e => store.actions.screens.select(e.detail)}
+ quiet
+ autoWidth
/>
@@ -59,6 +51,7 @@
justify-content: space-between;
align-items: flex-start;
gap: var(--spacing-l);
+ margin: 0 2px;
}
.header-left,
.header-right {
@@ -69,7 +62,8 @@
gap: var(--spacing-l);
}
.header-left :global(.spectrum-Picker) {
- width: 250px;
+ font-weight: 600;
+ color: var(--spectrum-global-color-gray-900);
}
.content {
flex: 1 1 auto;
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte
index abb956c9d3..304d41ad19 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte
@@ -3,6 +3,7 @@
import { onMount, onDestroy } from "svelte"
import {
store,
+ selectedComponent,
selectedScreen,
selectedLayout,
currentAsset,
@@ -14,6 +15,7 @@
Layout,
Heading,
Body,
+ Icon,
notifications,
} from "@budibase/bbui"
import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw"
@@ -96,6 +98,11 @@
$: json = JSON.stringify(previewData)
$: refreshContent(json)
+ // Determine if the add component menu is active
+ $: isAddingComponent = $isActive(
+ `./components/${$selectedComponent?._id}/new`
+ )
+
// Update the iframe with the builder info to render the correct preview
const refreshContent = message => {
if (iframe) {
@@ -219,6 +226,16 @@
idToDelete = null
}
+ const toggleAddComponent = () => {
+ if (isAddingComponent) {
+ $goto(`../${$selectedScreen._id}/components/${$selectedComponent?._id}`)
+ } else {
+ $goto(
+ `../${$selectedScreen._id}/components/${$selectedComponent?._id}/new`
+ )
+ }
+ }
+
onMount(() => {
window.addEventListener("message", receiveMessage)
if (!$store.clientFeatures.messagePassing) {
@@ -282,6 +299,13 @@
class:tablet={$store.previewDevice === "tablet"}
class:mobile={$store.previewDevice === "mobile"}
/>
+
+ Component
+
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/DevicePreviewSelect.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/DevicePreviewSelect.svelte
index 9f9447daee..870f801336 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/DevicePreviewSelect.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/DevicePreviewSelect.svelte
@@ -3,18 +3,21 @@
import { store } from "builderStore"
-
+
store.actions.preview.setDevice("desktop")}
/>
store.actions.preview.setDevice("tablet")}
/>
store.actions.preview.setDevice("mobile")}
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte
index 1bb4e3d9cd..ab776dea5d 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte
@@ -9,7 +9,7 @@
import { setContext } from "svelte"
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
import { DropPosition } from "./dndStore"
- import { notifications } from "@budibase/bbui"
+ import { notifications, Button } from "@budibase/bbui"
let scrollRef
@@ -24,7 +24,7 @@
let newOffsets = {}
// Calculate left offset
- const offsetX = bounds.left + bounds.width + scrollLeft - 58
+ const offsetX = bounds.left + bounds.width + scrollLeft - 36
if (offsetX > sidebarWidth) {
newOffsets.left = offsetX - sidebarWidth
} else {
@@ -71,13 +71,10 @@
})
- $goto("../new")}
- showExpandIcon
- borderRight
->
+
+
+
+
-
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json
similarity index 100%
rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/_components/componentStructure.json
rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/index.svelte
new file mode 100644
index 0000000000..965254cf0d
--- /dev/null
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/index.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/_components/NewComponentTargetPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/_components/NewComponentTargetPanel.svelte
deleted file mode 100644
index af44934526..0000000000
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/_components/NewComponentTargetPanel.svelte
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
- Components that you add will be placed {position}
- {title}
-
-
-
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/index.svelte
deleted file mode 100644
index 8f2042671b..0000000000
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/new/index.svelte
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/RoleIndicator.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/RoleIndicator.svelte
new file mode 100644
index 0000000000..eb25d86645
--- /dev/null
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/RoleIndicator.svelte
@@ -0,0 +1,58 @@
+
+
+
(showTooltip = true)}
+ on:mouseleave={() => (showTooltip = false)}
+ style="--color: {color};"
+>
+
+ {#if showTooltip}
+
+
+
+ {/if}
+
+
+
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte
index 8097291952..0c35fa391e 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte
@@ -50,7 +50,6 @@
await store.actions.screens.save(duplicateScreen)
} catch (error) {
notifications.error("Error duplicating screen")
- console.log(error)
}
}
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte
index ecb020b104..a6fd9089b1 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte
@@ -1,11 +1,12 @@
-
+
+
{#each filteredScreens as screen (screen._id)}
store.actions.screens.select(screen._id)}
- color={RoleUtils.getRoleColour(screen.routing.roleId)}
+ rightAlignIcon
>
+
{/each}
{#if !filteredScreens?.length}
diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte
index 03d39ddc45..b358e3bde0 100644
--- a/packages/builder/src/pages/builder/apps/index.svelte
+++ b/packages/builder/src/pages/builder/apps/index.svelte
@@ -13,7 +13,7 @@
notifications,
} from "@budibase/bbui"
import { onMount } from "svelte"
- import { apps, organisation, auth } from "stores/portal"
+ import { apps, organisation, auth, groups } from "stores/portal"
import { goto } from "@roxi/routify"
import { AppStatus } from "constants"
import { gradient } from "actions"
@@ -30,20 +30,41 @@
try {
await organisation.init()
await apps.load()
+ await groups.actions.init()
} catch (error) {
notifications.error("Error loading apps")
}
loaded = true
})
-
const publishedAppsOnly = app => app.status === AppStatus.DEPLOYED
+ $: userGroups = $groups.filter(group =>
+ group.users.find(user => user._id === $auth.user?._id)
+ )
+ let userApps = []
$: publishedApps = $apps.filter(publishedAppsOnly)
- $: userApps = $auth.user?.builder?.global
- ? publishedApps
- : publishedApps.filter(app =>
- Object.keys($auth.user?.roles).includes(app.prodId)
- )
+
+ $: {
+ if (!Object.keys($auth.user?.roles).length && $auth.user?.userGroups) {
+ userApps = $auth.user?.builder?.global
+ ? publishedApps
+ : publishedApps.filter(app => {
+ return userGroups.find(group => {
+ return Object.keys(group.roles)
+ .map(role => apps.extractAppId(role))
+ .includes(app.appId)
+ })
+ })
+ } else {
+ userApps = $auth.user?.builder?.global
+ ? publishedApps
+ : publishedApps.filter(app =>
+ Object.keys($auth.user?.roles)
+ .map(x => apps.extractAppId(x))
+ .includes(app.appId)
+ )
+ }
+ }
function getUrl(app) {
if (app.url) {
diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte
index ae0362af72..5da8b34700 100644
--- a/packages/builder/src/pages/builder/portal/_layout.svelte
+++ b/packages/builder/src/pages/builder/portal/_layout.svelte
@@ -52,6 +52,11 @@
href: "/builder/portal/manage/users",
heading: "Manage",
},
+ {
+ title: "User Groups",
+ href: "/builder/portal/manage/groups",
+ },
+
{ title: "Auth", href: "/builder/portal/manage/auth" },
{ title: "Email", href: "/builder/portal/manage/email" },
{
diff --git a/packages/builder/src/pages/builder/portal/apps/_components/AcessFilter.svelte b/packages/builder/src/pages/builder/portal/apps/_components/AcessFilter.svelte
new file mode 100644
index 0000000000..d0662e7b41
--- /dev/null
+++ b/packages/builder/src/pages/builder/portal/apps/_components/AcessFilter.svelte
@@ -0,0 +1,43 @@
+
+
+
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte
index de5ad178cb..0d05e170e0 100644
--- a/packages/builder/src/pages/builder/portal/apps/index.svelte
+++ b/packages/builder/src/pages/builder/portal/apps/index.svelte
@@ -20,12 +20,14 @@
import { store, automationStore } from "builderStore"
import { API } from "api"
import { onMount } from "svelte"
- import { apps, auth, admin, templates } from "stores/portal"
+ import { apps, auth, admin, templates, groups } from "stores/portal"
import download from "downloadjs"
import { goto } from "@roxi/routify"
import AppRow from "components/start/AppRow.svelte"
import { AppStatus } from "constants"
import Logo from "assets/bb-space-man.svg"
+ import AccessFilter from "./_components/AcessFilter.svelte"
+ import { Constants } from "@budibase/frontend-core"
let sortBy = "name"
let template
@@ -39,6 +41,7 @@
let cloud = $admin.cloud
let creatingFromTemplate = false
let automationErrors
+ let accessFilterList = null
const resolveWelcomeMessage = (auth, apps) => {
const userWelcome = auth?.user?.firstName
@@ -56,14 +59,20 @@
: "Start from scratch"
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
- $: filteredApps = enrichedApps.filter(app =>
- app?.name?.toLowerCase().includes(searchTerm.toLowerCase())
+ $: filteredApps = enrichedApps.filter(
+ app =>
+ app?.name?.toLowerCase().includes(searchTerm.toLowerCase()) &&
+ (accessFilterList !== null ? accessFilterList.includes(app?.appId) : true)
)
$: lockedApps = filteredApps.filter(app => app?.lockedYou || app?.lockedOther)
$: unlocked = lockedApps?.length === 0
$: automationErrors = getAutomationErrors(enrichedApps)
+ $: hasGroupsLicense = $auth.user?.license.features.includes(
+ Constants.Features.USER_GROUPS
+ )
+
const enrichApps = (apps, user, sortBy) => {
const enrichedApps = apps.map(app => ({
...app,
@@ -202,6 +211,10 @@
$goto(`../../app/${app.devId}`)
}
+ const accessFilterAction = accessFilter => {
+ accessFilterList = accessFilter.detail
+ }
+
function createAppFromTemplateUrl(templateKey) {
// validate the template key just to make sure
const templateParts = templateKey.split("/")
@@ -347,6 +360,9 @@
{/if}
+ {#if hasGroupsLicense && $groups.length}
+
+ {/if}