From 6d24a30d91af98cb78611f893bb3442522b8af50 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 5 Jul 2023 18:28:04 +0100 Subject: [PATCH 0001/1404] Basic refactor work, the types required for the new API endpoints. --- packages/types/src/api/web/user.ts | 7 +++++++ packages/types/src/documents/global/user.ts | 2 ++ packages/worker/src/api/controllers/global/users.ts | 8 ++++++++ packages/worker/src/api/routes/global/users.ts | 3 +++ 4 files changed, 20 insertions(+) diff --git a/packages/types/src/api/web/user.ts b/packages/types/src/api/web/user.ts index 619362805a..4a27f781af 100644 --- a/packages/types/src/api/web/user.ts +++ b/packages/types/src/api/web/user.ts @@ -85,3 +85,10 @@ export interface AcceptUserInviteResponse { export interface SyncUserRequest { previousUser?: User } + +export interface AddAppBuilderRequest { + userId: string + appId: string +} + +export interface RemoveAppBuilderRequest {} diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index 9b4aadf404..cae3c0e6e2 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -43,9 +43,11 @@ export interface User extends Document { roles: UserRoles builder?: { global: boolean + apps?: string[] } admin?: { global: boolean + apps?: string[] } password?: string status?: UserStatus diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 320f7be01a..b3720d578f 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -8,6 +8,8 @@ import env from "../../../environment" import { AcceptUserInviteRequest, AcceptUserInviteResponse, + AddAppBuilderRequest, + RemoveAppBuilderRequest, BulkUserRequest, BulkUserResponse, CloudAccount, @@ -431,3 +433,9 @@ export const inviteAccept = async ( ctx.throw(400, "Unable to create new user, invitation invalid.") } } + +export const addAppBuilder = async (ctx: Ctx) => {} + +export const removeAppBuilder = async ( + ctx: Ctx +) => {} diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts index 47e76c17be..557065e9a4 100644 --- a/packages/worker/src/api/routes/global/users.ts +++ b/packages/worker/src/api/routes/global/users.ts @@ -5,6 +5,7 @@ import Joi from "joi" import cloudRestricted from "../../../middleware/cloudRestricted" import { users } from "../validation" import * as selfController from "../../controllers/global/self" +import { addAppBuilder } from "../../controllers/global/users" const router: Router = new Router() @@ -131,5 +132,7 @@ router users.buildUserSaveValidation(), selfController.updateSelf ) + .post("/api/global/users/builder", controller.addAppBuilder) + .delete("/api/global/users/builder", controller.removeAppBuilder) export default router From 1d0ab398cd22de9b5f26531d523078d728e2ea21 Mon Sep 17 00:00:00 2001 From: Gerard Burns Date: Tue, 18 Jul 2023 15:55:06 +0100 Subject: [PATCH 0002/1404] New Design Section Nav Component --- .../builder/src/components/design/Pane.svelte | 21 +++ .../src/components/design/RightPanel.svelte | 112 +++++++++++ .../_components/NavigationInfoPanel.svelte | 33 ---- .../NavigationInfoPanel/CustomizePane.svelte | 176 ++++++++++++++++++ .../NavigationInfoPanel/SettingsPane.svelte | 31 +++ .../NavigationInfoPanel/index.svelte | 10 + .../design/[screenId]/navigation/index.svelte | 2 +- 7 files changed, 351 insertions(+), 34 deletions(-) create mode 100644 packages/builder/src/components/design/Pane.svelte create mode 100644 packages/builder/src/components/design/RightPanel.svelte delete mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte diff --git a/packages/builder/src/components/design/Pane.svelte b/packages/builder/src/components/design/Pane.svelte new file mode 100644 index 0000000000..88b6ea1c05 --- /dev/null +++ b/packages/builder/src/components/design/Pane.svelte @@ -0,0 +1,21 @@ + + +{#if $paneStore} +
+ +
+{/if} + + diff --git a/packages/builder/src/components/design/RightPanel.svelte b/packages/builder/src/components/design/RightPanel.svelte new file mode 100644 index 0000000000..f9075600f9 --- /dev/null +++ b/packages/builder/src/components/design/RightPanel.svelte @@ -0,0 +1,112 @@ + + +
+
+
+ +
+ {title} +
+
+ {#each Object.entries(panes) as [id, pane]} +
+ +
+ {/each} +
+
+ + +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte deleted file mode 100644 index 614e1eed80..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - - - {#if $selectedScreen.layoutId} - - You can't preview your navigation settings using this screen as it uses - a custom layout, which is deprecated - - {/if} - - Your navigation is configured for all the screens within your app. - - - You can hide and show your navigation for each screen in the screen - settings. - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte new file mode 100644 index 0000000000..05108b1736 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte @@ -0,0 +1,176 @@ + + + +
+
+ + CHANGES WILL APPLY TO ALL SCREENS +
+ + Your navigation is configured for all the screens within your app. + +
+ +
+
+ +
+ + update("navigation", "Top")} + /> + update("navigation", "Left")} + /> + + + {#if $store.navigation.navigation === "Top"} +
+ +
+ update("sticky", e.detail)} + /> +
+ +
+ update("logoUrl", e.detail)} + updateOnChange={false} + /> + {/if} +
+ +
+ update("hideTitle", !e.detail)} + /> + {#if !$store.navigation.hideTitle} +
+ +
+ update("title", e.detail)} + updateOnChange={false} + /> + {/if} +
+ +
+ update("navBackground", e.detail)} + /> +
+ +
+ update("navTextColor", e.detail)} + /> +
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte new file mode 100644 index 0000000000..d3ce720994 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte @@ -0,0 +1,31 @@ + + + +
+ + Show nav on this screen +
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte new file mode 100644 index 0000000000..6ce5405f93 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte @@ -0,0 +1,10 @@ + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte index fc2e03d8e8..2331d8b285 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte @@ -1,6 +1,6 @@ From 39746e0bf0db3633723ee2df60551ec3b25469df Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 18 Jul 2023 16:57:48 +0100 Subject: [PATCH 0003/1404] Main body of work to handle the new approach of per app builders support. --- packages/backend-core/src/constants/db.ts | 2 - packages/backend-core/src/db/views.ts | 11 ---- .../backend-core/src/events/identification.ts | 5 +- .../src/middleware/builderOnly.ts | 12 ++-- .../src/middleware/builderOrAdmin.ts | 15 +++-- packages/backend-core/src/users.ts | 62 ++++++++++++++++++- .../server/src/api/controllers/application.ts | 4 +- packages/server/src/middleware/authorized.ts | 11 +++- packages/server/src/middleware/currentapp.ts | 14 ++--- .../functions/backfill/global/users.ts | 10 ++- packages/server/src/utilities/global.ts | 6 +- packages/types/src/documents/global/user.ts | 1 - packages/worker/src/sdk/users/events.ts | 20 +++--- packages/worker/src/sdk/users/users.ts | 4 +- 14 files changed, 115 insertions(+), 62 deletions(-) diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index be49b9f261..34dab6c088 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -14,8 +14,6 @@ export enum ViewName { USER_BY_APP = "by_app", USER_BY_EMAIL = "by_email2", BY_API_KEY = "by_api_key", - /** @deprecated - could be deleted */ - USER_BY_BUILDERS = "by_builders", LINK = "by_link", ROUTING = "screen_routes", AUTOMATION_LOGS = "automation_logs", diff --git a/packages/backend-core/src/db/views.ts b/packages/backend-core/src/db/views.ts index fddb1ab34b..7f5ef29a0a 100644 --- a/packages/backend-core/src/db/views.ts +++ b/packages/backend-core/src/db/views.ts @@ -105,16 +105,6 @@ export const createApiKeyView = async () => { await createView(db, viewJs, ViewName.BY_API_KEY) } -export const createUserBuildersView = async () => { - const db = getGlobalDB() - const viewJs = `function(doc) { - if (doc.builder && doc.builder.global === true) { - emit(doc._id, doc._id) - } - }` - await createView(db, viewJs, ViewName.USER_BY_BUILDERS) -} - export interface QueryViewOptions { arrayResponse?: boolean } @@ -223,7 +213,6 @@ export const queryPlatformView = async ( const CreateFuncByName: any = { [ViewName.USER_BY_EMAIL]: createNewUserEmailView, [ViewName.BY_API_KEY]: createApiKeyView, - [ViewName.USER_BY_BUILDERS]: createUserBuildersView, [ViewName.USER_BY_APP]: createUserAppView, } diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index 5eb11d1354..52fcb2431f 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -21,6 +21,7 @@ import { processors } from "./processors" import { newid } from "../utils" import * as installation from "../installation" import * as configs from "../configs" +import * as users from "../users" import { withCache, TTL, CacheKey } from "../cache/generic" /** @@ -164,8 +165,8 @@ const identifyUser = async ( const id = user._id as string const tenantId = await getEventTenantId(user.tenantId) const type = IdentityType.USER - let builder = user.builder?.global || false - let admin = user.admin?.global || false + let builder = users.hasBuilderPermissions(user) + let admin = users.hasAdminPermissions(user) let providerType if (isSSOUser(user)) { providerType = user.providerType diff --git a/packages/backend-core/src/middleware/builderOnly.ts b/packages/backend-core/src/middleware/builderOnly.ts index a00fd63a22..744321252e 100644 --- a/packages/backend-core/src/middleware/builderOnly.ts +++ b/packages/backend-core/src/middleware/builderOnly.ts @@ -1,10 +1,10 @@ -import { BBContext } from "@budibase/types" +import { UserCtx } from "@budibase/types" +import { isBuilder } from "../users" +import { getAppId } from "../context" -export default async (ctx: BBContext, next: any) => { - if ( - !ctx.internal && - (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) - ) { +export default async (ctx: UserCtx, next: any) => { + const appId = getAppId() + if (!ctx.internal && !isBuilder(ctx.user, appId)) { ctx.throw(403, "Builder user only endpoint.") } return next() diff --git a/packages/backend-core/src/middleware/builderOrAdmin.ts b/packages/backend-core/src/middleware/builderOrAdmin.ts index 26bb3a1bda..2ba5bfe1e2 100644 --- a/packages/backend-core/src/middleware/builderOrAdmin.ts +++ b/packages/backend-core/src/middleware/builderOrAdmin.ts @@ -1,12 +1,11 @@ -import { BBContext } from "@budibase/types" +import { UserCtx } from "@budibase/types" +import { isBuilder, isAdmin } from "../users" +import { getAppId } from "../context" -export default async (ctx: BBContext, next: any) => { - if ( - !ctx.internal && - (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) && - (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) - ) { - ctx.throw(403, "Builder user only endpoint.") +export default async (ctx: UserCtx, next: any) => { + const appId = getAppId() + if (!ctx.internal && !isBuilder(ctx.user, appId) && !isAdmin(ctx.user)) { + ctx.throw(403, "Admin/Builder user only endpoint.") } return next() } diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index b49058f546..2e6ede3cf3 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -12,7 +12,12 @@ import { UNICODE_MAX, ViewName, } from "./db" -import { BulkDocsResponse, SearchUsersRequest, User } from "@budibase/types" +import { + BulkDocsResponse, + SearchUsersRequest, + User, + ContextUser, +} from "@budibase/types" import { getGlobalDB } from "./context" import * as context from "./context" @@ -178,7 +183,7 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => { * Performs a starts with search on the global email view. */ export const searchGlobalUsersByEmail = async ( - email: string, + email: string | unknown, opts: any, getOpts?: GetOpts ) => { @@ -248,3 +253,56 @@ export async function getUserCount() { }) return response.total_rows } + +// checks if a user is specifically a builder, given an app ID +export function isBuilder(user: User | ContextUser, appId?: string) { + if (user.builder?.global) { + return true + } else if (appId && user.builder?.apps?.includes(appId)) { + return true + } + return false +} + +// alias for hasAdminPermission, currently do the same thing +// in future whether someone has admin permissions and whether they are +// an admin for a specific resource could be separated +export function isAdmin(user: User | ContextUser) { + return hasAdminPermissions(user) +} + +// checks if a user is capable of building any app +export function hasBuilderPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.builder?.global || user.builder?.apps?.length !== 0 +} + +// checks if a user is capable of being an admin +export function hasAdminPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.admin?.global +} + +// used to remove the builder/admin permissions, for processing the +// user as an app user (they may have some specific role/group +export function removePortalUserPermissions(user: User | ContextUser) { + delete user.admin + delete user.builder + return user +} + +export function cleanseUserObject(user: User | ContextUser, base?: User) { + delete user.admin + delete user.builder + delete user.roles + if (base) { + user.admin = base.admin + user.builder = base.builder + user.roles = base.roles + } + return user +} diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 418ccf637e..6a0088d4dc 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -30,6 +30,7 @@ import { objectStore, roles, tenancy, + users, } from "@budibase/backend-core" import { USERS_TABLE_SCHEMA } from "../../constants" import { @@ -222,6 +223,7 @@ export async function fetchAppDefinition(ctx: UserCtx) { export async function fetchAppPackage(ctx: UserCtx) { const db = context.getAppDB() + const appId = context.getAppId() let application = await db.get(DocumentType.APP_METADATA) const layouts = await getLayouts() let screens = await getScreens() @@ -233,7 +235,7 @@ export async function fetchAppPackage(ctx: UserCtx) { ) // Only filter screens if the user is not a builder - if (!(ctx.user.builder && ctx.user.builder.global)) { + if (!users.isBuilder(ctx.user, appId)) { const userRoleId = getUserRoleId(ctx) const accessController = new roles.AccessController() screens = await accessController.checkScreensAccess(screens, userRoleId) diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 6268163fb2..81e42604bc 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -1,4 +1,10 @@ -import { roles, permissions, auth, context } from "@budibase/backend-core" +import { + roles, + permissions, + auth, + context, + users, +} from "@budibase/backend-core" import { Role } from "@budibase/types" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" @@ -21,8 +27,9 @@ const checkAuthorized = async ( permType: any, permLevel: any ) => { + const appId = context.getAppId() // check if this is a builder api and the user is not a builder - const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global + const isBuilder = users.isBuilder(ctx.user, appId) const isBuilderApi = permType === permissions.PermissionType.BUILDER if (isBuilderApi && !isBuilder) { return ctx.throw(403, "Not Authorized") diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 6879a103bc..10ee13a67a 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -4,12 +4,14 @@ import { roles, tenancy, context, + users, } from "@budibase/backend-core" import { generateUserMetadataID, isDevAppID } from "../db/utils" import { getCachedSelf } from "../utilities/global" import env from "../environment" import { isWebhookEndpoint } from "./utils" -import { UserCtx } from "@budibase/types" +import { UserCtx, ContextUser } from "@budibase/types" +import { removePortalUserPermissions } from "@budibase/backend-core/src/users" export default async (ctx: UserCtx, next: any) => { // try to get the appID from the request @@ -42,8 +44,7 @@ export default async (ctx: UserCtx, next: any) => { roleId = globalUser.roleId || roleId // Allow builders to specify their role via a header - const isBuilder = - globalUser && globalUser.builder && globalUser.builder.global + const isBuilder = users.isBuilder(globalUser, appId) const isDevApp = appId && isDevAppID(appId) const roleHeader = ctx.request && @@ -56,8 +57,7 @@ export default async (ctx: UserCtx, next: any) => { roleId = roleHeader // Delete admin and builder flags so that the specified role is honoured - delete ctx.user.builder - delete ctx.user.admin + ctx.user = users.removePortalUserPermissions(ctx.user) as ContextUser } } catch (error) { // Swallow error and do nothing @@ -81,9 +81,7 @@ export default async (ctx: UserCtx, next: any) => { !tenancy.isUserInAppTenant(requestAppId, ctx.user) ) { // don't error, simply remove the users rights (they are a public user) - delete ctx.user.builder - delete ctx.user.admin - delete ctx.user.roles + ctx.user = users.cleanseUserObject(ctx.user) as ContextUser ctx.isAuthenticated = false roleId = roles.BUILTIN_ROLE_IDS.PUBLIC skipCookie = true diff --git a/packages/server/src/migrations/functions/backfill/global/users.ts b/packages/server/src/migrations/functions/backfill/global/users.ts index 05c5f8f56e..9f536a97a5 100644 --- a/packages/server/src/migrations/functions/backfill/global/users.ts +++ b/packages/server/src/migrations/functions/backfill/global/users.ts @@ -1,4 +1,8 @@ -import { events, db as dbUtils } from "@budibase/backend-core" +import { + events, + db as dbUtils, + users as usersCore, +} from "@budibase/backend-core" import { User, CloudAccount } from "@budibase/types" import { DEFAULT_TIMESTAMP } from ".." @@ -30,11 +34,11 @@ export const backfill = async ( await events.identification.identifyUser(user, account, timestamp) await events.user.created(user, timestamp) - if (user.admin?.global) { + if (usersCore.hasAdminPermissions(user)) { await events.user.permissionAdminAssigned(user, timestamp) } - if (user.builder?.global) { + if (usersCore.hasBuilderPermissions(user)) { await events.user.permissionBuilderAssigned(user, timestamp) } diff --git a/packages/server/src/utilities/global.ts b/packages/server/src/utilities/global.ts index c9869b4920..63e33dafee 100644 --- a/packages/server/src/utilities/global.ts +++ b/packages/server/src/utilities/global.ts @@ -5,6 +5,7 @@ import { cache, tenancy, context, + users, } from "@budibase/backend-core" import env from "../environment" import { groups } from "@budibase/pro" @@ -22,8 +23,7 @@ export function updateAppRole( } // if in an multi-tenancy environment make sure roles are never updated if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) { - delete user.builder - delete user.admin + user = users.removePortalUserPermissions(user) user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC return user } @@ -32,7 +32,7 @@ export function updateAppRole( user.roleId = user.roles[dbCore.getProdAppID(appId)] } // if a role wasn't found then either set as admin (builder) or public (everyone else) - if (!user.roleId && user.builder && user.builder.global) { + if (!user.roleId && users.isBuilder(user, appId)) { user.roleId = roles.BUILTIN_ROLE_IDS.ADMIN } else if (!user.roleId && !user?.userGroups?.length) { user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index cae3c0e6e2..e9e3ccc662 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -47,7 +47,6 @@ export interface User extends Document { } admin?: { global: boolean - apps?: string[] } password?: string status?: UserStatus diff --git a/packages/worker/src/sdk/users/events.ts b/packages/worker/src/sdk/users/events.ts index 7d86182a3c..d8af13a82f 100644 --- a/packages/worker/src/sdk/users/events.ts +++ b/packages/worker/src/sdk/users/events.ts @@ -1,15 +1,18 @@ import env from "../../environment" -import { events, accounts, tenancy } from "@budibase/backend-core" +import { events, accounts, tenancy, users } from "@budibase/backend-core" import { User, UserRoles, CloudAccount } from "@budibase/types" +const hasBuilderPerm = users.hasBuilderPermissions +const hasAdminPerm = users.hasAdminPermissions + export const handleDeleteEvents = async (user: any) => { await events.user.deleted(user) - if (isBuilder(user)) { + if (hasBuilderPerm(user)) { await events.user.permissionBuilderRemoved(user) } - if (isAdmin(user)) { + if (hasAdminPerm(user)) { await events.user.permissionAdminRemoved(user) } } @@ -103,23 +106,20 @@ export const handleSaveEvents = async ( await handleAppRoleEvents(user, existingUser) } -const isBuilder = (user: any) => user.builder && user.builder.global -const isAdmin = (user: any) => user.admin && user.admin.global - export const isAddingBuilder = (user: any, existingUser: any) => { - return isAddingPermission(user, existingUser, isBuilder) + return isAddingPermission(user, existingUser, hasBuilderPerm) } export const isRemovingBuilder = (user: any, existingUser: any) => { - return isRemovingPermission(user, existingUser, isBuilder) + return isRemovingPermission(user, existingUser, hasBuilderPerm) } const isAddingAdmin = (user: any, existingUser: any) => { - return isAddingPermission(user, existingUser, isAdmin) + return isAddingPermission(user, existingUser, hasAdminPerm) } const isRemovingAdmin = (user: any, existingUser: any) => { - return isRemovingPermission(user, existingUser, isAdmin) + return isRemovingPermission(user, existingUser, hasAdminPerm) } const isOnboardingComplete = (user: any, existingUser: any) => { diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index c3cb8500cb..cfa68932d2 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -252,9 +252,7 @@ export const save = async ( let builtUser = await buildUser(user, opts, tenantId, dbUser) // don't allow a user to update its own roles/perms if (opts.currentUserId && opts.currentUserId === dbUser?._id) { - builtUser.builder = dbUser.builder - builtUser.admin = dbUser.admin - builtUser.roles = dbUser.roles + builtUser = usersCore.cleanseUserObject(builtUser, dbUser) as User } if (!dbUser && roles?.length) { From 91847504c8bd4e79f278f5d5287c5c6f5deda088 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 18 Jul 2023 18:10:15 +0100 Subject: [PATCH 0004/1404] Adding test cases for admin/builder checking middlewares. --- .../backend-core/src/middleware/adminOnly.ts | 4 +- .../src/middleware/tests/builder.spec.ts | 141 ++++++++++++++++++ packages/backend-core/src/users.ts | 2 +- .../tests/core/utilities/structures/users.ts | 19 +++ packages/server/src/middleware/currentapp.ts | 1 - packages/types/src/documents/global/user.ts | 11 +- 6 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 packages/backend-core/src/middleware/tests/builder.spec.ts diff --git a/packages/backend-core/src/middleware/adminOnly.ts b/packages/backend-core/src/middleware/adminOnly.ts index dbe1e3a501..dc2fe9064e 100644 --- a/packages/backend-core/src/middleware/adminOnly.ts +++ b/packages/backend-core/src/middleware/adminOnly.ts @@ -1,6 +1,6 @@ -import { BBContext } from "@budibase/types" +import { UserCtx } from "@budibase/types" -export default async (ctx: BBContext, next: any) => { +export default async (ctx: UserCtx, next: any) => { if ( !ctx.internal && (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) diff --git a/packages/backend-core/src/middleware/tests/builder.spec.ts b/packages/backend-core/src/middleware/tests/builder.spec.ts new file mode 100644 index 0000000000..68c72ffe8a --- /dev/null +++ b/packages/backend-core/src/middleware/tests/builder.spec.ts @@ -0,0 +1,141 @@ +import adminOnly from "../adminOnly" +import builderOnly from "../builderOnly" +import builderOrAdmin from "../builderOrAdmin" +import { structures } from "../../../tests" +import { ContextUser } from "@budibase/types" +import { doInAppContext } from "../../context" + +const appId = "app_aaa" +const basicUser = structures.users.user() +const adminUser = structures.users.adminUser() +const adminOnlyUser = structures.users.adminOnlyUser() +const builderUser = structures.users.builderUser() +const appBuilderUser = structures.users.appBuilderUser(appId) + +function buildUserCtx(user: ContextUser) { + return { + internal: false, + user, + throw: jest.fn(), + } as any +} + +function passed(throwFn: jest.Func, nextFn: jest.Func) { + expect(throwFn).not.toBeCalled() + expect(nextFn).toBeCalled() +} + +function threw(throwFn: jest.Func) { + // cant check next, the throw function doesn't actually throw - so it still continues + expect(throwFn).toBeCalled() +} + +describe("adminOnly middleware", () => { + it("should allow admin user", () => { + const ctx = buildUserCtx(adminUser), + next = jest.fn() + adminOnly(ctx, next) + passed(ctx.throw, next) + }) + + it("should not allow basic user", () => { + const ctx = buildUserCtx(basicUser), + next = jest.fn() + adminOnly(ctx, next) + threw(ctx.throw) + }) + + it("should not allow builder user", () => { + const ctx = buildUserCtx(builderUser), + next = jest.fn() + adminOnly(ctx, next) + threw(ctx.throw) + }) +}) + +describe("builderOnly middleware", () => { + it("should allow builder user", () => { + const ctx = buildUserCtx(builderUser), + next = jest.fn() + builderOnly(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow app builder user", () => { + const ctx = buildUserCtx(appBuilderUser), + next = jest.fn() + doInAppContext(appId, () => { + builderOnly(ctx, next) + }) + passed(ctx.throw, next) + }) + + it("should allow admin and builder user", () => { + const ctx = buildUserCtx(adminUser), + next = jest.fn() + builderOnly(ctx, next) + passed(ctx.throw, next) + }) + + it("should not allow admin user", () => { + const ctx = buildUserCtx(adminOnlyUser), + next = jest.fn() + builderOnly(ctx, next) + threw(ctx.throw) + }) + + it("should not allow app builder user to different app", () => { + const ctx = buildUserCtx(appBuilderUser), + next = jest.fn() + doInAppContext("app_bbb", () => { + builderOnly(ctx, next) + }) + threw(ctx.throw) + }) + + it("should not allow basic user", () => { + const ctx = buildUserCtx(basicUser), + next = jest.fn() + builderOnly(ctx, next) + threw(ctx.throw) + }) +}) + +describe("builderOrAdmin middleware", () => { + it("should allow builder user", () => { + const ctx = buildUserCtx(builderUser), + next = jest.fn() + builderOrAdmin(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow builder and admin user", () => { + const ctx = buildUserCtx(adminUser), + next = jest.fn() + builderOrAdmin(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow admin user", () => { + const ctx = buildUserCtx(adminOnlyUser), + next = jest.fn() + builderOrAdmin(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow app builder user", () => { + const ctx = buildUserCtx(appBuilderUser), + next = jest.fn() + doInAppContext(appId, () => { + builderOrAdmin(ctx, next) + }) + passed(ctx.throw, next) + }) + + it("should not allow basic user", () => { + const ctx = buildUserCtx(basicUser), + next = jest.fn() + builderOrAdmin(ctx, next) + threw(ctx.throw) + }) +}) diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 2e6ede3cf3..71cd599f87 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -258,7 +258,7 @@ export async function getUserCount() { export function isBuilder(user: User | ContextUser, appId?: string) { if (user.builder?.global) { return true - } else if (appId && user.builder?.apps?.includes(appId)) { + } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { return true } return false diff --git a/packages/backend-core/tests/core/utilities/structures/users.ts b/packages/backend-core/tests/core/utilities/structures/users.ts index 7a6b4f0d80..0a4f2e8b54 100644 --- a/packages/backend-core/tests/core/utilities/structures/users.ts +++ b/packages/backend-core/tests/core/utilities/structures/users.ts @@ -1,5 +1,6 @@ import { AdminUser, + AdminOnlyUser, BuilderUser, SSOAuthDetails, SSOUser, @@ -21,6 +22,15 @@ export const adminUser = (userProps?: any): AdminUser => { } } +export const adminOnlyUser = (userProps?: any): AdminOnlyUser => { + return { + ...user(userProps), + admin: { + global: true, + }, + } +} + export const builderUser = (userProps?: any): BuilderUser => { return { ...user(userProps), @@ -30,6 +40,15 @@ export const builderUser = (userProps?: any): BuilderUser => { } } +export const appBuilderUser = (appId: string, userProps?: any): BuilderUser => { + return { + ...user(userProps), + builder: { + apps: [appId], + }, + } +} + export function ssoUser( opts: { user?: any; details?: SSOAuthDetails } = {} ): SSOUser { diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 10ee13a67a..1f580d80e4 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -11,7 +11,6 @@ import { getCachedSelf } from "../utilities/global" import env from "../environment" import { isWebhookEndpoint } from "./utils" import { UserCtx, ContextUser } from "@budibase/types" -import { removePortalUserPermissions } from "@budibase/backend-core/src/users" export default async (ctx: UserCtx, next: any) => { // try to get the appID from the request diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index e9e3ccc662..2ce714801d 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -42,7 +42,7 @@ export interface User extends Document { forceResetPassword?: boolean roles: UserRoles builder?: { - global: boolean + global?: boolean apps?: string[] } admin?: { @@ -70,7 +70,8 @@ export interface UserRoles { export interface BuilderUser extends User { builder: { - global: boolean + global?: boolean + apps?: string[] } } @@ -83,6 +84,12 @@ export interface AdminUser extends User { } } +export interface AdminOnlyUser extends User { + admin: { + global: boolean + } +} + export function isUser(user: object): user is User { return !!(user as User).roles } From 85dea47a31d12b859ef4a864e17213337c2c5f47 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 19 Jul 2023 16:19:34 +0100 Subject: [PATCH 0005/1404] Moving user admin/builder functions to shared-core for frontend to use. --- packages/backend-core/package.json | 1 + packages/backend-core/src/constants/db.ts | 44 ++----------------- packages/backend-core/src/users.ts | 40 +++-------------- packages/server/src/middleware/currentapp.ts | 4 +- packages/shared-core/src/index.ts | 1 + .../src/sdk/documents/applications.ts | 29 ++++++++++++ .../shared-core/src/sdk/documents/index.ts | 2 + .../shared-core/src/sdk/documents/users.ts | 35 +++++++++++++++ packages/shared-core/src/sdk/index.ts | 1 + packages/types/src/documents/document.ts | 41 +++++++++++++++++ packages/worker/src/sdk/users/users.ts | 2 +- 11 files changed, 123 insertions(+), 77 deletions(-) create mode 100644 packages/shared-core/src/sdk/documents/applications.ts create mode 100644 packages/shared-core/src/sdk/documents/index.ts create mode 100644 packages/shared-core/src/sdk/documents/users.ts create mode 100644 packages/shared-core/src/sdk/index.ts diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 7f3c064c92..93f2529987 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -22,6 +22,7 @@ "dependencies": { "@budibase/nano": "10.1.2", "@budibase/pouchdb-replication-stream": "1.2.10", + "@budibase/shared-core": "0.0.0", "@budibase/types": "0.0.0", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index 34dab6c088..83f8298f54 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -1,5 +1,5 @@ -export const SEPARATOR = "_" -export const UNICODE_MAX = "\ufff0" +import { prefixed, DocumentType } from "@budibase/types" +export { SEPARATOR, UNICODE_MAX, DocumentType } from "@budibase/types" /** * Can be used to create a few different forms of querying a view. @@ -34,42 +34,6 @@ export enum InternalTable { USER_METADATA = "ta_users", } -export enum DocumentType { - USER = "us", - GROUP = "gr", - WORKSPACE = "workspace", - CONFIG = "config", - TEMPLATE = "template", - APP = "app", - DEV = "dev", - APP_DEV = "app_dev", - APP_METADATA = "app_metadata", - ROLE = "role", - MIGRATIONS = "migrations", - DEV_INFO = "devinfo", - AUTOMATION_LOG = "log_au", - ACCOUNT_METADATA = "acc_metadata", - PLUGIN = "plg", - DATASOURCE = "datasource", - DATASOURCE_PLUS = "datasource_plus", - APP_BACKUP = "backup", - TABLE = "ta", - ROW = "ro", - AUTOMATION = "au", - LINK = "li", - WEBHOOK = "wh", - INSTANCE = "inst", - LAYOUT = "layout", - SCREEN = "screen", - QUERY = "query", - DEPLOYMENTS = "deployments", - METADATA = "metadata", - MEM_VIEW = "view", - USER_FLAG = "flag", - AUTOMATION_METADATA = "meta_au", - AUDIT_LOG = "al", -} - export const StaticDatabases = { GLOBAL: { name: "global-db", @@ -93,7 +57,7 @@ export const StaticDatabases = { }, } -export const APP_PREFIX = DocumentType.APP + SEPARATOR -export const APP_DEV = DocumentType.APP_DEV + SEPARATOR +export const APP_PREFIX = prefixed(DocumentType.APP) +export const APP_DEV = prefixed(DocumentType.APP_DEV) export const APP_DEV_PREFIX = APP_DEV export const BUDIBASE_DATASOURCE_TYPE = "budibase" diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 71cd599f87..7224d827e8 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -18,6 +18,7 @@ import { User, ContextUser, } from "@budibase/types" +import { sdk } from "@budibase/shared-core" import { getGlobalDB } from "./context" import * as context from "./context" @@ -38,6 +39,12 @@ function removeUserPassword(users: User | User[]) { return users } +// extract from shared-core to make easily accessible from backend-core +export const isBuilder = sdk.users.isBuilder +export const isAdmin = sdk.users.isAdmin +export const hasAdminPermissions = sdk.users.hasAdminPermissions +export const hasBuilderPermissions = sdk.users.hasBuilderPermissions + export const bulkGetGlobalUsersById = async ( userIds: string[], opts?: GetOpts @@ -254,39 +261,6 @@ export async function getUserCount() { return response.total_rows } -// checks if a user is specifically a builder, given an app ID -export function isBuilder(user: User | ContextUser, appId?: string) { - if (user.builder?.global) { - return true - } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { - return true - } - return false -} - -// alias for hasAdminPermission, currently do the same thing -// in future whether someone has admin permissions and whether they are -// an admin for a specific resource could be separated -export function isAdmin(user: User | ContextUser) { - return hasAdminPermissions(user) -} - -// checks if a user is capable of building any app -export function hasBuilderPermissions(user?: User | ContextUser) { - if (!user) { - return false - } - return user.builder?.global || user.builder?.apps?.length !== 0 -} - -// checks if a user is capable of being an admin -export function hasAdminPermissions(user?: User | ContextUser) { - if (!user) { - return false - } - return user.admin?.global -} - // used to remove the builder/admin permissions, for processing the // user as an app user (they may have some specific role/group export function removePortalUserPermissions(user: User | ContextUser) { diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 1f580d80e4..800d43e69c 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -24,7 +24,7 @@ export default async (ctx: UserCtx, next: any) => { if ( isDevAppID(requestAppId) && !isWebhookEndpoint(ctx) && - (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) + !users.isBuilder(ctx.user, requestAppId) ) { return ctx.redirect("/") } @@ -70,7 +70,6 @@ export default async (ctx: UserCtx, next: any) => { } return context.doInAppContext(appId, async () => { - let skipCookie = false // if the user not in the right tenant then make sure they have no permissions // need to judge this only based on the request app ID, if ( @@ -83,7 +82,6 @@ export default async (ctx: UserCtx, next: any) => { ctx.user = users.cleanseUserObject(ctx.user) as ContextUser ctx.isAuthenticated = false roleId = roles.BUILTIN_ROLE_IDS.PUBLIC - skipCookie = true } ctx.appId = appId diff --git a/packages/shared-core/src/index.ts b/packages/shared-core/src/index.ts index 21f2f2c639..4a47eda898 100644 --- a/packages/shared-core/src/index.ts +++ b/packages/shared-core/src/index.ts @@ -2,3 +2,4 @@ export * from "./constants" export * as dataFilters from "./filters" export * as helpers from "./helpers" export * as utils from "./utils" +export * as sdk from "./sdk" diff --git a/packages/shared-core/src/sdk/documents/applications.ts b/packages/shared-core/src/sdk/documents/applications.ts new file mode 100644 index 0000000000..05129f6e75 --- /dev/null +++ b/packages/shared-core/src/sdk/documents/applications.ts @@ -0,0 +1,29 @@ +import { DocumentType, prefixed } from "@budibase/types" + +const APP_PREFIX = prefixed(DocumentType.APP) +const APP_DEV_PREFIX = prefixed(DocumentType.APP_DEV) + +export function getDevAppID(appId: string) { + if (!appId || appId.startsWith(APP_DEV_PREFIX)) { + return appId + } + // split to take off the app_ element, then join it together incase any other app_ exist + const split = appId.split(APP_PREFIX) + split.shift() + const rest = split.join(APP_PREFIX) + return `${APP_DEV_PREFIX}${rest}` +} + +/** + * Convert a development app ID to a deployed app ID. + */ +export function getProdAppID(appId: string) { + if (!appId || !appId.startsWith(APP_DEV_PREFIX)) { + return appId + } + // split to take off the app_dev element, then join it together incase any other app_ exist + const split = appId.split(APP_DEV_PREFIX) + split.shift() + const rest = split.join(APP_DEV_PREFIX) + return `${APP_PREFIX}${rest}` +} diff --git a/packages/shared-core/src/sdk/documents/index.ts b/packages/shared-core/src/sdk/documents/index.ts new file mode 100644 index 0000000000..d20631eef4 --- /dev/null +++ b/packages/shared-core/src/sdk/documents/index.ts @@ -0,0 +1,2 @@ +export * as applications from "./applications" +export * as users from "./users" diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts new file mode 100644 index 0000000000..df3ef0025f --- /dev/null +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -0,0 +1,35 @@ +import { ContextUser, User } from "@budibase/types" +import { getProdAppID } from "./applications" + +// checks if a user is specifically a builder, given an app ID +export function isBuilder(user: User | ContextUser, appId?: string) { + if (user.builder?.global) { + return true + } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { + return true + } + return false +} + +// alias for hasAdminPermission, currently do the same thing +// in future whether someone has admin permissions and whether they are +// an admin for a specific resource could be separated +export function isAdmin(user: User | ContextUser) { + return hasAdminPermissions(user) +} + +// checks if a user is capable of building any app +export function hasBuilderPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.builder?.global || user.builder?.apps?.length !== 0 +} + +// checks if a user is capable of being an admin +export function hasAdminPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.admin?.global +} diff --git a/packages/shared-core/src/sdk/index.ts b/packages/shared-core/src/sdk/index.ts new file mode 100644 index 0000000000..87868e2723 --- /dev/null +++ b/packages/shared-core/src/sdk/index.ts @@ -0,0 +1 @@ +export * from "./documents" diff --git a/packages/types/src/documents/document.ts b/packages/types/src/documents/document.ts index ac05214b82..6ba318269b 100644 --- a/packages/types/src/documents/document.ts +++ b/packages/types/src/documents/document.ts @@ -1,3 +1,44 @@ +export const SEPARATOR = "_" +export const UNICODE_MAX = "\ufff0" + +export const prefixed = (type: DocumentType) => `${type}${SEPARATOR}` + +export enum DocumentType { + USER = "us", + GROUP = "gr", + WORKSPACE = "workspace", + CONFIG = "config", + TEMPLATE = "template", + APP = "app", + DEV = "dev", + APP_DEV = "app_dev", + APP_METADATA = "app_metadata", + ROLE = "role", + MIGRATIONS = "migrations", + DEV_INFO = "devinfo", + AUTOMATION_LOG = "log_au", + ACCOUNT_METADATA = "acc_metadata", + PLUGIN = "plg", + DATASOURCE = "datasource", + DATASOURCE_PLUS = "datasource_plus", + APP_BACKUP = "backup", + TABLE = "ta", + ROW = "ro", + AUTOMATION = "au", + LINK = "li", + WEBHOOK = "wh", + INSTANCE = "inst", + LAYOUT = "layout", + SCREEN = "screen", + QUERY = "query", + DEPLOYMENTS = "deployments", + METADATA = "metadata", + MEM_VIEW = "view", + USER_FLAG = "flag", + AUTOMATION_METADATA = "meta_au", + AUDIT_LOG = "al", +} + export interface Document { _id?: string _rev?: string diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index cfa68932d2..e0aa042995 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -176,7 +176,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => { export async function isPreventPasswordActions(user: User, account?: Account) { // when in maintenance mode we allow sso users with the admin role // to perform any password action - this prevents lockout - if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE && user.admin?.global) { + if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE && usersCore.isAdmin(user)) { return false } From e469abb6793cecd1170f7c8c129dc3955539e1f5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 19 Jul 2023 18:05:02 +0100 Subject: [PATCH 0006/1404] reworking frontend to use shared core functions to check if is admin or builder (needs further expansion). --- .../backend-core/src/middleware/adminOnly.ts | 6 +-- .../_components/BuilderSidePanel.svelte | 12 ++--- .../src/pages/builder/apps/index.svelte | 46 +++++++++---------- .../builder/src/pages/builder/index.svelte | 3 +- .../src/pages/builder/portal/_layout.svelte | 3 +- .../portal/users/users/[userId].svelte | 7 +-- .../_components/AppsTableRenderer.svelte | 3 +- .../users/_components/UpdateRolesModal.svelte | 3 +- packages/builder/src/stores/portal/auth.js | 9 ++-- packages/builder/src/stores/portal/users.js | 9 +++- .../shared-core/src/sdk/documents/users.ts | 4 ++ 11 files changed, 58 insertions(+), 47 deletions(-) diff --git a/packages/backend-core/src/middleware/adminOnly.ts b/packages/backend-core/src/middleware/adminOnly.ts index dc2fe9064e..6b2ee87c01 100644 --- a/packages/backend-core/src/middleware/adminOnly.ts +++ b/packages/backend-core/src/middleware/adminOnly.ts @@ -1,10 +1,8 @@ import { UserCtx } from "@budibase/types" +import { isAdmin } from "../users" export default async (ctx: UserCtx, next: any) => { - if ( - !ctx.internal && - (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) - ) { + if (!ctx.internal && !isAdmin(ctx.user)) { ctx.throw(403, "Admin user only endpoint.") } return next() diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index 1dd4453537..db56602463 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -12,12 +12,12 @@ } from "@budibase/bbui" import { store } from "builderStore" import { groups, licensing, apps, users, auth, admin } from "stores/portal" - import { fetchData } from "@budibase/frontend-core" + import { fetchData, Constants, Utils } from "@budibase/frontend-core" + import { sdk } from "@budibase/shared-core" import { API } from "api" import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte" import RoleSelect from "components/common/RoleSelect.svelte" import UpgradeModal from "components/common/users/UpgradeModal.svelte" - import { Constants, Utils } from "@budibase/frontend-core" import { emailValidator } from "helpers/validation" import { roles } from "stores/backend" import { fly } from "svelte/transition" @@ -108,7 +108,7 @@ await usersFetch.refresh() filteredUsers = $usersFetch.rows.map(user => { - const isBuilderOrAdmin = user.admin?.global || user.builder?.global + const isBuilderOrAdmin = sdk.users.isBuilderOrAdmin(user, prodAppId) let role = undefined if (isBuilderOrAdmin) { role = Constants.Roles.ADMIN @@ -258,7 +258,7 @@ } // Must exclude users who have explicit privileges const userByEmail = filteredUsers.reduce((acc, user) => { - if (user.role || user.admin?.global || user.builder?.global) { + if (user.role || sdk.users.isBuilderOrAdmin(user, prodAppId)) { acc.push(user.email) } return acc @@ -389,9 +389,9 @@ } const userTitle = user => { - if (user.admin?.global) { + if (sdk.users.isAdmin(user)) { return "Admin" - } else if (user.builder?.global) { + } else if (sdk.users.isBuilder(user, prodAppId)) { return "Developer" } else { return "App user" diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte index ab75c50747..f6c5df17c9 100644 --- a/packages/builder/src/pages/builder/apps/index.svelte +++ b/packages/builder/src/pages/builder/apps/index.svelte @@ -22,7 +22,7 @@ import Spaceman from "assets/bb-space-man.svg" import Logo from "assets/bb-emblem.svg" import { UserAvatar } from "@budibase/frontend-core" - import { helpers } from "@budibase/shared-core" + import { helpers, sdk } from "@budibase/shared-core" let loaded = false let userInfoModal @@ -43,32 +43,30 @@ $: userGroups = $groups.filter(group => group.users.find(user => user._id === $auth.user?._id) ) - let userApps = [] $: publishedApps = $apps.filter(publishedAppsOnly) + $: userApps = getUserApps($auth.user) - $: { - if (!Object.keys($auth.user?.roles).length && $auth.user?.userGroups) { - userApps = - $auth.user?.builder?.global || $auth.user?.admin?.global - ? publishedApps - : publishedApps.filter(app => { - return userGroups.find(group => { - return groups.actions - .getGroupAppIds(group) - .map(role => apps.extractAppId(role)) - .includes(app.appId) - }) - }) - } else { - userApps = - $auth.user?.builder?.global || $auth.user?.admin?.global - ? publishedApps - : publishedApps.filter(app => - Object.keys($auth.user?.roles) - .map(x => apps.extractAppId(x)) - .includes(app.appId) - ) + function getUserApps(user) { + if (sdk.users.isAdmin(user)) { + return publishedApps } + return publishedApps.filter(app => { + if (sdk.users.isBuilder(user, app.appId)) { + return true + } + if (!Object.keys(user?.roles).length && user?.userGroups) { + return userGroups.find(group => { + return groups.actions + .getGroupAppIds(group) + .map(role => apps.extractAppId(role)) + .includes(app.appId) + }) + } else { + return Object.keys($auth.user?.roles) + .map(x => apps.extractAppId(x)) + .includes(app.appId) + } + }) } function getUrl(app) { diff --git a/packages/builder/src/pages/builder/index.svelte b/packages/builder/src/pages/builder/index.svelte index fcaa7fc55b..c6d9d3c1c3 100644 --- a/packages/builder/src/pages/builder/index.svelte +++ b/packages/builder/src/pages/builder/index.svelte @@ -1,11 +1,12 @@ diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/UpdateRolesModal.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/UpdateRolesModal.svelte index a9399fcca7..9ad41ad652 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/UpdateRolesModal.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/UpdateRolesModal.svelte @@ -2,6 +2,7 @@ import { createEventDispatcher } from "svelte" import { Body, Select, ModalContent, notifications } from "@budibase/bbui" import { users } from "stores/portal" + import { sdk } from "@budibase/shared-core" export let app export let user @@ -15,7 +16,7 @@ .filter(role => role._id !== "PUBLIC") .map(role => ({ value: role._id, label: role.name })) - if (!user?.builder?.global) { + if (!sdk.users.isBuilder(user, app?.appId)) { options.push({ value: NO_ACCESS, label: "No Access" }) } let selectedRole = user?.roles?.[app?._id] diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index ce64965af7..40113e2f76 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -2,6 +2,7 @@ import { derived, writable, get } from "svelte/store" import { API } from "api" import { admin } from "stores/portal" import analytics from "analytics" +import { sdk } from "@budibase/shared-core" export function createAuthStore() { const auth = writable({ @@ -17,8 +18,8 @@ export function createAuthStore() { let isBuilder = false if ($store.user) { const user = $store.user - isAdmin = !!user.admin?.global - isBuilder = !!user.builder?.global + isAdmin = sdk.users.isAdmin(user) + isBuilder = sdk.users.isBuilder(user) } return { user: $store.user, @@ -57,8 +58,8 @@ export function createAuthStore() { name: user.account?.name, user_id: user._id, tenant: user.tenantId, - admin: user?.admin?.global, - builder: user?.builder?.global, + admin: sdk.users.isAdmin(user), + builder: sdk.users.isBuilder(user), "Company size": user.account?.size, "Job role": user.account?.profession, }, diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index e522cd7958..992f6a5418 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -2,6 +2,7 @@ import { writable } from "svelte/store" import { API } from "api" import { update } from "lodash" import { licensing } from "." +import { sdk } from "@budibase/shared-core" export function createUsersStore() { const { subscribe, set } = writable({}) @@ -111,8 +112,12 @@ export function createUsersStore() { return await API.saveUser(user) } - const getUserRole = ({ admin, builder }) => - admin?.global ? "admin" : builder?.global ? "developer" : "appUser" + const getUserRole = user => + sdk.users.isAdmin(user) + ? "admin" + : sdk.users.isBuilder(user) + ? "developer" + : "appUser" const refreshUsage = fn => diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts index df3ef0025f..931f651a0e 100644 --- a/packages/shared-core/src/sdk/documents/users.ts +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -18,6 +18,10 @@ export function isAdmin(user: User | ContextUser) { return hasAdminPermissions(user) } +export function isAdminOrBuilder(user: User | ContextUser, appId?: string) { + return isBuilder(user, appId) || isAdmin(user) +} + // checks if a user is capable of building any app export function hasBuilderPermissions(user?: User | ContextUser) { if (!user) { From 00c04ec9acb5c4657402c05b57910af18242f0e8 Mon Sep 17 00:00:00 2001 From: Gerard Burns Date: Thu, 20 Jul 2023 10:05:50 +0100 Subject: [PATCH 0007/1404] New Design Section Nav Component --- .../builder/src/components/design/Pane.svelte | 21 +++ .../src/components/design/RightPanel.svelte | 112 +++++++++++ .../_components/NavigationInfoPanel.svelte | 33 ---- .../NavigationInfoPanel/CustomizePane.svelte | 176 ++++++++++++++++++ .../NavigationInfoPanel/SettingsPane.svelte | 31 +++ .../NavigationInfoPanel/index.svelte | 10 + .../design/[screenId]/navigation/index.svelte | 2 +- 7 files changed, 351 insertions(+), 34 deletions(-) create mode 100644 packages/builder/src/components/design/Pane.svelte create mode 100644 packages/builder/src/components/design/RightPanel.svelte delete mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte diff --git a/packages/builder/src/components/design/Pane.svelte b/packages/builder/src/components/design/Pane.svelte new file mode 100644 index 0000000000..88b6ea1c05 --- /dev/null +++ b/packages/builder/src/components/design/Pane.svelte @@ -0,0 +1,21 @@ + + +{#if $paneStore} +
+ +
+{/if} + + diff --git a/packages/builder/src/components/design/RightPanel.svelte b/packages/builder/src/components/design/RightPanel.svelte new file mode 100644 index 0000000000..f9075600f9 --- /dev/null +++ b/packages/builder/src/components/design/RightPanel.svelte @@ -0,0 +1,112 @@ + + +
+
+
+ +
+ {title} +
+
+ {#each Object.entries(panes) as [id, pane]} +
+ +
+ {/each} +
+
+ + +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte deleted file mode 100644 index 614e1eed80..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - - - {#if $selectedScreen.layoutId} - - You can't preview your navigation settings using this screen as it uses - a custom layout, which is deprecated - - {/if} - - Your navigation is configured for all the screens within your app. - - - You can hide and show your navigation for each screen in the screen - settings. - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte new file mode 100644 index 0000000000..05108b1736 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/CustomizePane.svelte @@ -0,0 +1,176 @@ + + + +
+
+ + CHANGES WILL APPLY TO ALL SCREENS +
+ + Your navigation is configured for all the screens within your app. + +
+ +
+
+ +
+ + update("navigation", "Top")} + /> + update("navigation", "Left")} + /> + + + {#if $store.navigation.navigation === "Top"} +
+ +
+ update("sticky", e.detail)} + /> +
+ +
+ update("logoUrl", e.detail)} + updateOnChange={false} + /> + {/if} +
+ +
+ update("hideTitle", !e.detail)} + /> + {#if !$store.navigation.hideTitle} +
+ +
+ update("title", e.detail)} + updateOnChange={false} + /> + {/if} +
+ +
+ update("navBackground", e.detail)} + /> +
+ +
+ update("navTextColor", e.detail)} + /> +
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte new file mode 100644 index 0000000000..d3ce720994 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/SettingsPane.svelte @@ -0,0 +1,31 @@ + + + +
+ + Show nav on this screen +
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte new file mode 100644 index 0000000000..6ce5405f93 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel/index.svelte @@ -0,0 +1,10 @@ + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte index fc2e03d8e8..2331d8b285 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte @@ -1,6 +1,6 @@ From d94fe7c2eb880c53cdd57d64020790f73ca46d75 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 20 Jul 2023 11:32:48 +0100 Subject: [PATCH 0008/1404] Moved screen settings/theme into the new rightpanel structure. Refactord the color picker to help with positioning. Removed dead home link --- .../bbui/src/Actions/position_dropdown.js | 3 +- .../bbui/src/ColorPicker/ColorPicker.svelte | 75 +++---- packages/bbui/src/Popover/Popover.svelte | 8 + .../builder/src/components/design/Pane.svelte | 2 +- .../controls/ColumnEditor/CellDrawer.svelte | 2 - .../design/[screenId]/_layout.svelte | 6 - .../ScreenSettingsPanel/GeneralPane.svelte | 198 ++++++++++++++++++ .../ScreenSettingsPanel/ThemePane.svelte | 98 +++++++++ .../ScreenSettingsPanel/index.svelte | 15 ++ .../theme}/AppThemeSelect.svelte | 0 .../theme}/ButtonRoundnessSelect.svelte | 0 .../design/[screenId]/screens/index.svelte | 2 +- .../theme/_components/ThemeInfoPanel.svelte | 12 -- .../_components/ThemeSettingsPanel.svelte | 55 ----- .../design/[screenId]/theme/index.svelte | 7 - .../server/src/api/controllers/application.ts | 7 +- 16 files changed, 354 insertions(+), 136 deletions(-) create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/GeneralPane.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/index.svelte rename packages/builder/src/pages/builder/app/[application]/design/[screenId]/{theme/_components => screens/_components/ScreenSettingsPanel/theme}/AppThemeSelect.svelte (100%) rename packages/builder/src/pages/builder/app/[application]/design/[screenId]/{theme/_components => screens/_components/ScreenSettingsPanel/theme}/ButtonRoundnessSelect.svelte (100%) delete mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeInfoPanel.svelte delete mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeSettingsPanel.svelte delete mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/index.svelte diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index 01555446d9..d73b3b4d6a 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -32,11 +32,10 @@ export default function positionDropdown(element, opts) { left: null, top: null, } - // Determine vertical styles if (align === "right-outside") { styles.top = anchorBounds.top - } else if (window.innerHeight - anchorBounds.bottom < 100) { + } else if (window.innerHeight - anchorBounds.bottom < (maxHeight || 100)) { styles.top = anchorBounds.top - elementBounds.height - offset styles.maxHeight = maxHeight || 240 } else { diff --git a/packages/bbui/src/ColorPicker/ColorPicker.svelte b/packages/bbui/src/ColorPicker/ColorPicker.svelte index 9a70134fb6..2ba5309860 100644 --- a/packages/bbui/src/ColorPicker/ColorPicker.svelte +++ b/packages/bbui/src/ColorPicker/ColorPicker.svelte @@ -1,8 +1,8 @@ -
-
(open = true)}> -
-
- {#if open} -
+
{ + dropdown.toggle() + }} +> +
+
+ + + +
{#each categories as category}
{category.label}
@@ -187,8 +184,8 @@
- {/if} -
+ + diff --git a/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte b/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte index a03d22386d..8e3079101a 100644 --- a/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/ColumnEditor/CellDrawer.svelte @@ -42,7 +42,6 @@ (column.background = e.detail)} - alignRight spectrumTheme={$store.theme} /> @@ -51,7 +50,6 @@ (column.color = e.detail)} - alignRight spectrumTheme={$store.theme} /> 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 8bc0dcc3e5..72d959c18b 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 @@ -38,12 +38,6 @@ active={$isActive("./components")} on:click={() => $goto("./components")} /> - $goto("./theme")} - /> + + + + + {#if $selectedScreen.layoutId} + + This screen uses a custom layout, which is deprecated + + {/if} + + {#each screenSettings as setting (setting.key)} + setScreenSetting(setting, val)} + props={{ ...setting.props, error: errors[setting.key] }} + {bindings} + /> + {/each} + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte new file mode 100644 index 0000000000..2e59ad5f7d --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte @@ -0,0 +1,98 @@ + + + +
+
+ + CHANGES WILL APPLY TO ALL SCREENS +
+ + Your navigation is configured for all the screens within your app. + +
+ + + + + + + update("buttonBorderRadius", e.detail)} + /> + + update("primaryColor", val)} + props={{ + spectrumTheme: $store.theme, + }} + /> + update("primaryColorHover", val)} + props={{ + spectrumTheme: $store.theme, + }} + /> + +
+ + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/index.svelte new file mode 100644 index 0000000000..554527ca4a --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/index.svelte @@ -0,0 +1,15 @@ + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/AppThemeSelect.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/theme/AppThemeSelect.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/AppThemeSelect.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/theme/AppThemeSelect.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ButtonRoundnessSelect.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/theme/ButtonRoundnessSelect.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ButtonRoundnessSelect.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/theme/ButtonRoundnessSelect.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/index.svelte index 6236721e1a..77019abebf 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/index.svelte @@ -1,7 +1,7 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeInfoPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeInfoPanel.svelte deleted file mode 100644 index c3325852c8..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeInfoPanel.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Your theme is set across all the screens within your app. - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeSettingsPanel.svelte deleted file mode 100644 index 1c86a51f67..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ThemeSettingsPanel.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - update("buttonBorderRadius", e.detail)} - /> - - - - update("primaryColor", e.detail)} - /> - - - - update("primaryColorHover", e.detail)} - /> - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/index.svelte deleted file mode 100644 index 013257d8e1..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/index.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index c068a422b0..6f9acb3efe 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -298,12 +298,7 @@ async function performAppCreate(ctx: UserCtx) { title: name, navWidth: "Large", navBackground: "var(--spectrum-global-color-gray-100)", - links: [ - { - url: "/home", - text: "Home", - }, - ], + links: [], }, theme: "spectrum--light", customTheme: { From a2e12a693c046e9ba9c89198e8c2f85dd4c9f5a1 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 20 Jul 2023 12:16:59 +0100 Subject: [PATCH 0009/1404] Corrected theme info copy --- .../screens/_components/ScreenSettingsPanel/ThemePane.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte index 2e59ad5f7d..20d5054648 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel/ThemePane.svelte @@ -38,7 +38,7 @@ CHANGES WILL APPLY TO ALL SCREENS
- Your navigation is configured for all the screens within your app. + Your theme is configured for all the screens within your app.
From 3abe5d4cb21dda1a9cba0f1764499ad96d87f817 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 20 Jul 2023 18:34:12 +0100 Subject: [PATCH 0010/1404] Frontend work to support logging in as an app builder - also making sure when a new app is created that the user is assigned app access to it. --- packages/backend-core/src/users.ts | 25 +++++++++ packages/builder/src/stores/portal/menu.js | 44 ++++++++------- .../server/src/api/controllers/application.ts | 37 ++++--------- packages/server/src/db/utils.ts | 8 +-- packages/server/src/middleware/authorized.ts | 11 ++-- .../src/sdk/app/applications/applications.ts | 54 +++++++++++++++++++ .../server/src/sdk/app/applications/index.ts | 2 + .../shared-core/src/sdk/documents/users.ts | 16 +++++- 8 files changed, 143 insertions(+), 54 deletions(-) create mode 100644 packages/server/src/sdk/app/applications/applications.ts diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 7224d827e8..05abe70fe3 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -2,6 +2,7 @@ import { directCouchFind, DocumentType, generateAppUserID, + getGlobalIDFromUserMetadataID, getGlobalUserParams, getProdAppID, getUsersByAppParams, @@ -21,6 +22,7 @@ import { import { sdk } from "@budibase/shared-core" import { getGlobalDB } from "./context" import * as context from "./context" +import { user as userCache } from "./cache" type GetOpts = { cleanup?: boolean } @@ -42,8 +44,10 @@ function removeUserPassword(users: User | User[]) { // extract from shared-core to make easily accessible from backend-core export const isBuilder = sdk.users.isBuilder export const isAdmin = sdk.users.isAdmin +export const isAdminOrBuilder = sdk.users.isAdminOrBuilder export const hasAdminPermissions = sdk.users.hasAdminPermissions export const hasBuilderPermissions = sdk.users.hasBuilderPermissions +export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions export const bulkGetGlobalUsersById = async ( userIds: string[], @@ -77,6 +81,27 @@ export const bulkUpdateGlobalUsers = async (users: User[]) => { return (await db.bulkDocs(users)) as BulkDocsResponse } +export const grantAppBuilderAccess = async (userId: string, appId: string) => { + const prodAppId = getProdAppID(appId) + const db = getGlobalDB() + const user = (await db.get(userId)) as User + if (!user.builder) { + user.builder = {} + } + if (!user.builder.apps) { + user.builder.apps = [] + } + if (!user.builder.apps.includes(prodAppId)) { + user.builder.apps.push(prodAppId) + } + try { + await db.put(user) + await userCache.invalidateUser(userId) + } catch (err: any) { + throw new Error(`Unable to grant user access: ${err.message}`) + } +} + export async function getById(id: string, opts?: GetOpts): Promise { const db = context.getGlobalDB() let user = await db.get(id) diff --git a/packages/builder/src/stores/portal/menu.js b/packages/builder/src/stores/portal/menu.js index c66c98d00f..2c17ce0b36 100644 --- a/packages/builder/src/stores/portal/menu.js +++ b/packages/builder/src/stores/portal/menu.js @@ -2,8 +2,12 @@ import { derived } from "svelte/store" import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags" import { admin } from "./admin" import { auth } from "./auth" +import { sdk } from "@budibase/shared-core" export const menu = derived([admin, auth], ([$admin, $auth]) => { + const user = $auth?.user + const isAdmin = sdk.users.isAdmin(user) + const cloud = $admin?.cloud // Determine user sub pages let userSubPages = [ { @@ -24,19 +28,24 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { title: "Apps", href: "/builder/portal/apps", }, - { + ] + if ( + sdk.users.hasBuilderPermissions(user) && + !sdk.users.hasAppBuilderPermissions(user) + ) { + menu.push({ title: "Users", href: "/builder/portal/users", subPages: userSubPages, - }, - { - title: "Plugins", - href: "/builder/portal/plugins", - }, - ] + }) + } + menu.push({ + title: "Plugins", + href: "/builder/portal/plugins", + }) // Add settings page for admins - if ($auth.isAdmin) { + if (isAdmin) { let settingsSubPages = [ { title: "Auth", @@ -59,7 +68,7 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { href: "/builder/portal/settings/environment", }, ] - if (!$admin.cloud) { + if (!cloud) { settingsSubPages.push({ title: "Version", href: "/builder/portal/settings/version", @@ -84,38 +93,35 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { href: "/builder/portal/account/usage", }, ] - if ($auth.isAdmin) { + if (isAdmin) { accountSubPages.push({ title: "Audit Logs", href: "/builder/portal/account/auditLogs", }) - if (!$admin.cloud) { + if (!cloud) { accountSubPages.push({ title: "System Logs", href: "/builder/portal/account/systemLogs", }) } } - if ($admin.cloud && $auth?.user?.accountPortalAccess) { + if (cloud && user?.accountPortalAccess) { accountSubPages.push({ title: "Upgrade", - href: $admin.accountPortalUrl + "/portal/upgrade", + href: $admin?.accountPortalUrl + "/portal/upgrade", }) - } else if (!$admin.cloud && $auth.isAdmin) { + } else if (!cloud && isAdmin) { accountSubPages.push({ title: "Upgrade", href: "/builder/portal/account/upgrade", }) } // add license check here - if ( - $auth?.user?.accountPortalAccess && - $auth.user.account.stripeCustomerId - ) { + if (user?.accountPortalAccess && user.account.stripeCustomerId) { accountSubPages.push({ title: "Billing", - href: $admin.accountPortalUrl + "/portal/billing", + href: $admin?.accountPortalUrl + "/portal/billing", }) } menu.push({ diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 6a0088d4dc..9538790827 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -50,12 +50,13 @@ import { MigrationType, PlanType, Screen, - SocketSession, UserCtx, + ContextUser, } from "@budibase/types" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" import sdk from "../../sdk" import { builderSocket } from "../../websockets" +import { grantAppBuilderAccess } from "@budibase/backend-core/src/users" // utility function, need to do away with this async function getLayouts() { @@ -178,32 +179,10 @@ export const addSampleData = async (ctx: UserCtx) => { } export async function fetch(ctx: UserCtx) { - const dev = ctx.query && ctx.query.status === AppStatus.DEV - const all = ctx.query && ctx.query.status === AppStatus.ALL - const apps = (await dbCore.getAllApps({ dev, all })) as App[] - - const appIds = apps - .filter((app: any) => app.status === "development") - .map((app: any) => app.appId) - - // get the locks for all the dev apps - if (dev || all) { - const locks = await getLocksById(appIds) - for (let app of apps) { - const lock = locks[app.appId] - if (lock) { - app.lockedBy = lock - } else { - // make sure its definitely not present - delete app.lockedBy - } - } - } - - // Enrich apps with all builder user sessions - const enrichedApps = await sdk.users.sessions.enrichApps(apps) - - ctx.body = await checkAppMetadata(enrichedApps) + ctx.body = await sdk.applications.fetch( + ctx.query.status as AppStatus, + ctx.user + ) } export async function fetchAppDefinition(ctx: UserCtx) { @@ -395,6 +374,10 @@ async function appPostCreate(ctx: UserCtx, app: App) { tenantId, appId: app.appId, }) + // they are an app builder, creating a new app, make sure they can access it + if (users.hasAppBuilderPermissions(ctx.user)) { + await users.grantAppBuilderAccess(ctx.user._id!, app.appId) + } await creationEvents(ctx.request, app) // app import & template creation if (ctx.request.body.useTemplate === "true") { diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index e08392c3a1..eb5cbc27ef 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -3,10 +3,10 @@ import { db as dbCore } from "@budibase/backend-core" type Optional = string | null -export const AppStatus = { - DEV: "development", - ALL: "all", - DEPLOYED: "published", +export enum AppStatus { + DEV = "development", + ALL = "all", + DEPLOYED = "published", } export const BudibaseInternalDB = { diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 81e42604bc..dba5d47cb9 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -5,7 +5,7 @@ import { context, users, } from "@budibase/backend-core" -import { Role } from "@budibase/types" +import { Role, UserCtx } from "@budibase/types" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" @@ -22,14 +22,19 @@ const csrf = auth.buildCsrfMiddleware() * - Otherwise the user must have the required role. */ const checkAuthorized = async ( - ctx: any, + ctx: UserCtx, resourceRoles: any, permType: any, permLevel: any ) => { const appId = context.getAppId() // check if this is a builder api and the user is not a builder - const isBuilder = users.isBuilder(ctx.user, appId) + let isBuilder + if (!appId) { + isBuilder = users.hasBuilderPermissions(ctx.user) + } else { + isBuilder = users.isBuilder(ctx.user, appId) + } const isBuilderApi = permType === permissions.PermissionType.BUILDER if (isBuilderApi && !isBuilder) { return ctx.throw(403, "Not Authorized") diff --git a/packages/server/src/sdk/app/applications/applications.ts b/packages/server/src/sdk/app/applications/applications.ts new file mode 100644 index 0000000000..73cacb8983 --- /dev/null +++ b/packages/server/src/sdk/app/applications/applications.ts @@ -0,0 +1,54 @@ +import { AppStatus } from "../../../db/utils" +import { App, ContextUser } from "@budibase/types" +import { getLocksById } from "../../../utilities/redis" +import { enrichApps } from "../../users/sessions" +import { checkAppMetadata } from "../../../automations/logging" +import { db as dbCore, users } from "@budibase/backend-core" + +export function filterAppList(user: ContextUser, apps: App[]) { + let appList: string[] = [] + if (users.hasAppBuilderPermissions(user)) { + appList = user.builder?.apps! + } else if (!users.isAdminOrBuilder(user)) { + appList = Object.keys(user.roles || {}) + } else { + return apps + } + const finalApps: App[] = [] + for (let app of apps) { + if (appList.includes(dbCore.getProdAppID(app.appId))) { + finalApps.push(app) + } + } + return finalApps +} + +export async function fetch(status: AppStatus, user: ContextUser) { + const dev = status === AppStatus.DEV + const all = status === AppStatus.ALL + let apps = (await dbCore.getAllApps({ dev, all })) as App[] + apps = filterAppList(user, apps) + + const appIds = apps + .filter((app: any) => app.status === "development") + .map((app: any) => app.appId) + + // get the locks for all the dev apps + if (dev || all) { + const locks = await getLocksById(appIds) + for (let app of apps) { + const lock = locks[app.appId] + if (lock) { + app.lockedBy = lock + } else { + // make sure its definitely not present + delete app.lockedBy + } + } + } + + // Enrich apps with all builder user sessions + const enrichedApps = await enrichApps(apps) + + return await checkAppMetadata(enrichedApps) +} diff --git a/packages/server/src/sdk/app/applications/index.ts b/packages/server/src/sdk/app/applications/index.ts index d917225e52..963d065ce2 100644 --- a/packages/server/src/sdk/app/applications/index.ts +++ b/packages/server/src/sdk/app/applications/index.ts @@ -1,7 +1,9 @@ import * as sync from "./sync" import * as utils from "./utils" +import * as applications from "./applications" export default { ...sync, ...utils, + ...applications, } diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts index 931f651a0e..1a9314f731 100644 --- a/packages/shared-core/src/sdk/documents/users.ts +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -3,6 +3,9 @@ import { getProdAppID } from "./applications" // checks if a user is specifically a builder, given an app ID export function isBuilder(user: User | ContextUser, appId?: string) { + if (!user) { + return false + } if (user.builder?.global) { return true } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { @@ -15,6 +18,9 @@ export function isBuilder(user: User | ContextUser, appId?: string) { // in future whether someone has admin permissions and whether they are // an admin for a specific resource could be separated export function isAdmin(user: User | ContextUser) { + if (!user) { + return false + } return hasAdminPermissions(user) } @@ -22,12 +28,20 @@ export function isAdminOrBuilder(user: User | ContextUser, appId?: string) { return isBuilder(user, appId) || isAdmin(user) } +// check if they are a builder within an app (not necessarily a global builder) +export function hasAppBuilderPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return !user.builder?.global && user.builder?.apps?.length !== 0 +} + // checks if a user is capable of building any app export function hasBuilderPermissions(user?: User | ContextUser) { if (!user) { return false } - return user.builder?.global || user.builder?.apps?.length !== 0 + return user.builder?.global || hasAppBuilderPermissions(user) } // checks if a user is capable of being an admin From d9c8e26f65467869c35317d31a0153d2cbdf9807 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 Jul 2023 18:29:46 +0100 Subject: [PATCH 0011/1404] Re-writing APIs based on most recent discussion about RBAC and per app builders. --- packages/types/src/api/web/user.ts | 7 --- packages/types/src/documents/global/user.ts | 1 + .../src/api/controllers/global/users.ts | 60 +++++++++++++++++-- .../worker/src/api/routes/global/users.ts | 11 +++- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/packages/types/src/api/web/user.ts b/packages/types/src/api/web/user.ts index 4a27f781af..619362805a 100644 --- a/packages/types/src/api/web/user.ts +++ b/packages/types/src/api/web/user.ts @@ -85,10 +85,3 @@ export interface AcceptUserInviteResponse { export interface SyncUserRequest { previousUser?: User } - -export interface AddAppBuilderRequest { - userId: string - appId: string -} - -export interface RemoveAppBuilderRequest {} diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index 2ce714801d..3249660624 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -43,6 +43,7 @@ export interface User extends Document { roles: UserRoles builder?: { global?: boolean + appBuilder?: boolean apps?: string[] } admin?: { diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 38406dc239..6862e44b05 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -8,8 +8,6 @@ import env from "../../../environment" import { AcceptUserInviteRequest, AcceptUserInviteResponse, - AddAppBuilderRequest, - RemoveAppBuilderRequest, BulkUserRequest, BulkUserResponse, CloudAccount, @@ -32,6 +30,7 @@ import { tenancy, platform, ErrorCode, + db as dbCore, } from "@budibase/backend-core" import { checkAnyUserExists } from "../../../utilities/users" import { isEmailConfigured } from "../../../utilities/email" @@ -434,8 +433,57 @@ export const inviteAccept = async ( } } -export const addAppBuilder = async (ctx: Ctx) => {} +export const grantAppBuilder = async (ctx: Ctx) => { + const { userId } = ctx.params + const user = await userSdk.getUser(userId) + if (!user.builder) { + user.builder = {} + } + user.builder.appBuilder = true + await userSdk.save(user, { hashPassword: false }) + ctx.body = { message: `User "${user.email}" granted app builder permissions` } +} -export const removeAppBuilder = async ( - ctx: Ctx -) => {} +export const addAppBuilder = async (ctx: Ctx) => { + const { userId, appId } = ctx.params + const user = await userSdk.getUser(userId) + if (!user.builder?.global || user.admin?.global) { + ctx.body = { message: "User already admin - no permissions updated." } + return + } + if (!user.builder?.appBuilder) { + ctx.throw( + 400, + "Unable to update access, user must be granted app builder permissions." + ) + } + const prodAppId = dbCore.getProdAppID(appId) + if (!user.builder.apps) { + user.builder.apps = [] + } + user.builder.apps.push(prodAppId) + await userSdk.save(user, { hashPassword: false }) + ctx.body = { message: `User "${user.email}" app builder access updated.` } +} + +export const removeAppBuilder = async (ctx: Ctx) => { + const { userId, appId } = ctx.params + const user = await userSdk.getUser(userId) + if (!user.builder?.global || user.admin?.global) { + ctx.body = { message: "User already admin - no permissions removed." } + return + } + if (!user.builder?.appBuilder) { + ctx.throw( + 400, + "Unable to update access, user must be granted app builder permissions." + ) + } + const prodAppId = dbCore.getProdAppID(appId) + const indexOf = user.builder?.apps?.indexOf(prodAppId) + if (indexOf && indexOf !== -1) { + user.builder.apps = user.builder.apps!.splice(indexOf, 1) + } + await userSdk.save(user, { hashPassword: false }) + ctx.body = { message: `User "${user.email}" app builder access removed.` } +} diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts index 557065e9a4..9c1b5d9acb 100644 --- a/packages/worker/src/api/routes/global/users.ts +++ b/packages/worker/src/api/routes/global/users.ts @@ -122,6 +122,15 @@ router buildAdminInitValidation(), controller.adminUser ) + .post("/api/global/users/:userId/app/builder", controller.grantAppBuilder) + .patch( + "/api/global/users/:userId/app/:appId/builder", + controller.addAppBuilder + ) + .delete( + "/api/global/users/:userId/app/:appId/builder", + controller.removeAppBuilder + ) .get("/api/global/users/tenant/:id", controller.tenantUserLookup) // global endpoint but needs to come at end (blocks other endpoints otherwise) .get("/api/global/users/:id", auth.builderOrAdmin, controller.find) @@ -132,7 +141,5 @@ router users.buildUserSaveValidation(), selfController.updateSelf ) - .post("/api/global/users/builder", controller.addAppBuilder) - .delete("/api/global/users/builder", controller.removeAppBuilder) export default router From 95faeea28638f234342c42ea87c39512c8509bf5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 18:32:31 +0200 Subject: [PATCH 0012/1404] datasourceType setup --- .../builder/src/components/backend/DataTable/DataTable.svelte | 1 + packages/client/src/components/app/GridBlock.svelte | 1 + packages/frontend-core/src/components/grid/layout/Grid.svelte | 2 ++ packages/frontend-core/src/components/grid/stores/rows.js | 3 ++- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 33db9b60e3..3fcaefc5bd 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -44,6 +44,7 @@ { loading, sort, tableId, + datasourceType, API, scroll, validation, @@ -111,7 +112,7 @@ export const deriveStores = context => { const newFetch = fetchData({ API, datasource: { - type: "table", + type: datasourceType, tableId: $tableId, }, options: { From 9ccc54773dbdea33264677caf24d7a4f5c383715 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 18:32:52 +0200 Subject: [PATCH 0013/1404] ViewV2 page --- .../TableNavigator/TableNavigator.svelte | 10 +++++++-- .../data/view/v2/[viewName].svelte | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index d9def682dc..ef8872c1ca 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -37,13 +37,19 @@ {/if} - {#each [...Object.keys(table.views || {})].sort() as viewName, idx (idx)} + {#each [...Object.entries(table.views || {})].sort() as [viewName, view], idx (idx)} $goto(`./view/${encodeURIComponent(viewName)}`)} + on:click={() => { + if (view.version === 2) { + $goto(`./view/v2/${encodeURIComponent(viewName)}`) + } else { + $goto(`./view/${encodeURIComponent(viewName)}`) + } + }} selectedBy={$userSelectedResourceMap[viewName]} > + import { views } from "stores/backend" + import { Grid } from "@budibase/frontend-core" + import { API } from "api" + + $: tableId = $views.selected?.id + + +
+ +
+ + From 82ea9a7cc1c35ed1a4db616b3b5924c425b74eed Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 10:14:42 +0200 Subject: [PATCH 0014/1404] Setup datasource type --- .../data/view/v2/{[viewName].svelte => [id].svelte} | 10 ++++++++-- .../frontend-core/src/components/grid/stores/rows.js | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) rename packages/builder/src/pages/builder/app/[application]/data/view/v2/{[viewName].svelte => [id].svelte} (73%) diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id].svelte similarity index 73% rename from packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v2/[id].svelte index 3df89bd777..266fd237f5 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id].svelte @@ -3,11 +3,17 @@ import { Grid } from "@budibase/frontend-core" import { API } from "api" - $: tableId = $views.selected?.id + export let id
- +
diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index f7b6f61a10..8f5a35ea1f 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -1,7 +1,14 @@ diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index e641bd99a5..caa9de222a 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -1,5 +1,5 @@ -
- -
- - + diff --git a/packages/builder/src/stores/backend/index.js b/packages/builder/src/stores/backend/index.js index 6fbc9f82c7..278e43c1ed 100644 --- a/packages/builder/src/stores/backend/index.js +++ b/packages/builder/src/stores/backend/index.js @@ -1,6 +1,7 @@ export { database } from "./database" export { tables } from "./tables" export { views } from "./views" +export { viewsV2 } from "./viewsV2" export { permissions } from "./permissions" export { roles } from "./roles" export { datasources, ImportTableError } from "./datasources" diff --git a/packages/builder/src/stores/backend/views.js b/packages/builder/src/stores/backend/views.js index 8df337a299..603b0830fc 100644 --- a/packages/builder/src/stores/backend/views.js +++ b/packages/builder/src/stores/backend/views.js @@ -9,7 +9,10 @@ export function createViewsStore() { const derivedStore = derived([store, tables], ([$store, $tables]) => { let list = [] $tables.list?.forEach(table => { - list = list.concat(Object.values(table?.views || {})) + const views = Object.values(table?.views || {}).filter(view => { + return view.version !== 2 + }) + list = list.concat(views) }) return { ...$store, @@ -26,11 +29,7 @@ export function createViewsStore() { } const deleteView = async view => { - if (view.version === 2) { - await API.viewV2.delete(view.id) - } else { - await API.deleteView(view.name) - } + await API.deleteView(view.name) // Update tables tables.update(state => { @@ -40,20 +39,6 @@ export function createViewsStore() { }) } - const create = async view => { - const savedViewResponse = await API.viewV2.create(view) - const savedView = savedViewResponse.data - - // Update tables - tables.update(state => { - const table = state.list.find(table => table._id === view.tableId) - table.views[view.name] = savedView - return { ...state } - }) - - return savedView - } - const save = async view => { const savedView = await API.saveView(view) @@ -74,7 +59,6 @@ export function createViewsStore() { subscribe: derivedStore.subscribe, select, delete: deleteView, - create, save, } } diff --git a/packages/builder/src/stores/backend/viewsV2.js b/packages/builder/src/stores/backend/viewsV2.js new file mode 100644 index 0000000000..8b7b1d876c --- /dev/null +++ b/packages/builder/src/stores/backend/viewsV2.js @@ -0,0 +1,85 @@ +import { writable, derived } from "svelte/store" +import { tables } from "./" +import { API } from "api" + +export function createViewsV2Store() { + const store = writable({ + selectedViewId: null, + }) + const derivedStore = derived([store, tables], ([$store, $tables]) => { + let list = [] + $tables.list?.forEach(table => { + const views = Object.values(table?.views || {}).filter(view => { + return view.version === 2 + }) + list = list.concat(views) + }) + return { + ...$store, + list, + selected: list.find(view => view.id === $store.selectedViewId), + } + }) + + const select = id => { + store.update(state => ({ + ...state, + selectedViewId: id, + })) + } + + const deleteView = async view => { + await API.viewV2.delete(view.id) + + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + delete table.views[view.name] + return { ...state } + }) + } + + const create = async view => { + const savedViewResponse = await API.viewV2.create(view) + const savedView = savedViewResponse.data + + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + table.views[view.name] = savedView + return { ...state } + }) + + return savedView + } + + const save = async view => { + // No dedicated save endpoint at this time + // const savedView = await API.saveView(view) + // + // // Update tables + // tables.update(state => { + // const table = state.list.find(table => table._id === view.tableId) + // if (table) { + // if (view.originalName) { + // delete table.views[view.originalName] + // } + // table.views[view.name] = savedView + // } + // return { ...state } + // }) + } + + const replace = (id, view) => {} + + return { + subscribe: derivedStore.subscribe, + select, + delete: deleteView, + create, + save, + replace, + } +} + +export const viewsV2 = createViewsV2Store() diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index d92851719f..556e6ca16e 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -28,8 +28,7 @@ } from "../lib/constants" export let API = null - export let tableId = null - export let datasourceType = null + export let datasource = null export let schemaOverrides = null export let columnWhitelist = null export let allowAddRows = true @@ -77,8 +76,7 @@ // Keep config store up to date with props $: config.set({ - tableId, - datasourceType, + datasource, schemaOverrides, columnWhitelist, allowAddRows, diff --git a/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte b/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte index f39e679da4..d964931682 100644 --- a/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte +++ b/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte @@ -12,7 +12,7 @@ width, config, hasNonAutoColumn, - tableId, + datasource, loading, } = getContext("grid") @@ -33,7 +33,7 @@
{#if $config.allowSchemaChanges} - {#key $tableId} + {#key $datasource} { - const { rows, tableId, users, focusedCellId, table, API } = context + const { rows, datasource, users, focusedCellId, table, API } = context const socket = createWebsocket("/socket/grid") - const connectToTable = tableId => { + const connectToDatasource = datasource => { if (!socket.connected) { return } // Identify which table we are editing const appId = API.getAppID() socket.emit( - GridSocketEvent.SelectTable, - { tableId, appId }, + GridSocketEvent.SelectDatasource, + { datasource, appId }, ({ users: gridUsers }) => { users.set(gridUsers) } @@ -23,7 +23,7 @@ export const createGridWebsocket = context => { // Built-in events socket.on("connect", () => { - connectToTable(get(tableId)) + connectToDatasource(get(datasource)) }) socket.on("connect_error", err => { console.log("Failed to connect to grid websocket:", err.message) @@ -57,7 +57,7 @@ export const createGridWebsocket = context => { }) // Change websocket connection when table changes - tableId.subscribe(connectToTable) + datasource.subscribe(connectToDatasource) // Notify selected cell changes focusedCellId.subscribe($focusedCellId => { diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index fc9decc1ef..8aa6b818e2 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -6,7 +6,7 @@ export const createStores = context => { const getProp = prop => derivedMemo(config, $config => $config[prop]) // Derive and memoize some props so that we can react to them in isolation - const tableId = getProp("tableId") + const datasource = getProp("datasource") const initialSortColumn = getProp("initialSortColumn") const initialSortOrder = getProp("initialSortOrder") const initialFilter = getProp("initialFilter") @@ -18,7 +18,7 @@ export const createStores = context => { return { config, - tableId, + datasource, initialSortColumn, initialSortOrder, initialFilter, diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index dccbc774a4..1346d7c8b2 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -59,7 +59,7 @@ export const deriveStores = context => { filter, loading, sort, - tableId, + datasource, API, scroll, validation, @@ -71,7 +71,7 @@ export const deriveStores = context => { hasNextPage, error, notifications, - props, + config, } = context const instanceLoaded = writable(false) const fetch = writable(null) @@ -95,7 +95,7 @@ export const deriveStores = context => { // Reset everything when table ID changes let unsubscribe = null let lastResetKey = null - tableId.subscribe(async $tableId => { + datasource.subscribe(async $datasource => { // Unsub from previous fetch if one exists unsubscribe?.() fetch.set(null) @@ -108,21 +108,6 @@ export const deriveStores = context => { const $filter = get(filter) const $sort = get(sort) - let datasource - if (props.datasourceType === "viewV2") { - const tableId = $tableId - datasource = { - type: props.datasourceType, - id: $tableId, - tableId: tableId.split("_").slice(0, -1).join("_"), - } - } else { - datasource = { - type: props.datasourceType, - tableId: $tableId, - } - } - // Create new fetch model const newFetch = fetchData({ API, From b0af0a287e897753148166c2d2fda8ed491cb325 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 26 Jul 2023 14:53:51 +0200 Subject: [PATCH 0046/1404] Remove view tests --- .../server/src/api/routes/tests/row.spec.ts | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index b153627992..dbc417a5b5 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -440,62 +440,6 @@ describe("/rows", () => { await assertRowUsage(rowUsage) await assertQueryUsage(queryUsage) }) - - describe("view row", () => { - it("should update only the fields that are supplied", async () => { - const existing = await config.createRow() - const view = await config.api.viewV2.create({ - columns: { name: { visible: true } }, - }) - const searchResponse = await config.api.viewV2.search(view.id) - - const [row] = searchResponse.body.rows as Row[] - - const res = await config.api.row.patch(table._id!, { - ...(row as PatchRowRequest), - name: "Updated Name", - description: "Updated Description", - }) - - const savedRow = await loadRow(res.body._id, table._id!) - - expect(savedRow.body).toEqual({ - ...existing, - name: "Updated Name", - description: "Updated Description", - _rev: expect.anything(), - createdAt: expect.anything(), - updatedAt: expect.anything(), - }) - }) - - it("should not edit fields that don't belong to the view", async () => { - const existing = await config.createRow() - const view = await config.api.viewV2.create({ - columns: { name: { visible: true } }, - }) - const searchResponse = await config.api.viewV2.search(view.id) - - const [row] = searchResponse.body.rows as Row[] - - const res = await config.api.row.patch(table._id!, { - ...(row as PatchRowRequest), - name: "Updated Name", - description: "Updated Description", - }) - - const savedRow = await loadRow(res.body._id, table._id!) - - expect(savedRow.body).toEqual({ - ...existing, - name: "Updated Name", - _rev: expect.anything(), - createdAt: expect.anything(), - updatedAt: expect.anything(), - }) - expect(savedRow.body.description).not.toEqual("Updated Description") - }) - }) }) describe("destroy", () => { From 508e30edae0810bdb8ae1dccf12a599ec8c73dd7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 26 Jul 2023 14:54:59 +0200 Subject: [PATCH 0047/1404] Fix sdk refs --- packages/server/src/api/controllers/row/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 92771411b5..4cbf17d844 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -295,7 +295,7 @@ export async function validate(ctx: Ctx) { if (isExternalTable(tableId)) { ctx.body = { valid: true } } else { - ctx.body = await utils.validate({ + ctx.body = await sdk.rows.utils.validate({ row: ctx.request.body, tableId, }) From 352b7ebe1ccb2ac84b95294309ed4ea28d8a9354 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 26 Jul 2023 14:07:07 +0100 Subject: [PATCH 0048/1404] Add dedicated route for routes v1, improve view creation modal, fix selection state --- .../DataTable/modals/CreateViewModal.svelte | 19 ++++++----- .../TableNavigator/TableNavigator.svelte | 32 ++++++++++--------- .../view/{ => v1}/[viewName]/_layout.svelte | 2 +- .../view/{ => v1}/[viewName]/index.svelte | 0 .../[application]/data/view/v1/index.svelte | 5 +++ .../view/v2/{[id] => [viewId]}/_layout.svelte | 2 +- .../view/v2/{[id] => [viewId]}/index.svelte | 0 .../[application]/data/view/v2/index.svelte | 5 +++ .../src/components/grid/stores/rows.js | 2 +- 9 files changed, 41 insertions(+), 26 deletions(-) rename packages/builder/src/pages/builder/app/[application]/data/view/{ => v1}/[viewName]/_layout.svelte (95%) rename packages/builder/src/pages/builder/app/[application]/data/view/{ => v1}/[viewName]/index.svelte (100%) create mode 100644 packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte rename packages/builder/src/pages/builder/app/[application]/data/view/v2/{[id] => [viewId]}/_layout.svelte (96%) rename packages/builder/src/pages/builder/app/[application]/data/view/v2/{[id] => [viewId]}/index.svelte (100%) create mode 100644 packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte index 6a7f5b96a1..ac5e522923 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte @@ -1,22 +1,19 @@ {#if $database?._id} @@ -37,28 +45,22 @@ {/if} - {#each [...Object.entries(table.views || {})].sort() as [viewName, view], idx (idx)} - {@const viewSelected = - $isActive("./view") && $views.selected?.name === viewName} - {@const viewV2Selected = - $isActive("./view/v2") && $viewsV2.selected?.name === viewName} + {#each [...Object.entries(table.views || {})].sort() as [name, view], idx (idx)} { if (view.version === 2) { $goto(`./view/v2/${view.id}`) } else { - $goto(`./view/${encodeURIComponent(viewName)}`) + $goto(`./view/v1/${encodeURIComponent(name)}`) } }} - selectedBy={$userSelectedResourceMap[viewName]} + selectedBy={$userSelectedResourceMap[name]} > - + {/each} {/each} diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/_layout.svelte similarity index 95% rename from packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/_layout.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/_layout.svelte index f3793317e8..7f4fc9c597 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/_layout.svelte @@ -13,7 +13,7 @@ stateKey: "selectedViewName", validate: name => $views.list?.some(view => view.name === name), update: views.select, - fallbackUrl: "../", + fallbackUrl: "../../", store: views, routify, decode: decodeURIComponent, diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/index.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/index.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/index.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte new file mode 100644 index 0000000000..c11ca87023 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/_layout.svelte similarity index 96% rename from packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/_layout.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/_layout.svelte index 62844a54df..8ddd6adbd0 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/_layout.svelte @@ -9,7 +9,7 @@ $: store.actions.websocket.selectResource(id) const stopSyncing = syncURLToState({ - urlParam: "id", + urlParam: "viewId", stateKey: "selectedViewId", validate: id => $viewsV2.list?.some(view => view.id === id), update: viewsV2.select, diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/index.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/index.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/index.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte new file mode 100644 index 0000000000..c11ca87023 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 1346d7c8b2..5d3cd20109 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -111,7 +111,7 @@ export const deriveStores = context => { // Create new fetch model const newFetch = fetchData({ API, - datasource, + datasource: $datasource, options: { filter: $filter, sortColumn: $sort.column, From 2d3da0dfcf6213e4b2412c625074535d27ffc8c3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 26 Jul 2023 14:26:34 +0100 Subject: [PATCH 0049/1404] Fix null issues in view fetch, fix edit view popover, improve handling of nullish view params --- .../backend/DataTable/ViewV2DataTable.svelte | 3 +- .../TableNavigator/TableNavigator.svelte | 2 +- .../popovers/EditViewPopover.svelte | 33 ++++++++++--------- packages/builder/src/helpers/urlStateSync.js | 2 +- .../app/[application]/data/view/index.svelte | 15 +++++---- .../src/components/grid/stores/rows.js | 5 +++ .../frontend-core/src/fetch/ViewV2Fetch.js | 6 ++-- 7 files changed, 39 insertions(+), 27 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index 6d549147fb..028030bb9a 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -1,5 +1,5 @@ + + diff --git a/packages/client/src/components/app/charts/index.js b/packages/client/src/components/app/charts/index.js index b1fea9ef0d..e428b4eecf 100644 --- a/packages/client/src/components/app/charts/index.js +++ b/packages/client/src/components/app/charts/index.js @@ -4,3 +4,4 @@ export { default as pie } from "./PieChart.svelte" export { default as donut } from "./DonutChart.svelte" export { default as area } from "./AreaChart.svelte" export { default as candlestick } from "./CandleStickChart.svelte" +export { default as histogram } from "./HistogramChart.svelte" From 822b2c728313c04b97ddc32e2327c9d2e517fecf Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 27 Jul 2023 12:56:54 +0000 Subject: [PATCH 0063/1404] Bump version to 2.8.28 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index b55764ceb9..b9545165d8 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.27", + "version": "2.8.28", "npmClient": "yarn", "packages": [ "packages/*" From deb4092cd38afb49da9c301d7f25fc15f2810a7b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 27 Jul 2023 14:17:26 +0100 Subject: [PATCH 0064/1404] Refactor grid stores and how config is handled --- .../backend/DataTable/TableDataTable.svelte | 10 ++- packages/frontend-core/src/api/viewsV2.js | 13 +++- .../src/components/grid/cells/DataCell.svelte | 2 +- .../components/grid/cells/GutterCell.svelte | 8 +-- .../components/grid/cells/HeaderCell.svelte | 4 +- .../src/components/grid/layout/Grid.svelte | 15 +++-- .../components/grid/layout/GridBody.svelte | 4 +- .../components/grid/layout/HeaderRow.svelte | 2 +- .../src/components/grid/layout/NewRow.svelte | 4 +- .../grid/layout/StickyColumn.svelte | 4 +- .../grid/overlays/KeyboardManager.svelte | 11 ++-- .../grid/overlays/MenuOverlay.svelte | 9 +-- .../src/components/grid/stores/clipboard.js | 2 +- .../src/components/grid/stores/columns.js | 63 ++++++++++--------- .../src/components/grid/stores/config.js | 47 ++++++++++++-- .../src/components/grid/stores/filter.js | 4 +- .../src/components/grid/stores/index.js | 7 ++- .../src/components/grid/stores/menu.js | 2 +- .../src/components/grid/stores/reorder.js | 2 +- .../src/components/grid/stores/resize.js | 2 +- .../src/components/grid/stores/rows.js | 38 ++++++----- .../src/components/grid/stores/sort.js | 7 ++- .../src/components/grid/stores/ui.js | 52 ++++++--------- .../src/components/grid/stores/users.js | 9 ++- .../src/components/grid/stores/viewport.js | 8 +-- 25 files changed, 193 insertions(+), 136 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index bd8ee258fd..7ef779d300 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -26,16 +26,14 @@ $: id = $tables.selected?._id $: isUsersTable = id === TableNames.USERS $: isInternal = $tables.selected?.type !== "external" - $: datasource = { + $: gridDatasource = { type: "table", tableId: id, } - - $: datasource = $datasources.list.find(datasource => { + $: tableDatasource = $datasources.list.find(datasource => { return datasource._id === $tables.selected?.sourceId }) - - $: relationshipsEnabled = relationshipSupport(datasource) + $: relationshipsEnabled = relationshipSupport(tableDatasource) const relationshipSupport = datasource => { const integration = $integrations[datasource?.source] @@ -58,7 +56,7 @@
({ /** * Create a new view - * @param tableId the id of the table where the view will be created * @param view the view object */ create: async view => { @@ -10,9 +9,18 @@ export const buildViewV2Endpoints = API => ({ body: view, }) }, + /** + * Updates a view + * @param view the view object + */ + update: async view => { + return await API.put({ + url: `/api/v2/views/${view.id}`, + body: view, + }) + }, /** * Fetches all rows in a view - * @param tableId the id of the table * @param viewId the id of the view */ fetch: async viewId => { @@ -20,7 +28,6 @@ export const buildViewV2Endpoints = API => ({ }, /** * Delete a view - * @param tableId the id of the table * @param viewId the id of the view */ delete: async viewId => { diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index cb8616a735..3a2a5f957f 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -34,7 +34,7 @@ column.schema.autocolumn || column.schema.disabled || column.schema.type === "formula" || - (!$config.allowEditRows && row._id) + (!$config.canEditRows && row._id) // Register this cell API if the row is focused $: { diff --git a/packages/frontend-core/src/components/grid/cells/GutterCell.svelte b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte index dd11066b98..5357d4b5cf 100644 --- a/packages/frontend-core/src/components/grid/cells/GutterCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte @@ -40,7 +40,7 @@
@@ -48,14 +48,14 @@ {#if !disableNumber}
{row.__idx + 1}
{/if} {/if} - {#if rowSelected && $config.allowDeleteRows} + {#if rowSelected && $config.canDeleteRows}
dispatch("request-bulk-delete")}>
{:else} -
+
Edit column @@ -175,7 +175,7 @@ icon="Label" on:click={makeDisplayColumn} disabled={idx === "sticky" || - !$config.allowSchemaChanges || + !$config.canEditPrimaryDisplay || bannedDisplayColumnTypes.includes(column.schema.type)} > Use as display column diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index 556e6ca16e..ea7cd73a4b 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -1,5 +1,6 @@ - + diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 80e4e6039d..23eae75428 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -482,9 +482,14 @@ export const createActions = context => { } // Refreshes the schema of the data fetch subscription - const refreshTableDefinition = async () => { - const definition = await API.fetchTableDefinition(get(tableId)) - table.set(definition) + const refreshDatasourceDefinition = async () => { + const $datasource = get(datasource) + if ($datasource.type === "table") { + table.set(await API.fetchTableDefinition($datasource.tableId)) + } else if ($datasource.type === "viewV2") { + // const definition = await API.viewsV2.(get(tableId)) + // table.set(definition) + } } // Checks if we have a row with a certain ID @@ -520,7 +525,7 @@ export const createActions = context => { refreshRow, replaceRow, refreshData, - refreshTableDefinition, + refreshDatasourceDefinition, }, }, } From 9665ec34dd7de7dadc0cc49cf8fced0f88568950 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 27 Jul 2023 15:53:50 +0100 Subject: [PATCH 0066/1404] Adjust grid props and config --- .../src/components/grid/layout/Grid.svelte | 24 +++++++++---------- .../src/components/grid/stores/config.js | 16 +++---------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index ea7cd73a4b..343419af58 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -32,12 +32,12 @@ export let datasource = null export let schemaOverrides = null export let columnWhitelist = null - export let allowAddRows = true - export let allowExpandRows = true - export let allowEditRows = true - export let allowDeleteRows = true - export let allowEditColumns = true - export let allowSchemaChanges = true + export let canAddRows = true + export let canExpandRows = true + export let canEditRows = true + export let canDeleteRows = true + export let canEditColumns = true + export let canSaveSchema = true export let stripeRows = false export let collaboration = true export let showAvatars = true @@ -83,12 +83,12 @@ datasource, schemaOverrides, columnWhitelist, - allowAddRows, - allowExpandRows, - allowEditRows, - allowDeleteRows, - allowEditColumns, - allowSchemaChanges, + canAddRows, + canExpandRows, + canEditRows, + canDeleteRows, + canEditColumns, + canSaveSchema, stripeRows, collaboration, showAvatars, diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index 82ec8e29f3..0ed04bf741 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -21,16 +21,10 @@ export const deriveStores = context => { [props, hasNonAutoColumn], ([$props, $hasNonAutoColumn]) => { let config = { - // Row features - canAddRows: $props.allowAddRows, - canExpandRows: $props.allowExpandRows, - canEditRows: $props.allowEditRows, - canDeleteRows: $props.allowDeleteRows, + ...$props, - // Column features - canEditColumns: $props.allowEditColumns, - canEditPrimaryDisplay: $props.allowEditColumns, - canSaveSchema: $props.allowSchemaChanges, + // Additional granular features which we don't expose as props + canEditPrimaryDisplay: $props.canEditColumns, } // Disable some features if we're editing a view @@ -42,15 +36,11 @@ export const deriveStores = context => { config.canAddRows = false } - console.log($hasNonAutoColumn) - // Disable adding rows if we don't have any valid columns if (!$hasNonAutoColumn) { config.canAddRows = false } - console.log(config) - return config } ) From aef9ff06af6634479ec73e2bf036dbafd90fa7dc Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 27 Jul 2023 16:09:38 +0100 Subject: [PATCH 0067/1404] Disable component validation for old app imports --- packages/server/src/api/controllers/application.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 53453b8538..8a871e28cf 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -331,6 +331,11 @@ async function performAppCreate(ctx: UserCtx) { } }) + // Keep existing validation setting + if (!existing.features?.componentValidation) { + newApplication.features!.componentValidation = false + } + // Migrate navigation settings and screens if required if (existing) { const navigation = await migrateAppNavigation() From 8406245a5cfd2ceafd2f49a3a8d930f4e585f0bc Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 27 Jul 2023 15:25:45 +0000 Subject: [PATCH 0068/1404] Bump version to 2.8.29-alpha.0 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index b9545165d8..54ba89443c 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.28", + "version": "2.8.29-alpha.0", "npmClient": "yarn", "packages": [ "packages/*" From 6795cb15e8b4abd9c11589cbe810d53e3bdb31f0 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 27 Jul 2023 15:43:30 +0000 Subject: [PATCH 0069/1404] Bump version to 2.8.29-alpha.1 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 54ba89443c..252d319ab9 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.29-alpha.0", + "version": "2.8.29-alpha.1", "npmClient": "yarn", "packages": [ "packages/*" From d8f50f139e44cbfb2ceb170dfc1ec4245e9afc60 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 27 Jul 2023 17:52:56 +0100 Subject: [PATCH 0070/1404] Frontend update for app builders, handling when in the builder portal and don't have any app access, as well as allowing viewing of apps from the portal. --- .../src/components/start/AppRow.svelte | 36 +++- .../_components/BuilderSidePanel.svelte | 12 +- .../src/pages/builder/portal/_layout.svelte | 2 +- .../pages/builder/portal/apps/_layout.svelte | 14 +- .../pages/builder/portal/apps/index.svelte | 199 ++++++++++-------- .../src/sdk/app/applications/applications.ts | 6 +- .../src/api/routes/global/tests/auth.spec.ts | 2 +- 7 files changed, 155 insertions(+), 116 deletions(-) diff --git a/packages/builder/src/components/start/AppRow.svelte b/packages/builder/src/components/start/AppRow.svelte index 2e7719987d..212ab4c6f8 100644 --- a/packages/builder/src/components/start/AppRow.svelte +++ b/packages/builder/src/components/start/AppRow.svelte @@ -1,16 +1,21 @@
@@ -39,7 +48,7 @@
- {#if editing} + {#if editing && isBuilder} Currently editing {:else if app.updatedAt} @@ -56,14 +65,21 @@ {app.deployed ? "Published" : "Unpublished"}
-
- - -
+ {#if isBuilder} +
+ + +
+ {:else} + +
+ +
+ {/if}
diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 9f951a6a7e..6706bf7a8b 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -21,6 +21,7 @@ export let offset = 5 export let customHeight export let animate = true + export let customZindex $: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum" @@ -77,8 +78,9 @@ }} on:keydown={handleEscape} class="spectrum-Popover is-open" + class:customZindex role="presentation" - style="height: {customHeight}" + style="height: {customHeight}; --customZindex: {customZindex};" transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }} > @@ -92,4 +94,8 @@ border-color: var(--spectrum-global-color-gray-300); overflow: auto; } + + .customZindex { + z-index: var(--customZindex) !important; + } diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index 97762d2b3a..cda6b5acbf 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -84,7 +84,7 @@ export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte export { default as Slider } from "./Form/Slider.svelte" export { default as Accordion } from "./Accordion/Accordion.svelte" export { default as File } from "./Form/File.svelte" - +export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte" // Renderers export { default as BoldRenderer } from "./Table/BoldRenderer.svelte" export { default as CodeRenderer } from "./Table/CodeRenderer.svelte" diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index d4c994dae5..38eb87aa73 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -64,6 +64,13 @@ + + + + + + + {#if isInternal} @@ -77,9 +84,8 @@ {:else} {/if} + - - {#if isUsersTable} {:else} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 4761ccee02..7c3e13f39a 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -7,12 +7,12 @@ Toggle, RadioGroup, DatePicker, - ModalContent, - Context, Modal, notifications, + OptionSelectDnD, + Layout, } from "@budibase/bbui" - import { createEventDispatcher } from "svelte" + import { createEventDispatcher, getContext } from "svelte" import { cloneDeep } from "lodash/fp" import { tables, datasources } from "stores/backend" import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" @@ -26,12 +26,10 @@ SWITCHABLE_TYPES, } from "constants/backend" import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils" - import ValuesList from "components/common/ValuesList.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte" import { truncate } from "lodash" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import { getBindings } from "components/backend/DataTable/formula" - import { getContext } from "svelte" import JSONSchemaModal from "./JSONSchemaModal.svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" @@ -45,11 +43,11 @@ const dispatch = createEventDispatcher() const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] - const { hide } = getContext(Context.Modal) - let fieldDefinitions = cloneDeep(FIELDS) + const { dispatch: gridDispatch } = getContext("grid") export let field + let fieldDefinitions = cloneDeep(FIELDS) let originalName let linkEditDisabled let primaryDisplay @@ -61,11 +59,10 @@ let savingColumn let deleteColName let jsonSchemaModal - + let allowedTypes = [] let editableColumn = { type: "string", constraints: fieldDefinitions.STRING.constraints, - // Initial value for column name in other table for linked records fieldName: $tables.selected.name, } @@ -83,7 +80,23 @@ primaryDisplay = $tables.selected.primaryDisplay == null || $tables.selected.primaryDisplay === editableColumn.name + } else if (!savingColumn) { + let highestNumber = 0 + Object.keys(table.schema).forEach(columnName => { + const columnNumber = extractColumnNumber(columnName) + if (columnNumber > highestNumber) { + highestNumber = columnNumber + } + return highestNumber + }) + + if (highestNumber >= 1) { + editableColumn.name = `Column 0${highestNumber + 1}` + } else { + editableColumn.name = "Column 01" + } } + allowedTypes = getAllowedTypes() } $: initialiseField(field, savingColumn) @@ -182,6 +195,8 @@ indexes, }) dispatch("updatecolumns") + gridDispatch("close-edit-column") + if ( saveColumn.type === LINK_TYPE && saveColumn.relationshipType === RelationshipType.MANY_TO_MANY @@ -203,6 +218,7 @@ function cancelEdit() { editableColumn.name = originalName + gridDispatch("close-edit-column") } async function deleteColumn() { @@ -214,8 +230,8 @@ await tables.deleteField(editableColumn) notifications.success(`Column ${editableColumn.name} deleted`) confirmDeleteDialog.hide() - hide() dispatch("updatecolumns") + gridDispatch("close-edit-column") } } catch (error) { notifications.error(`Error deleting column: ${error.message}`) @@ -251,14 +267,6 @@ required = req } - function onChangePrimaryDisplay(e) { - const isPrimary = e.detail - // primary display is always required - if (isPrimary) { - editableColumn.constraints.presence = { allowEmpty: false } - } - } - function openJsonSchemaEditor() { jsonSchemaModal.show() } @@ -272,6 +280,11 @@ deleteColName = "" } + function extractColumnNumber(columnName) { + const match = columnName.match(/Column (\d+)/) + return match ? parseInt(match[1]) : 0 + } + function getRelationshipOptions(field) { if (!field || !field.tableId) { return null @@ -402,15 +415,8 @@ } - + field.name} getOptionValue={field => field.type} + getOptionIcon={field => field.icon} isOptionEnabled={option => { if (option.type == AUTO_TYPE) { return availableAutoColumnKeys?.length > 0 @@ -433,28 +439,6 @@ }} /> - {#if canBeRequired || canBeDisplay} -
- {#if canBeRequired} - - {/if} - {#if canBeDisplay} - - {/if} -
- {/if} - {#if editableColumn.type === "string"} {:else if editableColumn.type === "options"} - {:else if editableColumn.type === "longform"}
@@ -480,19 +464,28 @@ />
{:else if editableColumn.type === "array"} - {:else if editableColumn.type === "datetime" && !editableColumn.autocolumn} - - +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}