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.

This commit is contained in:
mike12345567 2023-07-27 17:52:56 +01:00
parent 6176898ae3
commit d8f50f139e
7 changed files with 155 additions and 116 deletions

View File

@ -1,16 +1,21 @@
<script>
import { Heading, Body, Button, Icon } from "@budibase/bbui"
import { processStringSync } from "@budibase/string-templates"
import { auth } from "stores/portal"
import { goto } from "@roxi/routify"
import { UserAvatars } from "@budibase/frontend-core"
import { sdk } from "@budibase/shared-core"
export let app
export let lockedAction
$: editing = app.sessions?.length
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
const handleDefaultClick = () => {
if (window.innerWidth < 640) {
if (!isBuilder) {
goToApp()
} else if (window.innerWidth < 640) {
goToOverview()
} else {
goToBuilder()
@ -24,6 +29,10 @@
const goToOverview = () => {
$goto(`../../app/${app.devId}/settings`)
}
const goToApp = () => {
window.open(`/app/${app.name}`, "_blank")
}
</script>
<div class="app-row" on:click={lockedAction || handleDefaultClick}>
@ -39,7 +48,7 @@
</div>
<div class="updated">
{#if editing}
{#if editing && isBuilder}
Currently editing
<UserAvatars users={app.sessions} />
{:else if app.updatedAt}
@ -56,14 +65,21 @@
<Body size="S">{app.deployed ? "Published" : "Unpublished"}</Body>
</div>
<div class="app-row-actions">
<Button size="S" secondary on:click={lockedAction || goToOverview}>
Manage
</Button>
<Button size="S" primary on:click={lockedAction || goToBuilder}>
Edit
</Button>
</div>
{#if isBuilder}
<div class="app-row-actions">
<Button size="S" secondary on:click={lockedAction || goToOverview}>
Manage
</Button>
<Button size="S" primary on:click={lockedAction || goToBuilder}>
Edit
</Button>
</div>
{:else}
<!-- this can happen if an app builder has app user access to an app -->
<div class="app-row-actions">
<Button size="S" secondary>View</Button>
</div>
{/if}
</div>
<style>

View File

@ -108,9 +108,9 @@
await usersFetch.refresh()
filteredUsers = $usersFetch.rows.map(user => {
const isBuilderOrAdmin = sdk.users.isBuilderOrAdmin(user, prodAppId)
const isAdminOrBuilder = sdk.users.isAdminOrBuilder(user, prodAppId)
let role = undefined
if (isBuilderOrAdmin) {
if (isAdminOrBuilder) {
role = Constants.Roles.ADMIN
} else {
const appRole = Object.keys(user.roles).find(x => x === prodAppId)
@ -122,7 +122,7 @@
return {
...user,
role,
isBuilderOrAdmin,
isAdminOrBuilder,
}
})
}
@ -258,7 +258,7 @@
}
// Must exclude users who have explicit privileges
const userByEmail = filteredUsers.reduce((acc, user) => {
if (user.role || sdk.users.isBuilderOrAdmin(user, prodAppId)) {
if (user.role || sdk.users.isAdminOrBuilder(user, prodAppId)) {
acc.push(user.email)
}
return acc
@ -403,7 +403,7 @@
const role = $roles.find(role => role._id === user.role)
return `This user has been given ${role?.name} access from the ${user.group} group`
}
if (user.isBuilderOrAdmin) {
if (user.isAdminOrBuilder) {
return "This user's role grants admin access to all apps"
}
return null
@ -614,7 +614,7 @@
}}
autoWidth
align="right"
allowedRoles={user.isBuilderOrAdmin
allowedRoles={user.isAdminOrBuilder
? [Constants.Roles.ADMIN]
: null}
/>

View File

@ -15,7 +15,7 @@
let activeTab = "Apps"
$: $url(), updateActiveTab($menu)
$: fullscreen = !$apps.length
$: fullscreen = $apps.length == null
const updateActiveTab = menu => {
for (let entry of menu) {

View File

@ -1,11 +1,19 @@
<script>
import { notifications } from "@budibase/bbui"
import { admin, apps, templates, licensing, groups } from "stores/portal"
import {
admin,
apps,
templates,
licensing,
groups,
auth,
} from "stores/portal"
import { onMount } from "svelte"
import { redirect } from "@roxi/routify"
import { sdk } from "@budibase/shared-core"
// Don't block loading if we've already hydrated state
let loaded = $apps.length > 0
let loaded = $apps.length != null
onMount(async () => {
try {
@ -25,7 +33,7 @@
}
// Go to new app page if no apps exists
if (!$apps.length) {
if (!$apps.length && sdk.users.isGlobalBuilder($auth.user)) {
$redirect("./onboarding")
}
} catch (error) {

View File

@ -204,106 +204,109 @@
})
</script>
{#if $apps.length}
<Page>
<Layout noPadding gap="L">
{#each Object.keys(automationErrors || {}) as appId}
<Notification
wide
dismissable
action={() => goToAutomationError(appId)}
type="error"
icon="Alert"
actionMessage={errorCount(automationErrors[appId]) > 1
? "View errors"
: "View error"}
on:dismiss={async () => {
await automationStore.actions.clearLogErrors({ appId })
await apps.load()
}}
message={automationErrorMessage(appId)}
/>
{/each}
<div class="title">
<div class="welcome">
<Layout noPadding gap="XS">
<Heading size="L">{welcomeHeader}</Heading>
<Body size="M">
Manage your apps and get a head start with templates
</Body>
</Layout>
</div>
<Page>
<Layout noPadding gap="L">
{#each Object.keys(automationErrors || {}) as appId}
<Notification
wide
dismissable
action={() => goToAutomationError(appId)}
type="error"
icon="Alert"
actionMessage={errorCount(automationErrors[appId]) > 1
? "View errors"
: "View error"}
on:dismiss={async () => {
await automationStore.actions.clearLogErrors({ appId })
await apps.load()
}}
message={automationErrorMessage(appId)}
/>
{/each}
<div class="title">
<div class="welcome">
<Layout noPadding gap="XS">
<Heading size="L">{welcomeHeader}</Heading>
<Body size="M">
Below you'll find the list of apps that you have access to
</Body>
</Layout>
</div>
</div>
{#if enrichedApps.length}
<Layout noPadding gap="L">
<div class="title">
{#if $auth.user && sdk.users.isGlobalBuilder($auth.user)}
<div class="buttons">
{#if enrichedApps.length}
<Layout noPadding gap="L">
<div class="title">
{#if $auth.user && sdk.users.isGlobalBuilder($auth.user)}
<div class="buttons">
<Button
size="M"
cta
on:click={usersLimitLockAction || initiateAppCreation}
>
Create new app
</Button>
{#if $apps?.length > 0 && !$admin.offlineMode}
<Button
size="M"
cta
on:click={usersLimitLockAction || initiateAppCreation}
secondary
on:click={usersLimitLockAction ||
$goto("/builder/portal/apps/templates")}
>
Create new app
View templates
</Button>
{#if $apps?.length > 0 && !$admin.offlineMode}
<Button
size="M"
secondary
on:click={usersLimitLockAction ||
$goto("/builder/portal/apps/templates")}
>
View templates
</Button>
{/if}
{#if !$apps?.length}
<Button
size="L"
quiet
secondary
on:click={usersLimitLockAction || initiateAppImport}
>
Import app
</Button>
{/if}
</div>
{/if}
{#if enrichedApps.length > 1}
<div class="app-actions">
<Select
autoWidth
bind:value={sortBy}
placeholder={null}
options={[
{ label: "Sort by name", value: "name" },
{ label: "Sort by recently updated", value: "updated" },
{ label: "Sort by status", value: "status" },
]}
/>
<Search placeholder="Search" bind:value={searchTerm} />
</div>
{/if}
</div>
<div class="app-table">
{#each filteredApps as app (app.appId)}
<AppRow {app} lockedAction={usersLimitLockAction} />
{/each}
</div>
</Layout>
{/if}
{#if creatingFromTemplate}
<div class="empty-wrapper">
<img class="img-logo img-size" alt="logo" src={Logo} />
<p>Creating your Budibase app from your selected template...</p>
<Spinner size="10" />
{/if}
{#if !$apps?.length}
<Button
size="L"
quiet
secondary
on:click={usersLimitLockAction || initiateAppImport}
>
Import app
</Button>
{/if}
</div>
{/if}
{#if enrichedApps.length > 1}
<div class="app-actions">
<Select
autoWidth
bind:value={sortBy}
placeholder={null}
options={[
{ label: "Sort by name", value: "name" },
{ label: "Sort by recently updated", value: "updated" },
{ label: "Sort by status", value: "status" },
]}
/>
<Search placeholder="Search" bind:value={searchTerm} />
</div>
{/if}
</div>
{/if}
</Layout>
</Page>
{/if}
<div class="app-table">
{#each filteredApps as app (app.appId)}
<AppRow {app} lockedAction={usersLimitLockAction} />
{/each}
</div>
</Layout>
{:else}
<div class="no-apps">
<img class="spaceman" alt="spaceman" src={Logo} width="100px" />
<Body weight="700">You haven't been given access to any apps yet</Body>
</div>
{/if}
{#if creatingFromTemplate}
<div class="empty-wrapper">
<img class="img-logo img-size" alt="logo" src={Logo} />
<p>Creating your Budibase app from your selected template...</p>
<Spinner size="10" />
</div>
{/if}
</Layout>
</Page>
<Modal
bind:this={creationModal}
@ -371,6 +374,16 @@
height: 160px;
}
.no-apps {
background-color: var(--spectrum-global-color-gray-100);
padding: calc(var(--spacing-xl) * 2);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: var(--spacing-xl);
}
@media (max-width: 1000px) {
.img-logo {
display: none;

View File

@ -7,10 +7,12 @@ import { db as dbCore, users } from "@budibase/backend-core"
export function filterAppList(user: ContextUser, apps: App[]) {
let appList: string[] = []
const roleApps = Object.keys(user.roles || {})
if (users.hasAppBuilderPermissions(user)) {
appList = user.builder?.apps!
appList = user.builder?.apps || []
appList = appList.concat(roleApps)
} else if (!users.isAdminOrBuilder(user)) {
appList = Object.keys(user.roles || {})
appList = roleApps
} else {
return apps
}

View File

@ -206,7 +206,7 @@ describe("/api/global/auth", () => {
const newPassword = "newpassword"
const res = await config.api.auth.updatePassword(code!, newPassword)
user = await config.getUser(user.email) as User
user = (await config.getUser(user.email)) as User
delete user.password
expect(res.body).toEqual({ message: "password reset successfully." })