Merge remote-tracking branch 'origin/develop' into bug/budi-5901-usage-quota-document-conflicts-can-cause
This commit is contained in:
commit
04566dbabd
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.1",
|
"@budibase/nano": "10.1.1",
|
||||||
"@budibase/pouchdb-replication-stream": "1.2.10",
|
"@budibase/pouchdb-replication-stream": "1.2.10",
|
||||||
"@budibase/types": "2.3.18-alpha.14",
|
"@budibase/types": "2.3.18-alpha.17",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-cloudfront-sign": "2.2.0",
|
"aws-cloudfront-sign": "2.2.0",
|
||||||
|
|
|
@ -154,11 +154,29 @@ export async function getGoogleConfig(): Promise<
|
||||||
GoogleInnerConfig | undefined
|
GoogleInnerConfig | undefined
|
||||||
> {
|
> {
|
||||||
const config = await getGoogleConfigDoc()
|
const config = await getGoogleConfigDoc()
|
||||||
if (config) {
|
return config?.config
|
||||||
return config.config
|
}
|
||||||
|
|
||||||
|
export async function getGoogleDatasourceConfig(): Promise<
|
||||||
|
GoogleInnerConfig | undefined
|
||||||
|
> {
|
||||||
|
if (!env.SELF_HOSTED) {
|
||||||
|
// always use the env vars in cloud
|
||||||
|
return getDefaultGoogleConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use google fallback configuration from env variables
|
// prefer the config in self-host
|
||||||
|
let config = await getGoogleConfig()
|
||||||
|
|
||||||
|
// fallback to env vars
|
||||||
|
if (!config || !config.activated) {
|
||||||
|
config = getDefaultGoogleConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultGoogleConfig(): GoogleInnerConfig | undefined {
|
||||||
if (environment.GOOGLE_CLIENT_ID && environment.GOOGLE_CLIENT_SECRET) {
|
if (environment.GOOGLE_CLIENT_ID && environment.GOOGLE_CLIENT_SECRET) {
|
||||||
return {
|
return {
|
||||||
clientID: environment.GOOGLE_CLIENT_ID!,
|
clientID: environment.GOOGLE_CLIENT_ID!,
|
||||||
|
|
|
@ -12,7 +12,8 @@ type Passport = {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchGoogleCreds() {
|
async function fetchGoogleCreds() {
|
||||||
const config = await configs.getGoogleConfig()
|
let config = await configs.getGoogleDatasourceConfig()
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error("No google configuration found")
|
throw new Error("No google configuration found")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
generateAppUserID,
|
generateAppUserID,
|
||||||
queryGlobalView,
|
queryGlobalView,
|
||||||
UNICODE_MAX,
|
UNICODE_MAX,
|
||||||
|
directCouchFind,
|
||||||
} from "./db"
|
} from "./db"
|
||||||
import { BulkDocsResponse, User } from "@budibase/types"
|
import { BulkDocsResponse, User } from "@budibase/types"
|
||||||
import { getGlobalDB } from "./context"
|
import { getGlobalDB } from "./context"
|
||||||
|
@ -101,6 +102,7 @@ export const searchGlobalUsersByApp = async (
|
||||||
})
|
})
|
||||||
params.startkey = opts && opts.startkey ? opts.startkey : params.startkey
|
params.startkey = opts && opts.startkey ? opts.startkey : params.startkey
|
||||||
let response = await queryGlobalView(ViewName.USER_BY_APP, params)
|
let response = await queryGlobalView(ViewName.USER_BY_APP, params)
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
response = []
|
response = []
|
||||||
}
|
}
|
||||||
|
@ -111,6 +113,45 @@ export const searchGlobalUsersByApp = async (
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return any user who potentially has access to the application
|
||||||
|
Admins, developers and app users with the explicitly role.
|
||||||
|
*/
|
||||||
|
export const searchGlobalUsersByAppAccess = async (appId: any, opts: any) => {
|
||||||
|
const roleSelector = `roles.${appId}`
|
||||||
|
|
||||||
|
let orQuery: any[] = [
|
||||||
|
{
|
||||||
|
"builder.global": true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"admin.global": true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (appId) {
|
||||||
|
const roleCheck = {
|
||||||
|
[roleSelector]: {
|
||||||
|
$exists: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
orQuery.push(roleCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchOptions = {
|
||||||
|
selector: {
|
||||||
|
$or: orQuery,
|
||||||
|
_id: {
|
||||||
|
$regex: "^us_",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
limit: opts?.limit || 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await directCouchFind(context.getGlobalDBName(), searchOptions)
|
||||||
|
return resp?.rows
|
||||||
|
}
|
||||||
|
|
||||||
export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
||||||
"@budibase/string-templates": "2.3.18-alpha.14",
|
"@budibase/string-templates": "2.3.18-alpha.17",
|
||||||
"@spectrum-css/accordion": "3.0.24",
|
"@spectrum-css/accordion": "3.0.24",
|
||||||
"@spectrum-css/actionbutton": "1.0.1",
|
"@spectrum-css/actionbutton": "1.0.1",
|
||||||
"@spectrum-css/actiongroup": "1.0.1",
|
"@spectrum-css/actiongroup": "1.0.1",
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/actionbutton/dist/index-vars.css"
|
import "@spectrum-css/actionbutton/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
@ -13,6 +16,9 @@
|
||||||
export let active = false
|
export let active = false
|
||||||
export let fullWidth = false
|
export let fullWidth = false
|
||||||
export let noPadding = false
|
export let noPadding = false
|
||||||
|
export let tooltip = ""
|
||||||
|
|
||||||
|
let showTooltip = false
|
||||||
|
|
||||||
function longPress(element) {
|
function longPress(element) {
|
||||||
if (!longPressable) return
|
if (!longPressable) return
|
||||||
|
@ -35,42 +41,54 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<span
|
||||||
use:longPress
|
class="btn-wrap"
|
||||||
class:spectrum-ActionButton--quiet={quiet}
|
on:mouseover={() => (showTooltip = true)}
|
||||||
class:spectrum-ActionButton--emphasized={emphasized}
|
on:mouseleave={() => (showTooltip = false)}
|
||||||
class:is-selected={selected}
|
on:focus={() => (showTooltip = true)}
|
||||||
class:noPadding
|
|
||||||
class:fullWidth
|
|
||||||
class="spectrum-ActionButton spectrum-ActionButton--size{size}"
|
|
||||||
class:active
|
|
||||||
{disabled}
|
|
||||||
on:longPress
|
|
||||||
on:click|preventDefault
|
|
||||||
>
|
>
|
||||||
{#if longPressable}
|
<button
|
||||||
<svg
|
use:longPress
|
||||||
class="spectrum-Icon spectrum-UIIcon-CornerTriangle100 spectrum-ActionButton-hold"
|
class:spectrum-ActionButton--quiet={quiet}
|
||||||
focusable="false"
|
class:spectrum-ActionButton--emphasized={emphasized}
|
||||||
aria-hidden="true"
|
class:is-selected={selected}
|
||||||
>
|
class:noPadding
|
||||||
<use xlink:href="#spectrum-css-icon-CornerTriangle100" />
|
class:fullWidth
|
||||||
</svg>
|
class="spectrum-ActionButton spectrum-ActionButton--size{size}"
|
||||||
{/if}
|
class:active
|
||||||
{#if icon}
|
{disabled}
|
||||||
<svg
|
on:longPress
|
||||||
class="spectrum-Icon spectrum-Icon--size{size}"
|
on:click|preventDefault
|
||||||
focusable="false"
|
>
|
||||||
aria-hidden="true"
|
{#if longPressable}
|
||||||
aria-label={icon}
|
<svg
|
||||||
>
|
class="spectrum-Icon spectrum-UIIcon-CornerTriangle100 spectrum-ActionButton-hold"
|
||||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
focusable="false"
|
||||||
</svg>
|
aria-hidden="true"
|
||||||
{/if}
|
>
|
||||||
{#if $$slots}
|
<use xlink:href="#spectrum-css-icon-CornerTriangle100" />
|
||||||
<span class="spectrum-ActionButton-label"><slot /></span>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
{#if icon}
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-Icon--size{size}"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label={icon}
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
{#if $$slots}
|
||||||
|
<span class="spectrum-ActionButton-label"><slot /></span>
|
||||||
|
{/if}
|
||||||
|
{#if tooltip && showTooltip}
|
||||||
|
<div class="tooltip" in:fade={{ duration: 130, delay: 250 }}>
|
||||||
|
<Tooltip textWrapping direction="bottom" text={tooltip} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.fullWidth {
|
.fullWidth {
|
||||||
|
@ -98,4 +116,14 @@
|
||||||
.is-selected:not(.emphasized) .spectrum-Icon {
|
.is-selected:not(.emphasized) .spectrum-Icon {
|
||||||
color: var(--spectrum-global-color-gray-900);
|
color: var(--spectrum-global-color-gray-900);
|
||||||
}
|
}
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
left: 50%;
|
||||||
|
top: calc(100% + 4px);
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 150px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
export let getOptionIcon = () => null
|
export let getOptionIcon = () => null
|
||||||
|
export let useOptionIconImage = false
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = () => null
|
||||||
export let open = false
|
export let open = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
|
@ -33,6 +34,9 @@
|
||||||
export let sort = false
|
export let sort = false
|
||||||
export let fetchTerm = null
|
export let fetchTerm = null
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
|
export let align = "left"
|
||||||
|
export let footer = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let searchTerm = null
|
let searchTerm = null
|
||||||
|
@ -131,7 +135,7 @@
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
anchor={button}
|
anchor={button}
|
||||||
align="left"
|
align={align || "left"}
|
||||||
bind:this={popover}
|
bind:this={popover}
|
||||||
{open}
|
{open}
|
||||||
on:close={() => (open = false)}
|
on:close={() => (open = false)}
|
||||||
|
@ -186,7 +190,16 @@
|
||||||
>
|
>
|
||||||
{#if getOptionIcon(option, idx)}
|
{#if getOptionIcon(option, idx)}
|
||||||
<span class="option-extra icon">
|
<span class="option-extra icon">
|
||||||
<Icon size="S" name={getOptionIcon(option, idx)} />
|
{#if useOptionIconImage}
|
||||||
|
<img
|
||||||
|
src={getOptionIcon(option, idx)}
|
||||||
|
alt="icon"
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<Icon size="S" name={getOptionIcon(option, idx)} />
|
||||||
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if getOptionColour(option, idx)}
|
{#if getOptionColour(option, idx)}
|
||||||
|
@ -208,6 +221,12 @@
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{#if footer}
|
||||||
|
<div class="footer">
|
||||||
|
{footer}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
@ -284,4 +303,11 @@
|
||||||
.popover-content :global(.spectrum-Search .spectrum-Textfield-icon) {
|
.popover-content :global(.spectrum-Search .spectrum-Textfield-icon) {
|
||||||
top: 9px;
|
top: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 4px 12px 12px 12px;
|
||||||
|
font-style: italic;
|
||||||
|
max-width: 170px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
export let getOptionIcon = () => null
|
export let getOptionIcon = () => null
|
||||||
|
export let useOptionIconImage = false
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = () => null
|
||||||
export let isOptionEnabled
|
export let isOptionEnabled
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
|
@ -18,6 +19,8 @@
|
||||||
export let autoWidth = false
|
export let autoWidth = false
|
||||||
export let autocomplete = false
|
export let autocomplete = false
|
||||||
export let sort = false
|
export let sort = false
|
||||||
|
export let align
|
||||||
|
export let footer = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -41,7 +44,7 @@
|
||||||
const getFieldText = (value, options, placeholder) => {
|
const getFieldText = (value, options, placeholder) => {
|
||||||
// Always use placeholder if no value
|
// Always use placeholder if no value
|
||||||
if (value == null || value === "") {
|
if (value == null || value === "") {
|
||||||
return placeholder || "Choose an option"
|
return placeholder !== false ? "Choose an option" : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return getFieldAttribute(getOptionLabel, value, options)
|
return getFieldAttribute(getOptionLabel, value, options)
|
||||||
|
@ -66,15 +69,18 @@
|
||||||
{fieldColour}
|
{fieldColour}
|
||||||
{options}
|
{options}
|
||||||
{autoWidth}
|
{autoWidth}
|
||||||
|
{align}
|
||||||
|
{footer}
|
||||||
{getOptionLabel}
|
{getOptionLabel}
|
||||||
{getOptionValue}
|
{getOptionValue}
|
||||||
{getOptionIcon}
|
{getOptionIcon}
|
||||||
|
{useOptionIconImage}
|
||||||
{getOptionColour}
|
{getOptionColour}
|
||||||
{isOptionEnabled}
|
{isOptionEnabled}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{sort}
|
{sort}
|
||||||
isPlaceholder={value == null || value === ""}
|
isPlaceholder={value == null || value === ""}
|
||||||
placeholderOption={placeholder}
|
placeholderOption={placeholder === false ? null : placeholder}
|
||||||
isOptionSelected={option => option === value}
|
isOptionSelected={option => option === value}
|
||||||
onSelectOption={selectOption}
|
onSelectOption={selectOption}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = option => extractProperty(option, "label")
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
export let getOptionValue = option => extractProperty(option, "value")
|
||||||
export let getOptionIcon = option => option?.icon
|
export let getOptionIcon = option => option?.icon
|
||||||
|
export let useOptionIconImage = false
|
||||||
export let getOptionColour = option => option?.colour
|
export let getOptionColour = option => option?.colour
|
||||||
export let isOptionEnabled
|
export let isOptionEnabled
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
@ -22,6 +23,8 @@
|
||||||
export let tooltip = ""
|
export let tooltip = ""
|
||||||
export let autocomplete = false
|
export let autocomplete = false
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
|
export let align
|
||||||
|
export let footer = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -48,10 +51,13 @@
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{autoWidth}
|
{autoWidth}
|
||||||
{sort}
|
{sort}
|
||||||
|
{align}
|
||||||
|
{footer}
|
||||||
{getOptionLabel}
|
{getOptionLabel}
|
||||||
{getOptionValue}
|
{getOptionValue}
|
||||||
{getOptionIcon}
|
{getOptionIcon}
|
||||||
{getOptionColour}
|
{getOptionColour}
|
||||||
|
{useOptionIconImage}
|
||||||
{isOptionEnabled}
|
{isOptionEnabled}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{customPopoverHeight}
|
{customPopoverHeight}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.3.18-alpha.14",
|
"@budibase/bbui": "2.3.18-alpha.17",
|
||||||
"@budibase/client": "2.3.18-alpha.14",
|
"@budibase/client": "2.3.18-alpha.17",
|
||||||
"@budibase/frontend-core": "2.3.18-alpha.14",
|
"@budibase/frontend-core": "2.3.18-alpha.17",
|
||||||
"@budibase/string-templates": "2.3.18-alpha.14",
|
"@budibase/string-templates": "2.3.18-alpha.17",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
|
|
|
@ -72,6 +72,8 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
// onboarding
|
// onboarding
|
||||||
onboarding: false,
|
onboarding: false,
|
||||||
tourNodes: null,
|
tourNodes: null,
|
||||||
|
|
||||||
|
builderSidePanel: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendStore = () => {
|
export const getFrontendStore = () => {
|
||||||
|
|
|
@ -192,13 +192,13 @@
|
||||||
editableColumn.name = originalName
|
editableColumn.name = originalName
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteColumn() {
|
async function deleteColumn() {
|
||||||
try {
|
try {
|
||||||
editableColumn.name = deleteColName
|
editableColumn.name = deleteColName
|
||||||
if (editableColumn.name === $tables.selected.primaryDisplay) {
|
if (editableColumn.name === $tables.selected.primaryDisplay) {
|
||||||
notifications.error("You cannot delete the display column")
|
notifications.error("You cannot delete the display column")
|
||||||
} else {
|
} else {
|
||||||
tables.deleteField(editableColumn)
|
await tables.deleteField(editableColumn)
|
||||||
notifications.success(`Column ${editableColumn.name} deleted.`)
|
notifications.success(`Column ${editableColumn.name} deleted.`)
|
||||||
confirmDeleteDialog.hide()
|
confirmDeleteDialog.hide()
|
||||||
hide()
|
hide()
|
||||||
|
|
|
@ -11,16 +11,24 @@
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let allowPublic = true
|
export let allowPublic = true
|
||||||
export let allowRemove = false
|
export let allowRemove = false
|
||||||
|
export let disabled = false
|
||||||
|
export let align
|
||||||
|
export let footer = null
|
||||||
|
export let allowedRoles = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const RemoveID = "remove"
|
const RemoveID = "remove"
|
||||||
|
|
||||||
$: options = getOptions($roles, allowPublic, allowRemove)
|
$: options = getOptions($roles, allowPublic, allowRemove, allowedRoles)
|
||||||
|
|
||||||
const getOptions = (roles, allowPublic) => {
|
const getOptions = (roles, allowPublic, allowRemove, allowedRoles) => {
|
||||||
|
if (allowedRoles?.length) {
|
||||||
|
return roles.filter(role => allowedRoles.includes(role._id))
|
||||||
|
}
|
||||||
|
let newRoles = [...roles]
|
||||||
if (allowRemove) {
|
if (allowRemove) {
|
||||||
roles = [
|
newRoles = [
|
||||||
...roles,
|
...newRoles,
|
||||||
{
|
{
|
||||||
_id: RemoveID,
|
_id: RemoveID,
|
||||||
name: "Remove",
|
name: "Remove",
|
||||||
|
@ -28,9 +36,9 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if (allowPublic) {
|
if (allowPublic) {
|
||||||
return roles
|
return newRoles
|
||||||
}
|
}
|
||||||
return roles.filter(role => role._id !== Constants.Roles.PUBLIC)
|
return newRoles.filter(role => role._id !== Constants.Roles.PUBLIC)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getColor = role => {
|
const getColor = role => {
|
||||||
|
@ -59,6 +67,9 @@
|
||||||
<Select
|
<Select
|
||||||
{autoWidth}
|
{autoWidth}
|
||||||
{quiet}
|
{quiet}
|
||||||
|
{disabled}
|
||||||
|
{align}
|
||||||
|
{footer}
|
||||||
bind:value
|
bind:value
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
{options}
|
{options}
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
Heading,
|
Heading,
|
||||||
Body,
|
Body,
|
||||||
Button,
|
Button,
|
||||||
Icon,
|
ActionButton,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||||
|
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import analytics, { Events, EventSource } from "analytics"
|
import analytics, { Events, EventSource } from "analytics"
|
||||||
|
@ -16,6 +18,9 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import DeployModal from "components/deploy/DeployModal.svelte"
|
import DeployModal from "components/deploy/DeployModal.svelte"
|
||||||
import { apps } from "stores/portal"
|
import { apps } from "stores/portal"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
||||||
|
import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js"
|
||||||
|
|
||||||
export let application
|
export let application
|
||||||
|
|
||||||
|
@ -108,66 +113,97 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="deployment-top-nav">
|
<div class="action-top-nav">
|
||||||
{#if isPublished}
|
<div class="action-buttons">
|
||||||
<div class="publish-popover">
|
<div class="version">
|
||||||
<div bind:this={publishPopoverAnchor}>
|
<VersionModal />
|
||||||
<Icon
|
|
||||||
size="M"
|
|
||||||
hoverable
|
|
||||||
name="Globe"
|
|
||||||
tooltip="Your published app"
|
|
||||||
on:click={publishPopover.show()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Popover
|
|
||||||
bind:this={publishPopover}
|
|
||||||
align="right"
|
|
||||||
disabled={!isPublished}
|
|
||||||
anchor={publishPopoverAnchor}
|
|
||||||
offset={10}
|
|
||||||
>
|
|
||||||
<div class="popover-content">
|
|
||||||
<Layout noPadding gap="M">
|
|
||||||
<Heading size="XS">Your published app</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
<span class="publish-popover-message">
|
|
||||||
{processStringSync(
|
|
||||||
"Last published {{ duration time 'millisecond' }} ago",
|
|
||||||
{
|
|
||||||
time:
|
|
||||||
new Date().getTime() -
|
|
||||||
new Date(latestDeployments[0].updatedAt).getTime(),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</Body>
|
|
||||||
<div class="buttons">
|
|
||||||
<Button
|
|
||||||
warning={true}
|
|
||||||
icon="GlobeStrike"
|
|
||||||
disabled={!isPublished}
|
|
||||||
on:click={unpublishApp}
|
|
||||||
>
|
|
||||||
Unpublish
|
|
||||||
</Button>
|
|
||||||
<Button cta on:click={viewApp}>View app</Button>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
<RevertModal />
|
||||||
|
|
||||||
{#if !isPublished}
|
{#if isPublished}
|
||||||
<Icon
|
<div class="publish-popover">
|
||||||
size="M"
|
<div bind:this={publishPopoverAnchor}>
|
||||||
name="GlobeStrike"
|
<ActionButton
|
||||||
disabled
|
quiet
|
||||||
tooltip="Your app has not been published yet"
|
icon="Globe"
|
||||||
/>
|
size="M"
|
||||||
{/if}
|
tooltip="Your published app"
|
||||||
|
on:click={publishPopover.show()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Popover
|
||||||
|
bind:this={publishPopover}
|
||||||
|
align="right"
|
||||||
|
disabled={!isPublished}
|
||||||
|
anchor={publishPopoverAnchor}
|
||||||
|
offset={10}
|
||||||
|
>
|
||||||
|
<div class="popover-content">
|
||||||
|
<Layout noPadding gap="M">
|
||||||
|
<Heading size="XS">Your published app</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
<span class="publish-popover-message">
|
||||||
|
{processStringSync(
|
||||||
|
"Last published {{ duration time 'millisecond' }} ago",
|
||||||
|
{
|
||||||
|
time:
|
||||||
|
new Date().getTime() -
|
||||||
|
new Date(latestDeployments[0].updatedAt).getTime(),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Body>
|
||||||
|
<div class="buttons">
|
||||||
|
<Button
|
||||||
|
warning={true}
|
||||||
|
icon="GlobeStrike"
|
||||||
|
disabled={!isPublished}
|
||||||
|
on:click={unpublishApp}
|
||||||
|
>
|
||||||
|
Unpublish
|
||||||
|
</Button>
|
||||||
|
<Button cta on:click={viewApp}>View app</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !isPublished}
|
||||||
|
<ActionButton
|
||||||
|
quiet
|
||||||
|
icon="GlobeStrike"
|
||||||
|
size="M"
|
||||||
|
tooltip="Your app has not been published yet"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<TourWrap
|
||||||
|
tourStepKey={$store.onboarding
|
||||||
|
? TOUR_STEP_KEYS.BUILDER_USER_MANAGEMENT
|
||||||
|
: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT}
|
||||||
|
>
|
||||||
|
<span id="builder-app-users-button">
|
||||||
|
<ActionButton
|
||||||
|
quiet
|
||||||
|
icon="UserGroup"
|
||||||
|
size="M"
|
||||||
|
on:click={() => {
|
||||||
|
store.update(state => {
|
||||||
|
state.builderSidePanel = true
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Users
|
||||||
|
</ActionButton>
|
||||||
|
</span>
|
||||||
|
</TourWrap>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={unpublishModal}
|
bind:this={unpublishModal}
|
||||||
title="Confirm unpublish"
|
title="Confirm unpublish"
|
||||||
|
@ -183,6 +219,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* .banner-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
} */
|
||||||
.popover-content {
|
.popover-content {
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
@ -191,6 +232,22 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
/* gap: var(--spacing-s); */
|
||||||
|
}
|
||||||
|
.version {
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.action-top-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,10 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
Icon,
|
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
|
ActionButton,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
@ -28,12 +28,14 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Icon
|
<ActionButton
|
||||||
name="Revert"
|
quiet
|
||||||
hoverable
|
icon="Revert"
|
||||||
on:click={revertModal.show}
|
size="M"
|
||||||
tooltip="Revert changes"
|
tooltip="Revert changes"
|
||||||
|
on:click={revertModal.show}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal bind:this={revertModal}>
|
<Modal bind:this={revertModal}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Revert Changes"
|
title="Revert Changes"
|
||||||
|
|
|
@ -122,7 +122,9 @@
|
||||||
<Layout noPadding gap="M">
|
<Layout noPadding gap="M">
|
||||||
<div class="tour-header">
|
<div class="tour-header">
|
||||||
<Heading size="XS">{tourStep?.title || "-"}</Heading>
|
<Heading size="XS">{tourStep?.title || "-"}</Heading>
|
||||||
<div>{`${tourStepIdx + 1}/${tourSteps?.length}`}</div>
|
{#if tourSteps?.length > 1}
|
||||||
|
<div>{`${tourStepIdx + 1}/${tourSteps?.length}`}</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
<span class="tour-body">
|
<span class="tour-body">
|
||||||
|
|
|
@ -6,16 +6,19 @@
|
||||||
|
|
||||||
export let tourStepKey
|
export let tourStepKey
|
||||||
|
|
||||||
let currentTour
|
let currentTourStep
|
||||||
let ready = false
|
let ready = false
|
||||||
let handler
|
let handler
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!$store.tourKey) return
|
if (!$store.tourKey) return
|
||||||
|
|
||||||
currentTour = TOURS[$store.tourKey].find(step => step.id === tourStepKey)
|
currentTourStep = TOURS[$store.tourKey].find(
|
||||||
|
step => step.id === tourStepKey
|
||||||
|
)
|
||||||
|
if (!currentTourStep) return
|
||||||
|
|
||||||
const elem = document.querySelector(currentTour.query)
|
const elem = document.querySelector(currentTourStep.query)
|
||||||
handler = tourHandler(elem, tourStepKey)
|
handler = tourHandler(elem, tourStepKey)
|
||||||
ready = true
|
ready = true
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,11 +9,14 @@ export const TOUR_STEP_KEYS = {
|
||||||
BUILDER_APP_PUBLISH: "builder-app-publish",
|
BUILDER_APP_PUBLISH: "builder-app-publish",
|
||||||
BUILDER_DATA_SECTION: "builder-data-section",
|
BUILDER_DATA_SECTION: "builder-data-section",
|
||||||
BUILDER_DESIGN_SECTION: "builder-design-section",
|
BUILDER_DESIGN_SECTION: "builder-design-section",
|
||||||
|
BUILDER_USER_MANAGEMENT: "builder-user-management",
|
||||||
BUILDER_AUTOMATE_SECTION: "builder-automate-section",
|
BUILDER_AUTOMATE_SECTION: "builder-automate-section",
|
||||||
|
FEATURE_USER_MANAGEMENT: "feature-user-management",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TOUR_KEYS = {
|
export const TOUR_KEYS = {
|
||||||
TOUR_BUILDER_ONBOARDING: "builder-onboarding",
|
TOUR_BUILDER_ONBOARDING: "builder-onboarding",
|
||||||
|
FEATURE_ONBOARDING: "feature-onboarding",
|
||||||
}
|
}
|
||||||
|
|
||||||
const tourEvent = eventKey => {
|
const tourEvent = eventKey => {
|
||||||
|
@ -58,6 +61,15 @@ const getTours = () => {
|
||||||
},
|
},
|
||||||
align: "left",
|
align: "left",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: TOUR_STEP_KEYS.BUILDER_USER_MANAGEMENT,
|
||||||
|
title: "Users",
|
||||||
|
query: ".toprightnav #builder-app-users-button",
|
||||||
|
body: "Add users to your app and control what level of access they have.",
|
||||||
|
onLoad: () => {
|
||||||
|
tourEvent(TOUR_STEP_KEYS.BUILDER_USER_MANAGEMENT)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: TOUR_STEP_KEYS.BUILDER_APP_PUBLISH,
|
id: TOUR_STEP_KEYS.BUILDER_APP_PUBLISH,
|
||||||
title: "Publish",
|
title: "Publish",
|
||||||
|
@ -79,6 +91,37 @@ const getTours = () => {
|
||||||
// Update the cached user
|
// Update the cached user
|
||||||
await auth.getSelf()
|
await auth.getSelf()
|
||||||
|
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
tourNodes: undefined,
|
||||||
|
tourKey: undefined,
|
||||||
|
tourKeyStep: undefined,
|
||||||
|
onboarding: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[TOUR_KEYS.FEATURE_ONBOARDING]: [
|
||||||
|
{
|
||||||
|
id: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT,
|
||||||
|
title: "Users",
|
||||||
|
query: ".toprightnav #builder-app-users-button",
|
||||||
|
body: "Add users to your app and control what level of access they have.",
|
||||||
|
onLoad: () => {
|
||||||
|
tourEvent(TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT)
|
||||||
|
},
|
||||||
|
onComplete: async () => {
|
||||||
|
// Push the onboarding forward
|
||||||
|
if (get(auth).user) {
|
||||||
|
await users.save({
|
||||||
|
...get(auth).user,
|
||||||
|
onboardedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update the cached user
|
||||||
|
await auth.getSelf()
|
||||||
|
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
tourNodes: undefined,
|
tourNodes: undefined,
|
||||||
|
|
|
@ -0,0 +1,763 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Icon,
|
||||||
|
Heading,
|
||||||
|
Layout,
|
||||||
|
Input,
|
||||||
|
clickOutside,
|
||||||
|
notifications,
|
||||||
|
ActionButton,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import { groups, licensing, apps, users } from "stores/portal"
|
||||||
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
|
import { API } from "api"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
|
||||||
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
|
import { Constants, Utils } from "@budibase/frontend-core"
|
||||||
|
import { emailValidator } from "helpers/validation"
|
||||||
|
import CopyInput from "components/common/inputs/CopyInput.svelte"
|
||||||
|
import { roles } from "stores/backend"
|
||||||
|
|
||||||
|
let query = null
|
||||||
|
let loaded = false
|
||||||
|
let rendered = false
|
||||||
|
let inviting = false
|
||||||
|
let searchFocus = false
|
||||||
|
|
||||||
|
let appInvites = []
|
||||||
|
let filteredInvites = []
|
||||||
|
let filteredUsers = []
|
||||||
|
let filteredGroups = []
|
||||||
|
let selectedGroup
|
||||||
|
let userOnboardResponse = null
|
||||||
|
|
||||||
|
$: queryIsEmail = emailValidator(query) === true
|
||||||
|
$: prodAppId = apps.getProdAppID($store.appId)
|
||||||
|
$: promptInvite = showInvite(
|
||||||
|
filteredInvites,
|
||||||
|
filteredUsers,
|
||||||
|
filteredGroups,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
|
||||||
|
const showInvite = (invites, users, groups, query) => {
|
||||||
|
return !invites?.length && !users?.length && !groups?.length && query
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterInvites = async query => {
|
||||||
|
appInvites = await getInvites()
|
||||||
|
if (!query || query == "") {
|
||||||
|
filteredInvites = appInvites
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filteredInvites = appInvites.filter(invite => invite.email.includes(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
$: filterInvites(query)
|
||||||
|
|
||||||
|
const usersFetch = fetchData({
|
||||||
|
API,
|
||||||
|
datasource: {
|
||||||
|
type: "user",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchUsers = async (query, sidePaneOpen, loaded) => {
|
||||||
|
if (!sidePaneOpen || !loaded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!prodAppId) {
|
||||||
|
console.log("Application id required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await usersFetch.update({
|
||||||
|
query: {
|
||||||
|
appId: query ? null : prodAppId,
|
||||||
|
email: query,
|
||||||
|
paginated: query ? null : false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await usersFetch.refresh()
|
||||||
|
|
||||||
|
filteredUsers = $usersFetch.rows.map(user => {
|
||||||
|
const isBuilderOrAdmin = user.admin?.global || user.builder?.global
|
||||||
|
let role = undefined
|
||||||
|
if (isBuilderOrAdmin) {
|
||||||
|
role = Constants.Roles.ADMIN
|
||||||
|
} else {
|
||||||
|
const appRole = Object.keys(user.roles).find(x => x === prodAppId)
|
||||||
|
if (appRole) {
|
||||||
|
role = user.roles[appRole]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
role,
|
||||||
|
isBuilderOrAdmin,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedUpdateFetch = Utils.debounce(searchUsers, 250)
|
||||||
|
$: debouncedUpdateFetch(query, $store.builderSidePanel, loaded)
|
||||||
|
|
||||||
|
const updateAppUser = async (user, role) => {
|
||||||
|
if (!prodAppId) {
|
||||||
|
notifications.error("Application id must be specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const update = await users.get(user._id)
|
||||||
|
await users.save({
|
||||||
|
...update,
|
||||||
|
roles: {
|
||||||
|
...update.roles,
|
||||||
|
[prodAppId]: role,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await searchUsers(query, $store.builderSidePanel, loaded)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUpdateUser = async (user, role) => {
|
||||||
|
if (!user) {
|
||||||
|
notifications.error("A user must be specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (user.role === role) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await updateAppUser(user, role)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
notifications.error("User could not be updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAppGroup = async (target, role) => {
|
||||||
|
if (!prodAppId) {
|
||||||
|
notifications.error("Application id must be specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
await groups.actions.removeApp(target._id, prodAppId)
|
||||||
|
} else {
|
||||||
|
await groups.actions.addApp(target._id, prodAppId, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
await usersFetch.refresh()
|
||||||
|
await groups.actions.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUpdateGroup = async (group, role) => {
|
||||||
|
if (!group) {
|
||||||
|
notifications.error("A group must be specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await updateAppGroup(group, role)
|
||||||
|
} catch {
|
||||||
|
notifications.error("Group update failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAppGroups = (allGroups, appId) => {
|
||||||
|
if (!allGroups) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return allGroups.filter(group => {
|
||||||
|
if (!group.roles) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return groups.actions.getGroupAppIds(group).includes(appId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchGroups = (userGroups, query) => {
|
||||||
|
let filterGroups = query?.length
|
||||||
|
? userGroups
|
||||||
|
: getAppGroups(userGroups, prodAppId)
|
||||||
|
return filterGroups
|
||||||
|
.filter(group => {
|
||||||
|
if (!query?.length) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
//Group Name only.
|
||||||
|
const nameMatch = group.name
|
||||||
|
?.toLowerCase()
|
||||||
|
.includes(query?.toLowerCase())
|
||||||
|
|
||||||
|
return nameMatch
|
||||||
|
})
|
||||||
|
.map(enrichGroupRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
const enrichGroupRole = group => {
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
role: group.roles?.[
|
||||||
|
groups.actions.getGroupAppIds(group).find(x => x === prodAppId)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEnrichedGroups = groups => {
|
||||||
|
return groups.map(enrichGroupRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the 'role' attribute and sets it to the current app.
|
||||||
|
$: enrichedGroups = getEnrichedGroups($groups)
|
||||||
|
$: filteredGroups = searchGroups(enrichedGroups, query)
|
||||||
|
$: groupUsers = buildGroupUsers(filteredGroups, filteredUsers)
|
||||||
|
$: allUsers = [...filteredUsers, ...groupUsers]
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create pseudo users from the "users" attribute on app groups.
|
||||||
|
These users will appear muted in the UI and show the ROLE
|
||||||
|
inherited from their parent group. The users allow assigning of user
|
||||||
|
specific roles for the app.
|
||||||
|
*/
|
||||||
|
const buildGroupUsers = (userGroups, filteredUsers) => {
|
||||||
|
if (query) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
// Must exclude users who have explicit privileges
|
||||||
|
const userByEmail = filteredUsers.reduce((acc, user) => {
|
||||||
|
if (user.role || user.admin?.global || user.builder?.global) {
|
||||||
|
acc.push(user.email)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const indexedUsers = userGroups.reduce((acc, group) => {
|
||||||
|
group.users.forEach(user => {
|
||||||
|
if (userByEmail.indexOf(user.email) == -1) {
|
||||||
|
acc[user._id] = {
|
||||||
|
_id: user._id,
|
||||||
|
email: user.email,
|
||||||
|
role: group.role,
|
||||||
|
group: group.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
return Object.values(indexedUsers)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInvites = async () => {
|
||||||
|
try {
|
||||||
|
const invites = await users.getInvites()
|
||||||
|
return invites
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error(error.message)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function inviteUser() {
|
||||||
|
if (!queryIsEmail) {
|
||||||
|
notifications.error("Email is not valid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newUserEmail = query + ""
|
||||||
|
inviting = true
|
||||||
|
|
||||||
|
const payload = [
|
||||||
|
{
|
||||||
|
email: newUserEmail,
|
||||||
|
builder: false,
|
||||||
|
admin: false,
|
||||||
|
apps: { [prodAppId]: Constants.Roles.BASIC },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
let userInviteResponse
|
||||||
|
try {
|
||||||
|
userInviteResponse = await users.onboard(payload)
|
||||||
|
|
||||||
|
const newUser = userInviteResponse?.successful.find(
|
||||||
|
user => user.email === newUserEmail
|
||||||
|
)
|
||||||
|
if (newUser) {
|
||||||
|
notifications.success(
|
||||||
|
userInviteResponse.created
|
||||||
|
? "User created successfully"
|
||||||
|
: "User invite successful"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw new Error("User invite failed")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message)
|
||||||
|
notifications.error("Error inviting user")
|
||||||
|
}
|
||||||
|
inviting = false
|
||||||
|
return userInviteResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInviteUser = async () => {
|
||||||
|
userOnboardResponse = await inviteUser()
|
||||||
|
|
||||||
|
const userInviteSuccess = userOnboardResponse?.successful
|
||||||
|
if (userInviteSuccess && userInviteSuccess[0].email === query) {
|
||||||
|
query = null
|
||||||
|
query = userInviteSuccess[0].email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUpdateUserInvite = async (invite, role) => {
|
||||||
|
await users.updateInvite({
|
||||||
|
code: invite.code,
|
||||||
|
apps: {
|
||||||
|
...invite.apps,
|
||||||
|
[prodAppId]: role,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await filterInvites()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUninviteAppUser = async invite => {
|
||||||
|
await uninviteAppUser(invite)
|
||||||
|
await filterInvites()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge only the app from the invite or recind the invite if only 1 app remains?
|
||||||
|
const uninviteAppUser = async invite => {
|
||||||
|
let updated = { ...invite }
|
||||||
|
delete updated.info.apps[prodAppId]
|
||||||
|
|
||||||
|
return await users.updateInvite({
|
||||||
|
code: updated.code,
|
||||||
|
apps: updated.apps,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const initSidePanel = async sidePaneOpen => {
|
||||||
|
if (sidePaneOpen === true) {
|
||||||
|
await groups.actions.init()
|
||||||
|
}
|
||||||
|
loaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
$: initSidePanel($store.builderSidePanel)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
rendered = true
|
||||||
|
})
|
||||||
|
|
||||||
|
const userTitle = user => {
|
||||||
|
if (user.admin?.global) {
|
||||||
|
return "Admin"
|
||||||
|
} else if (user.builder?.global) {
|
||||||
|
return "Developer"
|
||||||
|
} else {
|
||||||
|
return "App user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRoleFooter = user => {
|
||||||
|
if (user.group) {
|
||||||
|
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) {
|
||||||
|
return "This user's role grants admin access to all apps"
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="builder-side-panel-container"
|
||||||
|
class:open={$store.builderSidePanel}
|
||||||
|
use:clickOutside={$store.builderSidePanel
|
||||||
|
? () => {
|
||||||
|
store.update(state => {
|
||||||
|
state.builderSidePanel = false
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
: () => {}}
|
||||||
|
>
|
||||||
|
<div class="builder-side-panel-header">
|
||||||
|
<Heading size="S">Users</Heading>
|
||||||
|
<Icon
|
||||||
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
|
name="RailRightClose"
|
||||||
|
hoverable
|
||||||
|
on:click={() => {
|
||||||
|
store.update(state => {
|
||||||
|
state.builderSidePanel = false
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="search" class:focused={searchFocus}>
|
||||||
|
<span class="search-input">
|
||||||
|
<Input
|
||||||
|
placeholder={"Add users and groups to your app"}
|
||||||
|
autocomplete="off"
|
||||||
|
disabled={inviting}
|
||||||
|
value={query}
|
||||||
|
on:input={e => {
|
||||||
|
query = e.target.value.trim()
|
||||||
|
}}
|
||||||
|
on:focus={() => (searchFocus = true)}
|
||||||
|
on:blur={() => (searchFocus = false)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="search-input-icon"
|
||||||
|
class:searching={query}
|
||||||
|
on:click={() => {
|
||||||
|
if (!query) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query = null
|
||||||
|
userOnboardResponse = null
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={query ? "Close" : "Search"} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
{#if promptInvite && !userOnboardResponse}
|
||||||
|
<Layout gap="S" paddingX="XL">
|
||||||
|
<div class="invite-header">
|
||||||
|
<Heading size="XS">No user found</Heading>
|
||||||
|
<div class="invite-directions">
|
||||||
|
Add a valid email to invite a new user
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="invite-form">
|
||||||
|
<span>{query || ""}</span>
|
||||||
|
<ActionButton
|
||||||
|
icon="UserAdd"
|
||||||
|
disabled={!queryIsEmail || inviting}
|
||||||
|
on:click={onInviteUser}
|
||||||
|
>
|
||||||
|
Add user
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !promptInvite}
|
||||||
|
<Layout gap="L" noPadding>
|
||||||
|
{#if filteredInvites?.length}
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<div class="auth-entity-header">
|
||||||
|
<div class="auth-entity-title">Pending invites</div>
|
||||||
|
<div class="auth-entity-access-title">Access</div>
|
||||||
|
</div>
|
||||||
|
{#each filteredInvites as invite}
|
||||||
|
<div class="auth-entity">
|
||||||
|
<div class="details">
|
||||||
|
<div class="user-email" title={invite.email}>
|
||||||
|
{invite.email}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="auth-entity-access">
|
||||||
|
<RoleSelect
|
||||||
|
placeholder={false}
|
||||||
|
value={invite.info.apps?.[prodAppId]}
|
||||||
|
allowRemove={invite.info.apps?.[prodAppId]}
|
||||||
|
allowPublic={false}
|
||||||
|
quiet={true}
|
||||||
|
on:change={e => {
|
||||||
|
onUpdateUserInvite(invite, e.detail)
|
||||||
|
}}
|
||||||
|
on:remove={() => {
|
||||||
|
onUninviteAppUser(invite)
|
||||||
|
}}
|
||||||
|
autoWidth
|
||||||
|
align="right"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $licensing.groupsEnabled && filteredGroups?.length}
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<div class="auth-entity-header">
|
||||||
|
<div class="auth-entity-title">Groups</div>
|
||||||
|
<div class="auth-entity-access-title">Access</div>
|
||||||
|
</div>
|
||||||
|
{#each filteredGroups as group}
|
||||||
|
<div
|
||||||
|
class="auth-entity group"
|
||||||
|
on:click={() => {
|
||||||
|
if (selectedGroup != group._id) {
|
||||||
|
selectedGroup = group._id
|
||||||
|
} else {
|
||||||
|
selectedGroup = null
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:keydown={() => {}}
|
||||||
|
>
|
||||||
|
<div class="details">
|
||||||
|
<GroupIcon {group} size="S" />
|
||||||
|
<div>
|
||||||
|
{group.name}
|
||||||
|
</div>
|
||||||
|
<div class="auth-entity-meta">
|
||||||
|
{`${group.users?.length} user${
|
||||||
|
group.users?.length != 1 ? "s" : ""
|
||||||
|
}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="auth-entity-access">
|
||||||
|
<RoleSelect
|
||||||
|
placeholder={false}
|
||||||
|
value={group.role}
|
||||||
|
allowRemove={group.role}
|
||||||
|
allowPublic={false}
|
||||||
|
quiet={true}
|
||||||
|
on:change={e => {
|
||||||
|
onUpdateGroup(group, e.detail)
|
||||||
|
}}
|
||||||
|
on:remove={() => {
|
||||||
|
onUpdateGroup(group)
|
||||||
|
}}
|
||||||
|
autoWidth
|
||||||
|
align="right"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if filteredUsers?.length}
|
||||||
|
<div class="auth-entity-section">
|
||||||
|
<div class="auth-entity-header ">
|
||||||
|
<div class="auth-entity-title">Users</div>
|
||||||
|
<div class="auth-entity-access-title">Access</div>
|
||||||
|
</div>
|
||||||
|
{#each allUsers as user}
|
||||||
|
<div class="auth-entity">
|
||||||
|
<div class="details">
|
||||||
|
<div class="user-email" title={user.email}>
|
||||||
|
{user.email}
|
||||||
|
</div>
|
||||||
|
<div class="auth-entity-meta">
|
||||||
|
{userTitle(user)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="auth-entity-access" class:muted={user.group}>
|
||||||
|
<RoleSelect
|
||||||
|
footer={getRoleFooter(user)}
|
||||||
|
placeholder={false}
|
||||||
|
value={user.role}
|
||||||
|
allowRemove={user.role && !user.group}
|
||||||
|
allowPublic={false}
|
||||||
|
quiet={true}
|
||||||
|
on:change={e => {
|
||||||
|
onUpdateUser(user, e.detail)
|
||||||
|
}}
|
||||||
|
on:remove={() => {
|
||||||
|
onUpdateUser(user)
|
||||||
|
}}
|
||||||
|
autoWidth
|
||||||
|
align="right"
|
||||||
|
allowedRoles={user.isBuilderOrAdmin
|
||||||
|
? [Constants.Roles.ADMIN]
|
||||||
|
: null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if userOnboardResponse?.created}
|
||||||
|
<Layout gap="S" paddingX="XL">
|
||||||
|
<div class="invite-header">
|
||||||
|
<Heading size="XS">User added!</Heading>
|
||||||
|
<div class="invite-directions">
|
||||||
|
Email invites are not available without SMTP configuration. Here is
|
||||||
|
the password that has been generated for this user.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<CopyInput
|
||||||
|
value={userOnboardResponse.successful[0]?.password}
|
||||||
|
label="Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.search :global(input) {
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-icon.searching {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity-meta {
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity-access {
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.auth-entity-access.muted :global(.spectrum-Picker-label),
|
||||||
|
.auth-entity-access.muted :global(.spectrum-StatusLight) {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity-header {
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity,
|
||||||
|
.auth-entity-header {
|
||||||
|
padding: 0px var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity,
|
||||||
|
.auth-entity-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 110px;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity .details {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-entity .user-email {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: calc(100vw - 40px);
|
||||||
|
background: var(--background);
|
||||||
|
border-left: var(--border-light);
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: transform 130ms ease-out;
|
||||||
|
position: absolute;
|
||||||
|
width: 400px;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.builder-side-panel-header,
|
||||||
|
#builder-side-panel-container .search {
|
||||||
|
padding: 0px var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container .auth-entity .details {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container .search {
|
||||||
|
padding-top: var(--spacing-m);
|
||||||
|
padding-bottom: var(--spacing-m);
|
||||||
|
border-top: var(--border-light);
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
border-right: 2px solid transparent;
|
||||||
|
margin-right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container .search :global(input) {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container .search :global(input) {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container .search.focused {
|
||||||
|
border-color: var(
|
||||||
|
--spectrum-textfield-m-border-color-down,
|
||||||
|
var(--spectrum-alias-border-color-mouse-focus)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container .search :global(input::placeholder) {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#builder-side-panel-container.open {
|
||||||
|
transform: translateX(0);
|
||||||
|
box-shadow: 0 0 40px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.builder-side-panel-header {
|
||||||
|
height: 58px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-header {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -13,15 +13,14 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
|
||||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
import AppActions from "components/deploy/AppActions.svelte"
|
||||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
|
||||||
import DeployNavigation from "components/deploy/DeployNavigation.svelte"
|
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { isActive, goto, layout, redirect } from "@roxi/routify"
|
import { isActive, goto, layout, redirect } from "@roxi/routify"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
||||||
import TourPopover from "components/portal/onboarding/TourPopover.svelte"
|
import TourPopover from "components/portal/onboarding/TourPopover.svelte"
|
||||||
|
import BuilderSidePanel from "./_components/BuilderSidePanel.svelte"
|
||||||
import { TOUR_KEYS, TOURS } from "components/portal/onboarding/tours.js"
|
import { TOUR_KEYS, TOURS } from "components/portal/onboarding/tours.js"
|
||||||
|
|
||||||
export let application
|
export let application
|
||||||
|
@ -69,22 +68,32 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const initTour = async () => {
|
const initTour = async () => {
|
||||||
if (
|
// Check if onboarding is enabled.
|
||||||
!$auth.user?.onboardedAt &&
|
if (isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)) {
|
||||||
isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)
|
if (!$auth.user?.onboardedAt) {
|
||||||
) {
|
// Determine the correct step
|
||||||
// Determine the correct step
|
const activeNav = $layout.children.find(c => $isActive(c.path))
|
||||||
const activeNav = $layout.children.find(c => $isActive(c.path))
|
const onboardingTour = TOURS[TOUR_KEYS.TOUR_BUILDER_ONBOARDING]
|
||||||
const onboardingTour = TOURS[TOUR_KEYS.TOUR_BUILDER_ONBOARDING]
|
const targetStep = activeNav
|
||||||
const targetStep = activeNav
|
? onboardingTour.find(step => step.route === activeNav?.path)
|
||||||
? onboardingTour.find(step => step.route === activeNav?.path)
|
: null
|
||||||
: null
|
await store.update(state => ({
|
||||||
await store.update(state => ({
|
...state,
|
||||||
...state,
|
onboarding: true,
|
||||||
onboarding: true,
|
tourKey: TOUR_KEYS.TOUR_BUILDER_ONBOARDING,
|
||||||
tourKey: TOUR_KEYS.TOUR_BUILDER_ONBOARDING,
|
tourStepKey: targetStep?.id,
|
||||||
tourStepKey: targetStep?.id,
|
}))
|
||||||
}))
|
} else {
|
||||||
|
// Feature tour date
|
||||||
|
const release_date = new Date("2023-03-01T00:00:00.000Z")
|
||||||
|
const onboarded = new Date($auth.user?.onboardedAt)
|
||||||
|
if (onboarded < release_date) {
|
||||||
|
await store.update(state => ({
|
||||||
|
...state,
|
||||||
|
tourKey: TOUR_KEYS.FEATURE_ONBOARDING,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +125,11 @@
|
||||||
<div class="loading" />
|
<div class="loading" />
|
||||||
{:then _}
|
{:then _}
|
||||||
<TourPopover />
|
<TourPopover />
|
||||||
|
|
||||||
|
{#if $store.builderSidePanel}
|
||||||
|
<BuilderSidePanel />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="top-nav">
|
<div class="top-nav">
|
||||||
<div class="topleftnav">
|
<div class="topleftnav">
|
||||||
|
@ -181,11 +195,7 @@
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<div class="version">
|
<AppActions {application} />
|
||||||
<VersionModal />
|
|
||||||
</div>
|
|
||||||
<RevertModal />
|
|
||||||
<DeployNavigation {application} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -250,10 +260,6 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-xl);
|
gap: var(--spacing-l);
|
||||||
}
|
|
||||||
|
|
||||||
.version {
|
|
||||||
margin-right: var(--spacing-s);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -131,24 +131,25 @@
|
||||||
isEqual(providers.google?.config, originalGoogleDoc?.config)
|
isEqual(providers.google?.config, originalGoogleDoc?.config)
|
||||||
? (googleSaveButtonDisabled = true)
|
? (googleSaveButtonDisabled = true)
|
||||||
: (googleSaveButtonDisabled = false)
|
: (googleSaveButtonDisabled = false)
|
||||||
|
|
||||||
|
// delete the callback url which is never saved to the oidc
|
||||||
|
// config doc, to ensure an accurate comparison
|
||||||
|
delete providers.oidc?.config.configs[0].callbackURL
|
||||||
|
|
||||||
isEqual(providers.oidc?.config, originalOidcDoc?.config)
|
isEqual(providers.oidc?.config, originalOidcDoc?.config)
|
||||||
? (oidcSaveButtonDisabled = true)
|
? (oidcSaveButtonDisabled = true)
|
||||||
: (oidcSaveButtonDisabled = false)
|
: (oidcSaveButtonDisabled = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a flag so that it will only try to save completed forms
|
$: googleComplete = !!(
|
||||||
$: partialGoogle =
|
|
||||||
providers.google?.config?.clientID || providers.google?.config?.clientSecret
|
|
||||||
$: partialOidc =
|
|
||||||
providers.oidc?.config?.configs[0].configUrl ||
|
|
||||||
providers.oidc?.config?.configs[0].clientID ||
|
|
||||||
providers.oidc?.config?.configs[0].clientSecret
|
|
||||||
$: googleComplete =
|
|
||||||
providers.google?.config?.clientID && providers.google?.config?.clientSecret
|
providers.google?.config?.clientID && providers.google?.config?.clientSecret
|
||||||
$: oidcComplete =
|
)
|
||||||
|
|
||||||
|
$: oidcComplete = !!(
|
||||||
providers.oidc?.config?.configs[0].configUrl &&
|
providers.oidc?.config?.configs[0].configUrl &&
|
||||||
providers.oidc?.config?.configs[0].clientID &&
|
providers.oidc?.config?.configs[0].clientID &&
|
||||||
providers.oidc?.config?.configs[0].clientSecret
|
providers.oidc?.config?.configs[0].clientSecret
|
||||||
|
)
|
||||||
|
|
||||||
const onFileSelected = e => {
|
const onFileSelected = e => {
|
||||||
let fileName = e.target.files[0].name
|
let fileName = e.target.files[0].name
|
||||||
|
@ -159,74 +160,88 @@
|
||||||
|
|
||||||
async function toggleIsSSOEnforced() {
|
async function toggleIsSSOEnforced() {
|
||||||
const value = $organisation.isSSOEnforced
|
const value = $organisation.isSSOEnforced
|
||||||
await organisation.save({ isSSOEnforced: !value })
|
try {
|
||||||
|
await organisation.save({ isSSOEnforced: !value })
|
||||||
|
} catch (e) {
|
||||||
|
notifications.error(e.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(docs) {
|
async function saveConfig(config) {
|
||||||
let calls = []
|
// Delete unsupported fields
|
||||||
// Only if the user has provided an image, upload it
|
delete config.createdAt
|
||||||
|
delete config.updatedAt
|
||||||
|
return API.saveConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveOIDCLogo() {
|
||||||
if (image) {
|
if (image) {
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
data.append("file", image)
|
data.append("file", image)
|
||||||
calls.push(
|
await API.uploadOIDCLogo({
|
||||||
API.uploadOIDCLogo({
|
name: image.name,
|
||||||
name: image.name,
|
data,
|
||||||
data,
|
})
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveOIDC() {
|
||||||
|
if (!oidcComplete) {
|
||||||
|
notifications.error(
|
||||||
|
`Please fill in all required ${ConfigTypes.OIDC} fields`
|
||||||
)
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
docs.forEach(element => {
|
|
||||||
// Delete unsupported fields
|
|
||||||
delete element.createdAt
|
|
||||||
delete element.updatedAt
|
|
||||||
|
|
||||||
const { activated } = element.config
|
const oidc = providers.oidc
|
||||||
|
|
||||||
if (element.type === ConfigTypes.OIDC) {
|
// Add a UUID here so each config is distinguishable when it arrives at the login page
|
||||||
// Add a UUID here so each config is distinguishable when it arrives at the login page
|
for (let config of oidc.config.configs) {
|
||||||
for (let config of element.config.configs) {
|
if (!config.uuid) {
|
||||||
if (!config.uuid) {
|
config.uuid = Helpers.uuid()
|
||||||
config.uuid = Helpers.uuid()
|
|
||||||
}
|
|
||||||
// Callback urls shouldn't be included
|
|
||||||
delete config.callbackURL
|
|
||||||
}
|
|
||||||
if ((partialOidc || activated) && !oidcComplete) {
|
|
||||||
notifications.error(
|
|
||||||
`Please fill in all required ${ConfigTypes.OIDC} fields`
|
|
||||||
)
|
|
||||||
} else if (oidcComplete || !activated) {
|
|
||||||
calls.push(API.saveConfig(element))
|
|
||||||
// Turn the save button grey when clicked
|
|
||||||
oidcSaveButtonDisabled = true
|
|
||||||
originalOidcDoc = cloneDeep(providers.oidc)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (element.type === ConfigTypes.Google) {
|
// Callback urls shouldn't be included
|
||||||
if ((partialGoogle || activated) && !googleComplete) {
|
delete config.callbackURL
|
||||||
notifications.error(
|
|
||||||
`Please fill in all required ${ConfigTypes.Google} fields`
|
|
||||||
)
|
|
||||||
} else if (googleComplete || !activated) {
|
|
||||||
calls.push(API.saveConfig(element))
|
|
||||||
googleSaveButtonDisabled = true
|
|
||||||
originalGoogleDoc = cloneDeep(providers.google)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (calls.length) {
|
|
||||||
Promise.all(calls)
|
|
||||||
.then(data => {
|
|
||||||
data.forEach(res => {
|
|
||||||
providers[res.type]._rev = res._rev
|
|
||||||
providers[res.type]._id = res._id
|
|
||||||
})
|
|
||||||
notifications.success(`Settings saved`)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
notifications.error("Failed to update auth settings")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await saveConfig(oidc)
|
||||||
|
providers[res.type]._rev = res._rev
|
||||||
|
providers[res.type]._id = res._id
|
||||||
|
await saveOIDCLogo()
|
||||||
|
notifications.success(`Settings saved`)
|
||||||
|
} catch (e) {
|
||||||
|
notifications.error(e.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the save button grey when clicked
|
||||||
|
oidcSaveButtonDisabled = true
|
||||||
|
originalOidcDoc = cloneDeep(providers.oidc)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveGoogle() {
|
||||||
|
if (!googleComplete) {
|
||||||
|
notifications.error(
|
||||||
|
`Please fill in all required ${ConfigTypes.Google} fields`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const google = providers.google
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await saveConfig(google)
|
||||||
|
providers[res.type]._rev = res._rev
|
||||||
|
providers[res.type]._id = res._id
|
||||||
|
notifications.success(`Settings saved`)
|
||||||
|
} catch (e) {
|
||||||
|
notifications.error(e.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
googleSaveButtonDisabled = true
|
||||||
|
originalGoogleDoc = cloneDeep(providers.google)
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultScopes = ["profile", "email", "offline_access"]
|
let defaultScopes = ["profile", "email", "offline_access"]
|
||||||
|
@ -266,7 +281,7 @@
|
||||||
if (!googleDoc?._id) {
|
if (!googleDoc?._id) {
|
||||||
providers.google = {
|
providers.google = {
|
||||||
type: ConfigTypes.Google,
|
type: ConfigTypes.Google,
|
||||||
config: { activated: true },
|
config: { activated: false },
|
||||||
}
|
}
|
||||||
originalGoogleDoc = cloneDeep(googleDoc)
|
originalGoogleDoc = cloneDeep(googleDoc)
|
||||||
} else {
|
} else {
|
||||||
|
@ -290,14 +305,17 @@
|
||||||
}
|
}
|
||||||
if (oidcLogos?.config) {
|
if (oidcLogos?.config) {
|
||||||
const logoKeys = Object.keys(oidcLogos.config)
|
const logoKeys = Object.keys(oidcLogos.config)
|
||||||
logoKeys.map(logoKey => {
|
logoKeys
|
||||||
const logoUrl = oidcLogos.config[logoKey]
|
// don't include the etag entry in the logo config
|
||||||
iconDropdownOptions.unshift({
|
.filter(key => !key.toLowerCase().includes("etag"))
|
||||||
label: logoKey,
|
.map(logoKey => {
|
||||||
value: logoKey,
|
const logoUrl = oidcLogos.config[logoKey]
|
||||||
icon: logoUrl,
|
iconDropdownOptions.unshift({
|
||||||
|
label: logoKey,
|
||||||
|
value: logoKey,
|
||||||
|
icon: logoUrl,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch OIDC config
|
// Fetch OIDC config
|
||||||
|
@ -310,7 +328,7 @@
|
||||||
if (!oidcDoc?._id) {
|
if (!oidcDoc?._id) {
|
||||||
providers.oidc = {
|
providers.oidc = {
|
||||||
type: ConfigTypes.OIDC,
|
type: ConfigTypes.OIDC,
|
||||||
config: { configs: [{ activated: true, scopes: defaultScopes }] },
|
config: { configs: [{ activated: false, scopes: defaultScopes }] },
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
originalOidcDoc = cloneDeep(oidcDoc)
|
originalOidcDoc = cloneDeep(oidcDoc)
|
||||||
|
@ -413,7 +431,7 @@
|
||||||
<Button
|
<Button
|
||||||
disabled={googleSaveButtonDisabled}
|
disabled={googleSaveButtonDisabled}
|
||||||
cta
|
cta
|
||||||
on:click={() => save([providers.google])}
|
on:click={() => saveGoogle()}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -469,6 +487,7 @@
|
||||||
<Select
|
<Select
|
||||||
label=""
|
label=""
|
||||||
bind:value={providers.oidc.config.configs[0].logo}
|
bind:value={providers.oidc.config.configs[0].logo}
|
||||||
|
useOptionIconImage
|
||||||
options={iconDropdownOptions}
|
options={iconDropdownOptions}
|
||||||
on:change={e => e.detail === "Upload" && fileinput.click()}
|
on:change={e => e.detail === "Upload" && fileinput.click()}
|
||||||
/>
|
/>
|
||||||
|
@ -575,11 +594,7 @@
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button disabled={oidcSaveButtonDisabled} cta on:click={() => saveOIDC()}>
|
||||||
disabled={oidcSaveButtonDisabled}
|
|
||||||
cta
|
|
||||||
on:click={() => save([providers.oidc])}
|
|
||||||
>
|
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
|
import _ from "lodash"
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
platformUrl: "",
|
platformUrl: "",
|
||||||
|
@ -26,14 +27,14 @@ export function createOrganisationStore() {
|
||||||
|
|
||||||
async function save(config) {
|
async function save(config) {
|
||||||
// Delete non-persisted fields
|
// Delete non-persisted fields
|
||||||
const storeConfig = get(store)
|
const storeConfig = _.cloneDeep(get(store))
|
||||||
delete storeConfig.oidc
|
delete storeConfig.oidc
|
||||||
delete storeConfig.google
|
delete storeConfig.google
|
||||||
delete storeConfig.oidcCallbackUrl
|
delete storeConfig.oidcCallbackUrl
|
||||||
delete storeConfig.googleCallbackUrl
|
delete storeConfig.googleCallbackUrl
|
||||||
await API.saveConfig({
|
await API.saveConfig({
|
||||||
type: "settings",
|
type: "settings",
|
||||||
config: { ...get(store), ...config },
|
config: { ...storeConfig, ...config },
|
||||||
})
|
})
|
||||||
await init()
|
await init()
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,15 @@ export function createUsersStore() {
|
||||||
return await API.getUsers()
|
return await API.getUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One or more users.
|
||||||
|
async function onboard(payload) {
|
||||||
|
return await API.onboardUsers(payload)
|
||||||
|
}
|
||||||
|
|
||||||
async function invite(payload) {
|
async function invite(payload) {
|
||||||
return API.inviteUsers(payload)
|
return API.inviteUsers(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function acceptInvite(inviteCode, password, firstName, lastName) {
|
async function acceptInvite(inviteCode, password, firstName, lastName) {
|
||||||
return API.acceptInvite({
|
return API.acceptInvite({
|
||||||
inviteCode,
|
inviteCode,
|
||||||
|
@ -42,6 +48,14 @@ export function createUsersStore() {
|
||||||
return API.getUserInvite(inviteCode)
|
return API.getUserInvite(inviteCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getInvites() {
|
||||||
|
return API.getUserInvites()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateInvite(invite) {
|
||||||
|
return API.updateUserInvite(invite)
|
||||||
|
}
|
||||||
|
|
||||||
async function create(data) {
|
async function create(data) {
|
||||||
let mappedUsers = data.users.map(user => {
|
let mappedUsers = data.users.map(user => {
|
||||||
const body = {
|
const body = {
|
||||||
|
@ -106,8 +120,11 @@ export function createUsersStore() {
|
||||||
getUserRole,
|
getUserRole,
|
||||||
fetch,
|
fetch,
|
||||||
invite,
|
invite,
|
||||||
|
onboard,
|
||||||
acceptInvite,
|
acceptInvite,
|
||||||
fetchInvite,
|
fetchInvite,
|
||||||
|
getInvites,
|
||||||
|
updateInvite,
|
||||||
create,
|
create,
|
||||||
save,
|
save,
|
||||||
bulkDelete,
|
bulkDelete,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.3.18-alpha.14",
|
"@budibase/backend-core": "2.3.18-alpha.17",
|
||||||
"@budibase/string-templates": "2.3.18-alpha.14",
|
"@budibase/string-templates": "2.3.18-alpha.17",
|
||||||
"@budibase/types": "2.3.18-alpha.14",
|
"@budibase/types": "2.3.18-alpha.17",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.3.18-alpha.14",
|
"@budibase/bbui": "2.3.18-alpha.17",
|
||||||
"@budibase/frontend-core": "2.3.18-alpha.14",
|
"@budibase/frontend-core": "2.3.18-alpha.17",
|
||||||
"@budibase/string-templates": "2.3.18-alpha.14",
|
"@budibase/string-templates": "2.3.18-alpha.17",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.3.18-alpha.14",
|
"@budibase/bbui": "2.3.18-alpha.17",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,10 @@ export const buildUserEndpoints = API => ({
|
||||||
* Gets a list of users in the current tenant.
|
* Gets a list of users in the current tenant.
|
||||||
* @param {string} page The page to retrieve
|
* @param {string} page The page to retrieve
|
||||||
* @param {string} search The starts with string to search username/email by.
|
* @param {string} search The starts with string to search username/email by.
|
||||||
|
* @param {string} appId Facilitate app/role based user searching
|
||||||
|
* @param {boolean} paginated Allow the disabling of pagination
|
||||||
*/
|
*/
|
||||||
searchUsers: async ({ page, email, appId } = {}) => {
|
searchUsers: async ({ paginated, page, email, appId } = {}) => {
|
||||||
const opts = {}
|
const opts = {}
|
||||||
if (page) {
|
if (page) {
|
||||||
opts.page = page
|
opts.page = page
|
||||||
|
@ -24,6 +26,9 @@ export const buildUserEndpoints = API => ({
|
||||||
if (appId) {
|
if (appId) {
|
||||||
opts.appId = appId
|
opts.appId = appId
|
||||||
}
|
}
|
||||||
|
if (typeof paginated === "boolean") {
|
||||||
|
opts.paginated = paginated
|
||||||
|
}
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: `/api/global/users/search`,
|
url: `/api/global/users/search`,
|
||||||
body: opts,
|
body: opts,
|
||||||
|
@ -133,7 +138,7 @@ export const buildUserEndpoints = API => ({
|
||||||
* @param builder whether the user should be a global builder
|
* @param builder whether the user should be a global builder
|
||||||
* @param admin whether the user should be a global admin
|
* @param admin whether the user should be a global admin
|
||||||
*/
|
*/
|
||||||
inviteUser: async ({ email, builder, admin }) => {
|
inviteUser: async ({ email, builder, admin, apps }) => {
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: "/api/global/users/invite",
|
url: "/api/global/users/invite",
|
||||||
body: {
|
body: {
|
||||||
|
@ -141,11 +146,43 @@ export const buildUserEndpoints = API => ({
|
||||||
userInfo: {
|
userInfo: {
|
||||||
admin: admin ? { global: true } : undefined,
|
admin: admin ? { global: true } : undefined,
|
||||||
builder: builder ? { global: true } : undefined,
|
builder: builder ? { global: true } : undefined,
|
||||||
|
apps: apps ? apps : undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onboardUsers: async payload => {
|
||||||
|
return await API.post({
|
||||||
|
url: "/api/global/users/onboard",
|
||||||
|
body: payload.map(invite => {
|
||||||
|
const { email, admin, builder, apps } = invite
|
||||||
|
return {
|
||||||
|
email,
|
||||||
|
userInfo: {
|
||||||
|
admin: admin ? { global: true } : undefined,
|
||||||
|
builder: builder ? { global: true } : undefined,
|
||||||
|
apps: apps ? apps : undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts a user invite as a body and will update the associated app roles.
|
||||||
|
* for an existing invite
|
||||||
|
* @param invite the invite code sent in the email
|
||||||
|
*/
|
||||||
|
updateUserInvite: async invite => {
|
||||||
|
await API.post({
|
||||||
|
url: `/api/global/users/invite/update/${invite.code}`,
|
||||||
|
body: {
|
||||||
|
apps: invite.apps,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the invitation associated with a provided code.
|
* Retrieves the invitation associated with a provided code.
|
||||||
* @param code The unique code for the target invite
|
* @param code The unique code for the target invite
|
||||||
|
@ -156,6 +193,16 @@ export const buildUserEndpoints = API => ({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the invitation associated with a provided code.
|
||||||
|
* @param code The unique code for the target invite
|
||||||
|
*/
|
||||||
|
getUserInvites: async () => {
|
||||||
|
return await API.get({
|
||||||
|
url: `/api/global/users/invites`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invites multiple users to the current tenant.
|
* Invites multiple users to the current tenant.
|
||||||
* @param users An array of users to invite
|
* @param users An array of users to invite
|
||||||
|
@ -169,6 +216,7 @@ export const buildUserEndpoints = API => ({
|
||||||
admin: user.admin ? { global: true } : undefined,
|
admin: user.admin ? { global: true } : undefined,
|
||||||
builder: user.admin || user.builder ? { global: true } : undefined,
|
builder: user.admin || user.builder ? { global: true } : undefined,
|
||||||
userGroups: user.groups,
|
userGroups: user.groups,
|
||||||
|
roles: user.apps ? user.apps : undefined,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
|
@ -35,6 +35,7 @@ export default class UserFetch extends DataFetch {
|
||||||
page: cursor,
|
page: cursor,
|
||||||
email: query.email,
|
email: query.email,
|
||||||
appId: query.appId,
|
appId: query.appId,
|
||||||
|
paginated: query.paginated,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
rows: res?.data || [],
|
rows: res?.data || [],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -172,6 +172,9 @@ module FetchMock {
|
||||||
),
|
),
|
||||||
ok: true,
|
ok: true,
|
||||||
})
|
})
|
||||||
|
} else if (url === "https://www.googleapis.com/oauth2/v4/token") {
|
||||||
|
// any valid response
|
||||||
|
return json({})
|
||||||
} else if (url.includes("failonce.com")) {
|
} else if (url.includes("failonce.com")) {
|
||||||
failCount++
|
failCount++
|
||||||
if (failCount === 1) {
|
if (failCount === 1) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -43,11 +43,11 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.3.18-alpha.14",
|
"@budibase/backend-core": "2.3.18-alpha.17",
|
||||||
"@budibase/client": "2.3.18-alpha.14",
|
"@budibase/client": "2.3.18-alpha.17",
|
||||||
"@budibase/pro": "2.3.18-alpha.14",
|
"@budibase/pro": "2.3.18-alpha.17",
|
||||||
"@budibase/string-templates": "2.3.18-alpha.14",
|
"@budibase/string-templates": "2.3.18-alpha.17",
|
||||||
"@budibase/types": "2.3.18-alpha.14",
|
"@budibase/types": "2.3.18-alpha.17",
|
||||||
"@bull-board/api": "3.7.0",
|
"@bull-board/api": "3.7.0",
|
||||||
"@bull-board/koa": "3.9.4",
|
"@bull-board/koa": "3.9.4",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { OAuth2Client } from "google-auth-library"
|
||||||
import { buildExternalTableId } from "./utils"
|
import { buildExternalTableId } from "./utils"
|
||||||
import { DataSourceOperation, FieldTypes } from "../constants"
|
import { DataSourceOperation, FieldTypes } from "../constants"
|
||||||
import { GoogleSpreadsheet } from "google-spreadsheet"
|
import { GoogleSpreadsheet } from "google-spreadsheet"
|
||||||
|
import fetch from "node-fetch"
|
||||||
import { configs, HTTPError } from "@budibase/backend-core"
|
import { configs, HTTPError } from "@budibase/backend-core"
|
||||||
const fetch = require("node-fetch")
|
|
||||||
|
|
||||||
interface GoogleSheetsConfig {
|
interface GoogleSheetsConfig {
|
||||||
spreadsheetId: string
|
spreadsheetId: string
|
||||||
|
@ -111,7 +111,7 @@ const SCHEMA: Integration = {
|
||||||
|
|
||||||
class GoogleSheetsIntegration implements DatasourcePlus {
|
class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
private readonly config: GoogleSheetsConfig
|
private readonly config: GoogleSheetsConfig
|
||||||
private client: any
|
private client: GoogleSpreadsheet
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, Table> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
async connect() {
|
async connect() {
|
||||||
try {
|
try {
|
||||||
// Initialise oAuth client
|
// Initialise oAuth client
|
||||||
let googleConfig = await configs.getGoogleConfig()
|
let googleConfig = await configs.getGoogleDatasourceConfig()
|
||||||
if (!googleConfig) {
|
if (!googleConfig) {
|
||||||
throw new HTTPError("Google config not found", 400)
|
throw new HTTPError("Google config not found", 400)
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
|
|
||||||
async buildSchema(datasourceId: string) {
|
async buildSchema(datasourceId: string) {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheets = await this.client.sheetsByIndex
|
const sheets = this.client.sheetsByIndex
|
||||||
const tables: Record<string, Table> = {}
|
const tables: Record<string, Table> = {}
|
||||||
for (let sheet of sheets) {
|
for (let sheet of sheets) {
|
||||||
// must fetch rows to determine schema
|
// must fetch rows to determine schema
|
||||||
|
@ -286,7 +286,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
async updateTable(table?: any) {
|
async updateTable(table?: any) {
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheet = await this.client.sheetsByTitle[table.name]
|
const sheet = this.client.sheetsByTitle[table.name]
|
||||||
await sheet.loadHeaderRow()
|
await sheet.loadHeaderRow()
|
||||||
|
|
||||||
if (table._rename) {
|
if (table._rename) {
|
||||||
|
@ -300,10 +300,17 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
await sheet.setHeaderRow(headers)
|
await sheet.setHeaderRow(headers)
|
||||||
} else {
|
} else {
|
||||||
let newField = Object.keys(table.schema).find(
|
const updatedHeaderValues = [...sheet.headerValues]
|
||||||
|
|
||||||
|
const newField = Object.keys(table.schema).find(
|
||||||
key => !sheet.headerValues.includes(key)
|
key => !sheet.headerValues.includes(key)
|
||||||
)
|
)
|
||||||
await sheet.setHeaderRow([...sheet.headerValues, newField])
|
|
||||||
|
if (newField) {
|
||||||
|
updatedHeaderValues.push(newField)
|
||||||
|
}
|
||||||
|
|
||||||
|
await sheet.setHeaderRow(updatedHeaderValues)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error updating table in google sheets", err)
|
console.error("Error updating table in google sheets", err)
|
||||||
|
@ -314,7 +321,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
async deleteTable(sheet: any) {
|
async deleteTable(sheet: any) {
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheetToDelete = await this.client.sheetsByTitle[sheet]
|
const sheetToDelete = this.client.sheetsByTitle[sheet]
|
||||||
return await sheetToDelete.delete()
|
return await sheetToDelete.delete()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error deleting table in google sheets", err)
|
console.error("Error deleting table in google sheets", err)
|
||||||
|
@ -325,7 +332,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
async create(query: { sheet: string; row: any }) {
|
async create(query: { sheet: string; row: any }) {
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
||||||
const rowToInsert =
|
const rowToInsert =
|
||||||
typeof query.row === "string" ? JSON.parse(query.row) : query.row
|
typeof query.row === "string" ? JSON.parse(query.row) : query.row
|
||||||
const row = await sheet.addRow(rowToInsert)
|
const row = await sheet.addRow(rowToInsert)
|
||||||
|
@ -341,7 +348,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
async read(query: { sheet: string }) {
|
async read(query: { sheet: string }) {
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
||||||
const rows = await sheet.getRows()
|
const rows = await sheet.getRows()
|
||||||
const headerValues = sheet.headerValues
|
const headerValues = sheet.headerValues
|
||||||
const response = []
|
const response = []
|
||||||
|
@ -360,7 +367,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
async update(query: { sheet: string; rowIndex: number; row: any }) {
|
async update(query: { sheet: string; rowIndex: number; row: any }) {
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
||||||
const rows = await sheet.getRows()
|
const rows = await sheet.getRows()
|
||||||
const row = rows[query.rowIndex]
|
const row = rows[query.rowIndex]
|
||||||
if (row) {
|
if (row) {
|
||||||
|
@ -384,7 +391,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
|
|
||||||
async delete(query: { sheet: string; rowIndex: number }) {
|
async delete(query: { sheet: string; rowIndex: number }) {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
||||||
const rows = await sheet.getRows()
|
const rows = await sheet.getRows()
|
||||||
const row = rows[query.rowIndex]
|
const row = rows[query.rowIndex]
|
||||||
if (row) {
|
if (row) {
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import type { GoogleSpreadsheetWorksheet } from "google-spreadsheet"
|
||||||
|
|
||||||
|
jest.mock("google-auth-library")
|
||||||
|
const { OAuth2Client } = require("google-auth-library")
|
||||||
|
|
||||||
|
const setCredentialsMock = jest.fn()
|
||||||
|
const getAccessTokenMock = jest.fn()
|
||||||
|
|
||||||
|
OAuth2Client.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
setCredentials: setCredentialsMock,
|
||||||
|
getAccessToken: getAccessTokenMock,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.mock("google-spreadsheet")
|
||||||
|
const { GoogleSpreadsheet } = require("google-spreadsheet")
|
||||||
|
|
||||||
|
const sheetsByTitle: { [title: string]: GoogleSpreadsheetWorksheet } = {}
|
||||||
|
|
||||||
|
GoogleSpreadsheet.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
useOAuth2Client: jest.fn(),
|
||||||
|
loadInfo: jest.fn(),
|
||||||
|
sheetsByTitle,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
import { structures } from "@budibase/backend-core/tests"
|
||||||
|
import TestConfiguration from "../../tests/utilities/TestConfiguration"
|
||||||
|
import GoogleSheetsIntegration from "../googlesheets"
|
||||||
|
import { FieldType, Table, TableSchema } from "../../../../types/src/documents"
|
||||||
|
|
||||||
|
describe("Google Sheets Integration", () => {
|
||||||
|
let integration: any,
|
||||||
|
config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
integration = new GoogleSheetsIntegration.integration({
|
||||||
|
spreadsheetId: "randomId",
|
||||||
|
auth: {
|
||||||
|
appId: "appId",
|
||||||
|
accessToken: "accessToken",
|
||||||
|
refreshToken: "refreshToken",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await config.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
function createBasicTable(name: string, columns: string[]): Table {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
schema: {
|
||||||
|
...columns.reduce((p, c) => {
|
||||||
|
p[c] = {
|
||||||
|
name: c,
|
||||||
|
type: FieldType.STRING,
|
||||||
|
constraints: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}, {} as TableSchema),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSheet({
|
||||||
|
headerValues,
|
||||||
|
}: {
|
||||||
|
headerValues: string[]
|
||||||
|
}): GoogleSpreadsheetWorksheet {
|
||||||
|
return {
|
||||||
|
// to ignore the unmapped fields
|
||||||
|
...({} as any),
|
||||||
|
loadHeaderRow: jest.fn(),
|
||||||
|
headerValues,
|
||||||
|
setHeaderRow: jest.fn(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("update table", () => {
|
||||||
|
test("adding a new field will be adding a new header row", async () => {
|
||||||
|
await config.doInContext(structures.uuid(), async () => {
|
||||||
|
const tableColumns = ["name", "description", "new field"]
|
||||||
|
const table = createBasicTable(structures.uuid(), tableColumns)
|
||||||
|
|
||||||
|
const sheet = createSheet({ headerValues: ["name", "description"] })
|
||||||
|
sheetsByTitle[table.name] = sheet
|
||||||
|
await integration.updateTable(table)
|
||||||
|
|
||||||
|
expect(sheet.loadHeaderRow).toBeCalledTimes(1)
|
||||||
|
expect(sheet.setHeaderRow).toBeCalledTimes(1)
|
||||||
|
expect(sheet.setHeaderRow).toBeCalledWith(tableColumns)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("removing an existing field will not remove the data from the spreadsheet", async () => {
|
||||||
|
await config.doInContext(structures.uuid(), async () => {
|
||||||
|
const tableColumns = ["name"]
|
||||||
|
const table = createBasicTable(structures.uuid(), tableColumns)
|
||||||
|
|
||||||
|
const sheet = createSheet({
|
||||||
|
headerValues: ["name", "description", "location"],
|
||||||
|
})
|
||||||
|
sheetsByTitle[table.name] = sheet
|
||||||
|
await integration.updateTable(table)
|
||||||
|
|
||||||
|
expect(sheet.loadHeaderRow).toBeCalledTimes(1)
|
||||||
|
expect(sheet.setHeaderRow).toBeCalledTimes(1)
|
||||||
|
expect(sheet.setHeaderRow).toBeCalledWith([
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"location",
|
||||||
|
])
|
||||||
|
|
||||||
|
// No undefineds are sent
|
||||||
|
expect((sheet.setHeaderRow as any).mock.calls[0][0]).toHaveLength(3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1278,14 +1278,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.3.18-alpha.14":
|
"@budibase/backend-core@2.3.18-alpha.17":
|
||||||
version "2.3.18-alpha.14"
|
version "2.3.18-alpha.17"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.18-alpha.14.tgz#84c10d5840a61437c77c62cb27aa52a48ebea34c"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.18-alpha.17.tgz#4b887d161a0ad6f21bf8f582417cfec90c2c417f"
|
||||||
integrity sha512-8MlNAJNFhct4CwN49wu7EBZJQyToSnUlhZeBlvv94AQxKF6iTzTq40CoNIMCv69nzbeTbDw/ImG7dPW8kBHjpg==
|
integrity sha512-tXza/NP4pA08FjIyToPqJSQcYFL03RFJRdeT6aA0u8Ibd6ASSXZ/iVw7t0VEk57S4G7fftXA5e6Q3XX8PRR2+A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.1"
|
"@budibase/nano" "10.1.1"
|
||||||
"@budibase/pouchdb-replication-stream" "1.2.10"
|
"@budibase/pouchdb-replication-stream" "1.2.10"
|
||||||
"@budibase/types" "2.3.18-alpha.14"
|
"@budibase/types" "2.3.18-alpha.17"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@shopify/jest-koa-mocks" "5.0.1"
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
"@techpass/passport-openidconnect" "0.3.2"
|
||||||
aws-cloudfront-sign "2.2.0"
|
aws-cloudfront-sign "2.2.0"
|
||||||
|
@ -1367,6 +1367,31 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
|
"@budibase/handlebars-helpers@^0.11.8":
|
||||||
|
version "0.11.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.8.tgz#6953d29673a8c5c407e096c0a84890465c7ce841"
|
||||||
|
integrity sha512-ggWJUt0GqsHFAEup5tlWlcrmYML57nKhpNGGLzVsqXVYN8eVmf3xluYmmMe7fDYhQH0leSprrdEXmsdFQF3HAQ==
|
||||||
|
dependencies:
|
||||||
|
array-sort "^1.0.0"
|
||||||
|
define-property "^2.0.2"
|
||||||
|
extend-shallow "^3.0.2"
|
||||||
|
for-in "^1.0.2"
|
||||||
|
get-object "^0.2.0"
|
||||||
|
get-value "^3.0.1"
|
||||||
|
handlebars "^4.7.7"
|
||||||
|
handlebars-utils "^1.0.6"
|
||||||
|
has-value "^2.0.2"
|
||||||
|
helper-md "^0.2.2"
|
||||||
|
html-tag "^2.0.0"
|
||||||
|
is-even "^1.0.0"
|
||||||
|
is-glob "^4.0.1"
|
||||||
|
kind-of "^6.0.3"
|
||||||
|
micromatch "^3.1.5"
|
||||||
|
relative "^3.0.2"
|
||||||
|
striptags "^3.1.1"
|
||||||
|
to-gfm-code-block "^0.1.1"
|
||||||
|
year "^0.2.1"
|
||||||
|
|
||||||
"@budibase/nano@10.1.1":
|
"@budibase/nano@10.1.1":
|
||||||
version "10.1.1"
|
version "10.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/nano/-/nano-10.1.1.tgz#36ccda4d9bb64b5ee14dd2b27a295b40739b1038"
|
resolved "https://registry.yarnpkg.com/@budibase/nano/-/nano-10.1.1.tgz#36ccda4d9bb64b5ee14dd2b27a295b40739b1038"
|
||||||
|
@ -1392,18 +1417,20 @@
|
||||||
pouchdb-promise "^6.0.4"
|
pouchdb-promise "^6.0.4"
|
||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
|
|
||||||
"@budibase/pro@2.3.18-alpha.14":
|
"@budibase/pro@2.3.18-alpha.17":
|
||||||
version "2.3.18-alpha.14"
|
version "2.3.18-alpha.17"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.18-alpha.14.tgz#037045c7c315c23d2981abdcd35fb18bc1a4727d"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.18-alpha.17.tgz#4eceff71514ffa5254082bf5b15f9f82f5b1bdf4"
|
||||||
integrity sha512-15OFi/Kycp8lYTP424fMoF1l+l3M/0pcDOHpkn0sHix4KhQz2mnqE2iu3k1FR/w9tpPEGHwdd4++BHiDw6h1ZQ==
|
integrity sha512-Xsk3kw1MnwGtWI3aNheOdmphSjRjwh0dx/VGTP6MHiFObCKYUxrxm6VmzS9ysw5HxCfyRG8Y2w2EuXoJ08jxtQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.3.18-alpha.14"
|
"@budibase/backend-core" "2.3.18-alpha.17"
|
||||||
"@budibase/types" "2.3.18-alpha.14"
|
"@budibase/string-templates" "2.3.18-alpha.14"
|
||||||
|
"@budibase/types" "2.3.18-alpha.17"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
jsonwebtoken "8.5.1"
|
jsonwebtoken "8.5.1"
|
||||||
lru-cache "^7.14.1"
|
lru-cache "^7.14.1"
|
||||||
|
memorystream "^0.3.1"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
|
||||||
"@budibase/standard-components@^0.9.139":
|
"@budibase/standard-components@^0.9.139":
|
||||||
|
@ -1424,10 +1451,22 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/types@2.3.18-alpha.14":
|
"@budibase/string-templates@2.3.18-alpha.14":
|
||||||
version "2.3.18-alpha.14"
|
version "2.3.18-alpha.14"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.18-alpha.14.tgz#3fa32f0b262169c4c8679f38f5e0e321f43f54dc"
|
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-2.3.18-alpha.14.tgz#c3b8d45ced321088c76bcda4efd7e9c7635a2788"
|
||||||
integrity sha512-mkp0GAqB7zAeLSNV8//dBjSH9dP9z8Q/Sxv29zlExKAxBHlUcYIB442QZJ8Z2V8Tzb+DlRIK7SLTG622cfyUgg==
|
integrity sha512-xamfugDHgvzupe3EkvTY7ymXn9cRxb61nKaap52NsQQl8Zby2W2qJNVBNnuSnhnkQQeF5EatIFgGni+yBDchtQ==
|
||||||
|
dependencies:
|
||||||
|
"@budibase/handlebars-helpers" "^0.11.8"
|
||||||
|
dayjs "^1.10.4"
|
||||||
|
handlebars "^4.7.6"
|
||||||
|
handlebars-utils "^1.0.6"
|
||||||
|
lodash "^4.17.20"
|
||||||
|
vm2 "^3.9.4"
|
||||||
|
|
||||||
|
"@budibase/types@2.3.18-alpha.17":
|
||||||
|
version "2.3.18-alpha.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.18-alpha.17.tgz#603a1374b601720ed39e047367fbb71fb5a1c51f"
|
||||||
|
integrity sha512-e+hJBt7LxbOjEcjklfNlzn59yODAgdjd3nhH8d/7Mv9q1tcp92ssjN6gLcFCuNSvFdH861J/ln0ApbkcNAOeTg==
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
|
@ -4230,7 +4269,7 @@ arg@^4.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||||
|
|
||||||
argparse@^1.0.7:
|
argparse@^1.0.10, argparse@^1.0.7:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||||
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
||||||
|
@ -4277,6 +4316,15 @@ array-equal@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
|
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
|
||||||
integrity sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==
|
integrity sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==
|
||||||
|
|
||||||
|
array-sort@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a"
|
||||||
|
integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==
|
||||||
|
dependencies:
|
||||||
|
default-compare "^1.0.0"
|
||||||
|
get-value "^2.0.6"
|
||||||
|
kind-of "^5.0.2"
|
||||||
|
|
||||||
array-union@^2.1.0:
|
array-union@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
|
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
|
||||||
|
@ -4420,6 +4468,13 @@ atomic-sleep@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
|
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
|
||||||
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
|
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
|
||||||
|
|
||||||
|
autolinker@~0.28.0:
|
||||||
|
version "0.28.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47"
|
||||||
|
integrity sha512-zQAFO1Dlsn69eXaO6+7YZc+v84aquQKbwpzCE3L0stj56ERn9hutFxPopViLjo9G+rWwjozRhgS5KJ25Xy19cQ==
|
||||||
|
dependencies:
|
||||||
|
gulp-header "^1.7.1"
|
||||||
|
|
||||||
available-typed-arrays@^1.0.5:
|
available-typed-arrays@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||||
|
@ -5500,6 +5555,13 @@ concat-map@0.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||||
|
|
||||||
|
concat-with-sourcemaps@*:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e"
|
||||||
|
integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==
|
||||||
|
dependencies:
|
||||||
|
source-map "^0.6.1"
|
||||||
|
|
||||||
condense-newlines@^0.2.1:
|
condense-newlines@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f"
|
resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f"
|
||||||
|
@ -5941,6 +6003,13 @@ deepmerge@^4.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||||
|
|
||||||
|
default-compare@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f"
|
||||||
|
integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==
|
||||||
|
dependencies:
|
||||||
|
kind-of "^5.0.2"
|
||||||
|
|
||||||
default-shell@^1.0.0:
|
default-shell@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/default-shell/-/default-shell-1.0.1.tgz#752304bddc6174f49eb29cb988feea0b8813c8bc"
|
resolved "https://registry.yarnpkg.com/default-shell/-/default-shell-1.0.1.tgz#752304bddc6174f49eb29cb988feea0b8813c8bc"
|
||||||
|
@ -6417,6 +6486,11 @@ enhanced-resolve@^5.9.3:
|
||||||
graceful-fs "^4.2.4"
|
graceful-fs "^4.2.4"
|
||||||
tapable "^2.2.0"
|
tapable "^2.2.0"
|
||||||
|
|
||||||
|
ent@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
|
||||||
|
integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==
|
||||||
|
|
||||||
entities@~2.1.0:
|
entities@~2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||||
|
@ -7617,6 +7691,14 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
has-symbols "^1.0.3"
|
has-symbols "^1.0.3"
|
||||||
|
|
||||||
|
get-object@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c"
|
||||||
|
integrity sha512-7P6y6k6EzEFmO/XyUyFlXm1YLJy9xeA1x/grNV8276abX5GuwUtYgKFkRFkLixw4hf4Pz9q2vgv/8Ar42R0HuQ==
|
||||||
|
dependencies:
|
||||||
|
is-number "^2.0.2"
|
||||||
|
isobject "^0.2.0"
|
||||||
|
|
||||||
get-package-type@^0.1.0:
|
get-package-type@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||||
|
@ -7679,6 +7761,13 @@ get-value@^2.0.3, get-value@^2.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||||
integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==
|
integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==
|
||||||
|
|
||||||
|
get-value@^3.0.0, get-value@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8"
|
||||||
|
integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==
|
||||||
|
dependencies:
|
||||||
|
isobject "^3.0.1"
|
||||||
|
|
||||||
getopts@2.3.0:
|
getopts@2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4"
|
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4"
|
||||||
|
@ -7966,7 +8055,24 @@ gtoken@^5.0.4:
|
||||||
google-p12-pem "^3.1.3"
|
google-p12-pem "^3.1.3"
|
||||||
jws "^4.0.0"
|
jws "^4.0.0"
|
||||||
|
|
||||||
handlebars@^4.7.7:
|
gulp-header@^1.7.1:
|
||||||
|
version "1.8.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84"
|
||||||
|
integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==
|
||||||
|
dependencies:
|
||||||
|
concat-with-sourcemaps "*"
|
||||||
|
lodash.template "^4.4.0"
|
||||||
|
through2 "^2.0.0"
|
||||||
|
|
||||||
|
handlebars-utils@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9"
|
||||||
|
integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw==
|
||||||
|
dependencies:
|
||||||
|
kind-of "^6.0.0"
|
||||||
|
typeof-article "^0.1.1"
|
||||||
|
|
||||||
|
handlebars@^4.7.6, handlebars@^4.7.7:
|
||||||
version "4.7.7"
|
version "4.7.7"
|
||||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
|
||||||
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
|
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
|
||||||
|
@ -8060,6 +8166,14 @@ has-value@^1.0.0:
|
||||||
has-values "^1.0.0"
|
has-values "^1.0.0"
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
|
|
||||||
|
has-value@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658"
|
||||||
|
integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA==
|
||||||
|
dependencies:
|
||||||
|
get-value "^3.0.0"
|
||||||
|
has-values "^2.0.1"
|
||||||
|
|
||||||
has-values@^0.1.4:
|
has-values@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
|
resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
|
||||||
|
@ -8073,6 +8187,13 @@ has-values@^1.0.0:
|
||||||
is-number "^3.0.0"
|
is-number "^3.0.0"
|
||||||
kind-of "^4.0.0"
|
kind-of "^4.0.0"
|
||||||
|
|
||||||
|
has-values@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d"
|
||||||
|
integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w==
|
||||||
|
dependencies:
|
||||||
|
kind-of "^6.0.2"
|
||||||
|
|
||||||
has-yarn@^2.1.0:
|
has-yarn@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
|
resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
|
||||||
|
@ -8085,6 +8206,16 @@ has@^1.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
|
helper-md@^0.2.2:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f"
|
||||||
|
integrity sha512-49TaQzK+Ic7ZVTq4i1UZxRUJEmAilTk8hz7q4I0WNUaTclLR8ArJV5B3A1fe1xF2HtsDTr2gYKLaVTof/Lt84Q==
|
||||||
|
dependencies:
|
||||||
|
ent "^2.2.0"
|
||||||
|
extend-shallow "^2.0.1"
|
||||||
|
fs-exists-sync "^0.1.0"
|
||||||
|
remarkable "^1.6.2"
|
||||||
|
|
||||||
hexoid@^1.0.0:
|
hexoid@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||||
|
@ -8119,6 +8250,14 @@ html-escaper@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||||
|
|
||||||
|
html-tag@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed"
|
||||||
|
integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g==
|
||||||
|
dependencies:
|
||||||
|
is-self-closing "^1.0.1"
|
||||||
|
kind-of "^6.0.0"
|
||||||
|
|
||||||
http-assert@^1.3.0:
|
http-assert@^1.3.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f"
|
resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f"
|
||||||
|
@ -8595,6 +8734,13 @@ is-docker@^2.0.0, is-docker@^2.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
|
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
|
||||||
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
|
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
|
||||||
|
|
||||||
|
is-even@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06"
|
||||||
|
integrity sha512-LEhnkAdJqic4Dbqn58A0y52IXoHWlsueqQkKfMfdEnIYG8A1sm/GHidKkS6yvXlMoRrkM34csHnXQtOqcb+Jzg==
|
||||||
|
dependencies:
|
||||||
|
is-odd "^0.1.2"
|
||||||
|
|
||||||
is-extendable@^0.1.0, is-extendable@^0.1.1:
|
is-extendable@^0.1.0, is-extendable@^0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
|
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
|
||||||
|
@ -8701,6 +8847,13 @@ is-number-object@^1.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-tostringtag "^1.0.0"
|
has-tostringtag "^1.0.0"
|
||||||
|
|
||||||
|
is-number@^2.0.2:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
|
||||||
|
integrity sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==
|
||||||
|
dependencies:
|
||||||
|
kind-of "^3.0.2"
|
||||||
|
|
||||||
is-number@^3.0.0:
|
is-number@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
|
||||||
|
@ -8723,6 +8876,13 @@ is-object@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf"
|
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf"
|
||||||
integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==
|
integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==
|
||||||
|
|
||||||
|
is-odd@^0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7"
|
||||||
|
integrity sha512-Ri7C2K7o5IrUU9UEI8losXJCCD/UtsaIrkR5sxIcFg4xQ9cRJXlWA5DQvTE0yDc0krvSNLsRGXN11UPS6KyfBw==
|
||||||
|
dependencies:
|
||||||
|
is-number "^3.0.0"
|
||||||
|
|
||||||
is-path-inside@^3.0.2:
|
is-path-inside@^3.0.2:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||||
|
@ -8763,6 +8923,13 @@ is-retry-allowed@^2.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d"
|
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d"
|
||||||
integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==
|
integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==
|
||||||
|
|
||||||
|
is-self-closing@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4"
|
||||||
|
integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg==
|
||||||
|
dependencies:
|
||||||
|
self-closing-tags "^1.0.1"
|
||||||
|
|
||||||
is-shared-array-buffer@^1.0.2:
|
is-shared-array-buffer@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
|
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
|
||||||
|
@ -8878,6 +9045,11 @@ isexe@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||||
|
|
||||||
|
isobject@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e"
|
||||||
|
integrity sha512-VaWq6XYAsbvM0wf4dyBO7WH9D7GosB7ZZlqrawI9BBiTMINBeCyqSKBa35m870MY3O4aM31pYyZi9DfGrYMJrQ==
|
||||||
|
|
||||||
isobject@^2.0.0:
|
isobject@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
|
||||||
|
@ -10072,7 +10244,7 @@ keyv@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer "3.0.0"
|
json-buffer "3.0.0"
|
||||||
|
|
||||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||||
integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==
|
integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==
|
||||||
|
@ -10086,12 +10258,12 @@ kind-of@^4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-buffer "^1.1.5"
|
is-buffer "^1.1.5"
|
||||||
|
|
||||||
kind-of@^5.0.0:
|
kind-of@^5.0.0, kind-of@^5.0.2:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
|
||||||
integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
|
integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
|
||||||
|
|
||||||
kind-of@^6.0.0, kind-of@^6.0.2:
|
kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
|
||||||
version "6.0.3"
|
version "6.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||||
|
@ -10518,6 +10690,11 @@ locate-path@^5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^4.1.0"
|
p-locate "^4.1.0"
|
||||||
|
|
||||||
|
lodash._reinterpolate@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
|
integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==
|
||||||
|
|
||||||
lodash.camelcase@^4.3.0:
|
lodash.camelcase@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||||
|
@ -10628,6 +10805,21 @@ lodash.sortby@^4.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||||
integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
|
integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
|
||||||
|
|
||||||
|
lodash.template@^4.4.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
|
||||||
|
integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
|
||||||
|
dependencies:
|
||||||
|
lodash._reinterpolate "^3.0.0"
|
||||||
|
lodash.templatesettings "^4.0.0"
|
||||||
|
|
||||||
|
lodash.templatesettings@^4.0.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
|
||||||
|
integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
|
||||||
|
dependencies:
|
||||||
|
lodash._reinterpolate "^3.0.0"
|
||||||
|
|
||||||
lodash.uniq@^4.5.0:
|
lodash.uniq@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||||
|
@ -10643,7 +10835,7 @@ lodash.xor@^4.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.xor/-/lodash.xor-4.5.0.tgz#4d48ed7e98095b0632582ba714d3ff8ae8fb1db6"
|
resolved "https://registry.yarnpkg.com/lodash.xor/-/lodash.xor-4.5.0.tgz#4d48ed7e98095b0632582ba714d3ff8ae8fb1db6"
|
||||||
integrity sha512-sVN2zimthq7aZ5sPGXnSz32rZPuqcparVW50chJQe+mzTYV+IsxSsl/2gnkWWE2Of7K3myBQBqtLKOUEHJKRsQ==
|
integrity sha512-sVN2zimthq7aZ5sPGXnSz32rZPuqcparVW50chJQe+mzTYV+IsxSsl/2gnkWWE2Of7K3myBQBqtLKOUEHJKRsQ==
|
||||||
|
|
||||||
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.3:
|
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
@ -10875,7 +11067,7 @@ memory-pager@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
|
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
|
||||||
integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
|
integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
|
||||||
|
|
||||||
memorystream@0.3.1:
|
memorystream@0.3.1, memorystream@^0.3.1:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
|
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
|
||||||
integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==
|
integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==
|
||||||
|
@ -10900,7 +11092,7 @@ methods@^1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||||
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
|
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
|
||||||
|
|
||||||
micromatch@^3.1.10, micromatch@^3.1.4:
|
micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.5:
|
||||||
version "3.1.10"
|
version "3.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
||||||
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
|
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
|
||||||
|
@ -13096,6 +13288,21 @@ relative-microtime@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/relative-microtime/-/relative-microtime-2.0.0.tgz#cceed2af095ecd72ea32011279c79e5fcc7de29b"
|
resolved "https://registry.yarnpkg.com/relative-microtime/-/relative-microtime-2.0.0.tgz#cceed2af095ecd72ea32011279c79e5fcc7de29b"
|
||||||
integrity sha512-l18ha6HEZc+No/uK4GyAnNxgKW7nvEe35IaeN54sShMojtqik2a6GbTyuiezkjpPaqP874Z3lW5ysBo5irz4NA==
|
integrity sha512-l18ha6HEZc+No/uK4GyAnNxgKW7nvEe35IaeN54sShMojtqik2a6GbTyuiezkjpPaqP874Z3lW5ysBo5irz4NA==
|
||||||
|
|
||||||
|
relative@^3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f"
|
||||||
|
integrity sha512-Q5W2qeYtY9GbiR8z1yHNZ1DGhyjb4AnLEjt8iE6XfcC1QIu+FAtj3HQaO0wH28H1mX6cqNLvAqWhP402dxJGyA==
|
||||||
|
dependencies:
|
||||||
|
isobject "^2.0.0"
|
||||||
|
|
||||||
|
remarkable@^1.6.2:
|
||||||
|
version "1.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00"
|
||||||
|
integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==
|
||||||
|
dependencies:
|
||||||
|
argparse "^1.0.10"
|
||||||
|
autolinker "~0.28.0"
|
||||||
|
|
||||||
remove-trailing-separator@^1.0.1:
|
remove-trailing-separator@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||||
|
@ -13444,6 +13651,11 @@ seek-bzip@^1.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "^2.8.1"
|
commander "^2.8.1"
|
||||||
|
|
||||||
|
self-closing-tags@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d"
|
||||||
|
integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA==
|
||||||
|
|
||||||
semver-compare@^1.0.0:
|
semver-compare@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||||
|
@ -14232,6 +14444,11 @@ strip-outer@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp "^1.0.2"
|
escape-string-regexp "^1.0.2"
|
||||||
|
|
||||||
|
striptags@^3.1.1:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052"
|
||||||
|
integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==
|
||||||
|
|
||||||
style-loader@^3.3.1:
|
style-loader@^3.3.1:
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
|
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
|
||||||
|
@ -14716,6 +14933,11 @@ to-fast-properties@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||||
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
|
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
|
||||||
|
|
||||||
|
to-gfm-code-block@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82"
|
||||||
|
integrity sha512-LQRZWyn8d5amUKnfR9A9Uu7x9ss7Re8peuWR2gkh1E+ildOfv2aF26JpuDg8JtvCduu5+hOrMIH+XstZtnagqg==
|
||||||
|
|
||||||
to-json-schema@0.2.5:
|
to-json-schema@0.2.5:
|
||||||
version "0.2.5"
|
version "0.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/to-json-schema/-/to-json-schema-0.2.5.tgz#ef3c3f11ad64460dcfbdbafd0fd525d69d62a98f"
|
resolved "https://registry.yarnpkg.com/to-json-schema/-/to-json-schema-0.2.5.tgz#ef3c3f11ad64460dcfbdbafd0fd525d69d62a98f"
|
||||||
|
@ -14984,6 +15206,13 @@ typedarray-to-buffer@^3.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
|
typeof-article@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af"
|
||||||
|
integrity sha512-Vn42zdX3FhmUrzEmitX3iYyLb+Umwpmv8fkZRIknYh84lmdrwqZA5xYaoKiIj2Rc5i/5wcDrpUmZcbk1U51vTw==
|
||||||
|
dependencies:
|
||||||
|
kind-of "^3.1.0"
|
||||||
|
|
||||||
typeof@^1.0.0:
|
typeof@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/typeof/-/typeof-1.0.0.tgz#9c84403f2323ae5399167275497638ea1d2f2440"
|
resolved "https://registry.yarnpkg.com/typeof/-/typeof-1.0.0.tgz#9c84403f2323ae5399167275497638ea1d2f2440"
|
||||||
|
@ -15344,6 +15573,14 @@ vm2@3.9.11:
|
||||||
acorn "^8.7.0"
|
acorn "^8.7.0"
|
||||||
acorn-walk "^8.2.0"
|
acorn-walk "^8.2.0"
|
||||||
|
|
||||||
|
vm2@^3.9.4:
|
||||||
|
version "3.9.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.14.tgz#964042b474cf1e6e4f475a39144773cdb9deb734"
|
||||||
|
integrity sha512-HgvPHYHeQy8+QhzlFryvSteA4uQLBCOub02mgqdR+0bN/akRZ48TGB1v0aCv7ksyc0HXx16AZtMHKS38alc6TA==
|
||||||
|
dependencies:
|
||||||
|
acorn "^8.7.0"
|
||||||
|
acorn-walk "^8.2.0"
|
||||||
|
|
||||||
vuvuzela@1.0.3:
|
vuvuzela@1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b"
|
resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b"
|
||||||
|
@ -15905,6 +16142,11 @@ yauzl@^2.4.2:
|
||||||
buffer-crc32 "~0.2.3"
|
buffer-crc32 "~0.2.3"
|
||||||
fd-slicer "~1.1.0"
|
fd-slicer "~1.1.0"
|
||||||
|
|
||||||
|
year@^0.2.1:
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0"
|
||||||
|
integrity sha512-9GnJUZ0QM4OgXuOzsKNzTJ5EOkums1Xc+3YQXp+Q+UxFjf7zLucp9dQ8QMIft0Szs1E1hUiXFim1OYfEKFq97w==
|
||||||
|
|
||||||
ylru@^1.2.0:
|
ylru@^1.2.0:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785"
|
resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/types",
|
"name": "@budibase/types",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
@ -16,6 +16,7 @@ export interface BulkUserRequest {
|
||||||
userIds: string[]
|
userIds: string[]
|
||||||
}
|
}
|
||||||
create?: {
|
create?: {
|
||||||
|
roles?: any[]
|
||||||
users: User[]
|
users: User[]
|
||||||
groups: any[]
|
groups: any[]
|
||||||
}
|
}
|
||||||
|
@ -49,7 +50,7 @@ export interface SearchUsersRequest {
|
||||||
page?: string
|
page?: string
|
||||||
email?: string
|
email?: string
|
||||||
appId?: string
|
appId?: string
|
||||||
userIds?: string[]
|
paginated?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateAdminUserRequest {
|
export interface CreateAdminUserRequest {
|
||||||
|
|
|
@ -36,6 +36,9 @@ export interface SettingsConfig extends Config {
|
||||||
config: SettingsInnerConfig
|
config: SettingsInnerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SSOConfigType = ConfigType.GOOGLE | ConfigType.OIDC
|
||||||
|
export type SSOConfig = GoogleInnerConfig | OIDCInnerConfig
|
||||||
|
|
||||||
export interface GoogleInnerConfig {
|
export interface GoogleInnerConfig {
|
||||||
clientID: string
|
clientID: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
|
@ -60,6 +63,10 @@ export interface OIDCStrategyConfiguration {
|
||||||
callbackURL: string
|
callbackURL: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OIDCConfigs {
|
||||||
|
configs: OIDCInnerConfig[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface OIDCInnerConfig {
|
export interface OIDCInnerConfig {
|
||||||
configUrl: string
|
configUrl: string
|
||||||
clientID: string
|
clientID: string
|
||||||
|
@ -72,9 +79,7 @@ export interface OIDCInnerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OIDCConfig extends Config {
|
export interface OIDCConfig extends Config {
|
||||||
config: {
|
config: OIDCConfigs
|
||||||
configs: OIDCInnerConfig[]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OIDCWellKnownConfig {
|
export interface OIDCWellKnownConfig {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.3.18-alpha.14",
|
"version": "2.3.18-alpha.17",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.3.18-alpha.14",
|
"@budibase/backend-core": "2.3.18-alpha.17",
|
||||||
"@budibase/pro": "2.3.18-alpha.14",
|
"@budibase/pro": "2.3.18-alpha.17",
|
||||||
"@budibase/string-templates": "2.3.18-alpha.14",
|
"@budibase/string-templates": "2.3.18-alpha.17",
|
||||||
"@budibase/types": "2.3.18-alpha.14",
|
"@budibase/types": "2.3.18-alpha.17",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
"@sentry/node": "6.17.7",
|
"@sentry/node": "6.17.7",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
|
|
|
@ -17,10 +17,15 @@ import {
|
||||||
Ctx,
|
Ctx,
|
||||||
GetPublicOIDCConfigResponse,
|
GetPublicOIDCConfigResponse,
|
||||||
GetPublicSettingsResponse,
|
GetPublicSettingsResponse,
|
||||||
|
GoogleInnerConfig,
|
||||||
isGoogleConfig,
|
isGoogleConfig,
|
||||||
isOIDCConfig,
|
isOIDCConfig,
|
||||||
isSettingsConfig,
|
isSettingsConfig,
|
||||||
isSMTPConfig,
|
isSMTPConfig,
|
||||||
|
OIDCConfigs,
|
||||||
|
SettingsInnerConfig,
|
||||||
|
SSOConfig,
|
||||||
|
SSOConfigType,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as pro from "@budibase/pro"
|
import * as pro from "@budibase/pro"
|
||||||
|
@ -119,6 +124,61 @@ const getEventFns = async (config: Config, existing?: Config) => {
|
||||||
return fns
|
return fns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SSOConfigs = { [key in SSOConfigType]: SSOConfig | undefined }
|
||||||
|
|
||||||
|
async function getSSOConfigs(): Promise<SSOConfigs> {
|
||||||
|
const google = await configs.getGoogleConfig()
|
||||||
|
const oidc = await configs.getOIDCConfig()
|
||||||
|
return {
|
||||||
|
[ConfigType.GOOGLE]: google,
|
||||||
|
[ConfigType.OIDC]: oidc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hasActivatedConfig(ssoConfigs?: SSOConfigs) {
|
||||||
|
if (!ssoConfigs) {
|
||||||
|
ssoConfigs = await getSSOConfigs()
|
||||||
|
}
|
||||||
|
return !!Object.values(ssoConfigs).find(c => c?.activated)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifySettingsConfig(config: SettingsInnerConfig) {
|
||||||
|
if (config.isSSOEnforced) {
|
||||||
|
const valid = await hasActivatedConfig()
|
||||||
|
if (!valid) {
|
||||||
|
throw new Error("Cannot enforce SSO without an activated configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifySSOConfig(type: SSOConfigType, config: SSOConfig) {
|
||||||
|
const settings = await configs.getSettingsConfig()
|
||||||
|
if (settings.isSSOEnforced && !config.activated) {
|
||||||
|
// config is being saved as deactivated
|
||||||
|
// ensure there is at least one other activated sso config
|
||||||
|
const ssoConfigs = await getSSOConfigs()
|
||||||
|
|
||||||
|
// overwrite the config being updated
|
||||||
|
// to reflect the desired state
|
||||||
|
ssoConfigs[type] = config
|
||||||
|
|
||||||
|
const activated = await hasActivatedConfig(ssoConfigs)
|
||||||
|
if (!activated) {
|
||||||
|
throw new Error(
|
||||||
|
"Configuration cannot be deactivated while SSO is enforced"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyGoogleConfig(config: GoogleInnerConfig) {
|
||||||
|
await verifySSOConfig(ConfigType.GOOGLE, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyOIDCConfig(config: OIDCConfigs) {
|
||||||
|
await verifySSOConfig(ConfigType.OIDC, config.configs[0])
|
||||||
|
}
|
||||||
|
|
||||||
export async function save(ctx: UserCtx<Config>) {
|
export async function save(ctx: UserCtx<Config>) {
|
||||||
const body = ctx.request.body
|
const body = ctx.request.body
|
||||||
const type = body.type
|
const type = body.type
|
||||||
|
@ -133,10 +193,19 @@ export async function save(ctx: UserCtx<Config>) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// verify the configuration
|
// verify the configuration
|
||||||
switch (config.type) {
|
switch (type) {
|
||||||
case ConfigType.SMTP:
|
case ConfigType.SMTP:
|
||||||
await email.verifyConfig(config)
|
await email.verifyConfig(config)
|
||||||
break
|
break
|
||||||
|
case ConfigType.SETTINGS:
|
||||||
|
await verifySettingsConfig(config)
|
||||||
|
break
|
||||||
|
case ConfigType.GOOGLE:
|
||||||
|
await verifyGoogleConfig(config)
|
||||||
|
break
|
||||||
|
case ConfigType.OIDC:
|
||||||
|
await verifyOIDCConfig(config)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { checkInviteCode } from "../../../utilities/redis"
|
import {
|
||||||
|
checkInviteCode,
|
||||||
|
getInviteCodes,
|
||||||
|
updateInviteCode,
|
||||||
|
} from "../../../utilities/redis"
|
||||||
|
// import sdk from "../../../sdk"
|
||||||
import * as userSdk from "../../../sdk/users"
|
import * as userSdk from "../../../sdk/users"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import {
|
import {
|
||||||
|
@ -28,6 +33,7 @@ import {
|
||||||
platform,
|
platform,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { checkAnyUserExists } from "../../../utilities/users"
|
import { checkAnyUserExists } from "../../../utilities/users"
|
||||||
|
import { isEmailConfigured } from "../../../utilities/email"
|
||||||
|
|
||||||
const MAX_USERS_UPLOAD_LIMIT = 1000
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
||||||
|
|
||||||
|
@ -179,16 +185,28 @@ export const destroy = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAppUsers = async (ctx: any) => {
|
||||||
|
const body = ctx.request.body as SearchUsersRequest
|
||||||
|
const users = await userSdk.getUsersByAppAccess(body?.appId)
|
||||||
|
|
||||||
|
ctx.body = { data: users }
|
||||||
|
}
|
||||||
|
|
||||||
export const search = async (ctx: any) => {
|
export const search = async (ctx: any) => {
|
||||||
const body = ctx.request.body as SearchUsersRequest
|
const body = ctx.request.body as SearchUsersRequest
|
||||||
const paginated = await userSdk.paginatedUsers(body)
|
|
||||||
// user hashed password shouldn't ever be returned
|
if (body.paginated === false) {
|
||||||
for (let user of paginated.data) {
|
await getAppUsers(ctx)
|
||||||
if (user) {
|
} else {
|
||||||
delete user.password
|
const paginated = await userSdk.paginatedUsers(body)
|
||||||
|
// user hashed password shouldn't ever be returned
|
||||||
|
for (let user of paginated.data) {
|
||||||
|
if (user) {
|
||||||
|
delete user.password
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ctx.body = paginated
|
||||||
}
|
}
|
||||||
ctx.body = paginated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// called internally by app server user fetch
|
// called internally by app server user fetch
|
||||||
|
@ -218,9 +236,71 @@ export const tenantUserLookup = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Encapsulate the app user onboarding flows here.
|
||||||
|
*/
|
||||||
|
export const onboardUsers = async (ctx: any) => {
|
||||||
|
const request = ctx.request.body as InviteUsersRequest | BulkUserRequest
|
||||||
|
const isBulkCreate = "create" in request
|
||||||
|
|
||||||
|
const emailConfigured = await isEmailConfigured()
|
||||||
|
|
||||||
|
let onboardingResponse
|
||||||
|
|
||||||
|
if (isBulkCreate) {
|
||||||
|
// @ts-ignore
|
||||||
|
const { users, groups, roles } = request.create
|
||||||
|
const assignUsers = users.map((user: User) => (user.roles = roles))
|
||||||
|
onboardingResponse = await userSdk.bulkCreate(assignUsers, groups)
|
||||||
|
ctx.body = onboardingResponse
|
||||||
|
} else if (emailConfigured) {
|
||||||
|
onboardingResponse = await inviteMultiple(ctx)
|
||||||
|
} else if (!emailConfigured) {
|
||||||
|
const inviteRequest = ctx.request.body as InviteUsersRequest
|
||||||
|
|
||||||
|
let createdPasswords: any = {}
|
||||||
|
|
||||||
|
const users: User[] = inviteRequest.map(invite => {
|
||||||
|
let password = Math.random().toString(36).substring(2, 22)
|
||||||
|
|
||||||
|
// Temp password to be passed to the user.
|
||||||
|
createdPasswords[invite.email] = password
|
||||||
|
|
||||||
|
return {
|
||||||
|
email: invite.email,
|
||||||
|
password,
|
||||||
|
forceResetPassword: true,
|
||||||
|
roles: invite.userInfo.apps,
|
||||||
|
admin: { global: false },
|
||||||
|
builder: { global: false },
|
||||||
|
tenantId: tenancy.getTenantId(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let bulkCreateReponse = await userSdk.bulkCreate(users, [])
|
||||||
|
|
||||||
|
// Apply temporary credentials
|
||||||
|
let createWithCredentials = {
|
||||||
|
...bulkCreateReponse,
|
||||||
|
successful: bulkCreateReponse?.successful.map(user => {
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
password: createdPasswords[user.email],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
created: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = createWithCredentials
|
||||||
|
} else {
|
||||||
|
ctx.throw(400, "User onboarding failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const invite = async (ctx: any) => {
|
export const invite = async (ctx: any) => {
|
||||||
const request = ctx.request.body as InviteUserRequest
|
const request = ctx.request.body as InviteUserRequest
|
||||||
const response = await userSdk.invite([request])
|
|
||||||
|
let multiRequest = [request] as InviteUsersRequest
|
||||||
|
const response = await userSdk.invite(multiRequest)
|
||||||
|
|
||||||
// explicitly throw for single user invite
|
// explicitly throw for single user invite
|
||||||
if (response.unsuccessful.length) {
|
if (response.unsuccessful.length) {
|
||||||
|
@ -234,6 +314,8 @@ export const invite = async (ctx: any) => {
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Invitation has been sent.",
|
message: "Invitation has been sent.",
|
||||||
|
successful: response.successful,
|
||||||
|
unsuccessful: response.unsuccessful,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +337,53 @@ export const checkInvite = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getUserInvites = async (ctx: any) => {
|
||||||
|
let invites
|
||||||
|
try {
|
||||||
|
// Restricted to the currently authenticated tenant
|
||||||
|
invites = await getInviteCodes([ctx.user.tenantId])
|
||||||
|
} catch (e) {
|
||||||
|
ctx.throw(400, "There was a problem fetching invites")
|
||||||
|
}
|
||||||
|
ctx.body = invites
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateInvite = async (ctx: any) => {
|
||||||
|
const { code } = ctx.params
|
||||||
|
let updateBody = { ...ctx.request.body }
|
||||||
|
|
||||||
|
delete updateBody.email
|
||||||
|
|
||||||
|
let invite
|
||||||
|
try {
|
||||||
|
invite = await checkInviteCode(code, false)
|
||||||
|
if (!invite) {
|
||||||
|
throw new Error("The invite could not be retrieved")
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ctx.throw(400, "There was a problem with the invite")
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated = {
|
||||||
|
...invite,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) {
|
||||||
|
updated.info.apps = []
|
||||||
|
} else {
|
||||||
|
updated.info = {
|
||||||
|
...invite.info,
|
||||||
|
apps: {
|
||||||
|
...invite.info.apps,
|
||||||
|
...updateBody.apps,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateInviteCode(code, updated)
|
||||||
|
ctx.body = { ...invite }
|
||||||
|
}
|
||||||
|
|
||||||
export const inviteAccept = async (
|
export const inviteAccept = async (
|
||||||
ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
|
ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
|
||||||
) => {
|
) => {
|
||||||
|
@ -263,13 +392,23 @@ export const inviteAccept = async (
|
||||||
// info is an extension of the user object that was stored by global
|
// info is an extension of the user object that was stored by global
|
||||||
const { email, info }: any = await checkInviteCode(inviteCode)
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
||||||
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
||||||
const saved = await userSdk.save({
|
let request = {
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
|
roles: info.apps,
|
||||||
|
tenantId: info.tenantId,
|
||||||
|
}
|
||||||
|
|
||||||
|
delete info.apps
|
||||||
|
|
||||||
|
request = {
|
||||||
|
...request,
|
||||||
...info,
|
...info,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const saved = await userSdk.save(request)
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
const user = await db.get(saved._id)
|
const user = await db.get(saved._id)
|
||||||
await events.user.inviteAccepted(user)
|
await events.user.inviteAccepted(user)
|
||||||
|
|
|
@ -34,8 +34,8 @@ function settingValidation() {
|
||||||
function googleValidation() {
|
function googleValidation() {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
clientID: Joi.when('activated', { is: true, then: Joi.string().required() }),
|
clientID: Joi.string().required(),
|
||||||
clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }),
|
clientSecret: Joi.string().required(),
|
||||||
activated: Joi.boolean().required(),
|
activated: Joi.boolean().required(),
|
||||||
}).unknown(true)
|
}).unknown(true)
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,12 @@ function oidcValidation() {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
configs: Joi.array().items(
|
configs: Joi.array().items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
clientID: Joi.when('activated', { is: true, then: Joi.string().required() }),
|
clientID: Joi.string().required(),
|
||||||
clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }),
|
clientSecret: Joi.string().required(),
|
||||||
configUrl: Joi.when('activated', { is: true, then: Joi.string().required() }),
|
configUrl: Joi.string().required(),
|
||||||
logo: Joi.string().allow("", null),
|
logo: Joi.string().allow("", null),
|
||||||
name: Joi.string().allow("", null),
|
name: Joi.string().allow("", null),
|
||||||
uuid: Joi.when('activated', { is: true, then: Joi.string().required() }),
|
uuid: Joi.string().required(),
|
||||||
activated: Joi.boolean().required(),
|
activated: Joi.boolean().required(),
|
||||||
scopes: Joi.array().optional()
|
scopes: Joi.array().optional()
|
||||||
})
|
})
|
||||||
|
|
|
@ -30,7 +30,11 @@ describe("/api/global/users", () => {
|
||||||
email
|
email
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(res.body).toEqual({ message: "Invitation has been sent." })
|
expect(res.body?.message).toBe("Invitation has been sent.")
|
||||||
|
expect(res.body?.unsuccessful.length).toBe(0)
|
||||||
|
expect(res.body?.successful.length).toBe(1)
|
||||||
|
expect(res.body?.successful[0].email).toBe(email)
|
||||||
|
|
||||||
expect(sendMailMock).toHaveBeenCalled()
|
expect(sendMailMock).toHaveBeenCalled()
|
||||||
expect(code).toBeDefined()
|
expect(code).toBeDefined()
|
||||||
expect(events.user.invited).toBeCalledTimes(1)
|
expect(events.user.invited).toBeCalledTimes(1)
|
||||||
|
|
|
@ -38,13 +38,6 @@ function buildInviteMultipleValidation() {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInviteLookupValidation() {
|
|
||||||
// prettier-ignore
|
|
||||||
return auth.joiValidator.params(Joi.object({
|
|
||||||
code: Joi.string().required()
|
|
||||||
}).unknown(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
const createUserAdminOnly = (ctx: any, next: any) => {
|
const createUserAdminOnly = (ctx: any, next: any) => {
|
||||||
if (!ctx.request.body._id) {
|
if (!ctx.request.body._id) {
|
||||||
return auth.adminOnly(ctx, next)
|
return auth.adminOnly(ctx, next)
|
||||||
|
@ -88,22 +81,34 @@ router
|
||||||
.get("/api/global/roles/:appId")
|
.get("/api/global/roles/:appId")
|
||||||
.post(
|
.post(
|
||||||
"/api/global/users/invite",
|
"/api/global/users/invite",
|
||||||
auth.adminOnly,
|
auth.builderOrAdmin,
|
||||||
buildInviteValidation(),
|
buildInviteValidation(),
|
||||||
controller.invite
|
controller.invite
|
||||||
)
|
)
|
||||||
|
.post(
|
||||||
|
"/api/global/users/onboard",
|
||||||
|
auth.builderOrAdmin,
|
||||||
|
buildInviteMultipleValidation(),
|
||||||
|
controller.onboardUsers
|
||||||
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/global/users/multi/invite",
|
"/api/global/users/multi/invite",
|
||||||
auth.adminOnly,
|
auth.builderOrAdmin,
|
||||||
buildInviteMultipleValidation(),
|
buildInviteMultipleValidation(),
|
||||||
controller.inviteMultiple
|
controller.inviteMultiple
|
||||||
)
|
)
|
||||||
|
|
||||||
// non-global endpoints
|
// non-global endpoints
|
||||||
|
.get("/api/global/users/invite/:code", controller.checkInvite)
|
||||||
|
.post(
|
||||||
|
"/api/global/users/invite/update/:code",
|
||||||
|
auth.builderOrAdmin,
|
||||||
|
controller.updateInvite
|
||||||
|
)
|
||||||
.get(
|
.get(
|
||||||
"/api/global/users/invite/:code",
|
"/api/global/users/invites",
|
||||||
buildInviteLookupValidation(),
|
auth.builderOrAdmin,
|
||||||
controller.checkInvite
|
controller.getUserInvites
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/global/users/invite/accept",
|
"/api/global/users/invite/accept",
|
||||||
|
|
|
@ -57,11 +57,22 @@ export const countUsersByApp = async (appId: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getUsersByAppAccess = async (appId?: string) => {
|
||||||
|
const opts: any = {
|
||||||
|
include_docs: true,
|
||||||
|
limit: 50,
|
||||||
|
}
|
||||||
|
let response: User[] = await usersCore.searchGlobalUsersByAppAccess(
|
||||||
|
appId,
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
export const paginatedUsers = async ({
|
export const paginatedUsers = async ({
|
||||||
page,
|
page,
|
||||||
email,
|
email,
|
||||||
appId,
|
appId,
|
||||||
userIds,
|
|
||||||
}: SearchUsersRequest = {}) => {
|
}: SearchUsersRequest = {}) => {
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
// get one extra document, to have the next page
|
// get one extra document, to have the next page
|
||||||
|
@ -234,7 +245,7 @@ export const save = async (
|
||||||
const tenantId = tenancy.getTenantId()
|
const tenantId = tenancy.getTenantId()
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
|
|
||||||
let { email, _id, userGroups = [] } = user
|
let { email, _id, userGroups = [], roles } = user
|
||||||
|
|
||||||
if (!email && !_id) {
|
if (!email && !_id) {
|
||||||
throw new Error("_id or email is required")
|
throw new Error("_id or email is required")
|
||||||
|
@ -276,6 +287,10 @@ export const save = async (
|
||||||
builtUser.roles = dbUser.roles
|
builtUser.roles = dbUser.roles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dbUser && roles?.length) {
|
||||||
|
builtUser.roles = { ...roles }
|
||||||
|
}
|
||||||
|
|
||||||
// make sure we set the _id field for a new user
|
// make sure we set the _id field for a new user
|
||||||
// Also if this is a new user, associate groups with them
|
// Also if this is a new user, associate groups with them
|
||||||
let groupPromises = []
|
let groupPromises = []
|
||||||
|
|
|
@ -203,7 +203,7 @@ export async function sendEmail(
|
||||||
* @param {object} config an SMTP configuration - this is based on the nodemailer API.
|
* @param {object} config an SMTP configuration - this is based on the nodemailer API.
|
||||||
* @return {Promise<boolean>} returns true if the configuration is valid.
|
* @return {Promise<boolean>} returns true if the configuration is valid.
|
||||||
*/
|
*/
|
||||||
export async function verifyConfig(config: any) {
|
export async function verifyConfig(config: SMTPInnerConfig) {
|
||||||
const transport = createSMTPTransport(config)
|
const transport = createSMTPTransport(config)
|
||||||
await transport.verify()
|
await transport.verify()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ function getExpirySecondsForDB(db: string) {
|
||||||
return 3600
|
return 3600
|
||||||
case redis.utils.Databases.INVITATIONS:
|
case redis.utils.Databases.INVITATIONS:
|
||||||
// a day
|
// a day
|
||||||
return 86400
|
return 604800
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,20 @@ async function writeACode(db: string, value: any) {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateACode(db: string, code: string, value: any) {
|
||||||
|
const client = await getClient(db)
|
||||||
|
await client.store(code, value, getExpirySecondsForDB(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an invite code and invite body, allow the update an existing/valid invite in redis
|
||||||
|
* @param {string} inviteCode The invite code for an invite in redis
|
||||||
|
* @param {object} value The body of the updated user invitation
|
||||||
|
*/
|
||||||
|
export async function updateInviteCode(inviteCode: string, value: string) {
|
||||||
|
await updateACode(redis.utils.Databases.INVITATIONS, inviteCode, value)
|
||||||
|
}
|
||||||
|
|
||||||
async function getACode(db: string, code: string, deleteCode = true) {
|
async function getACode(db: string, code: string, deleteCode = true) {
|
||||||
const client = await getClient(db)
|
const client = await getClient(db)
|
||||||
const value = await client.get(code)
|
const value = await client.get(code)
|
||||||
|
@ -113,3 +127,27 @@ export async function checkInviteCode(
|
||||||
throw "Invitation is not valid or has expired, please request a new one."
|
throw "Invitation is not valid or has expired, please request a new one."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get all currently available user invitations.
|
||||||
|
@return {Object[]} A list of all objects containing invite metadata
|
||||||
|
**/
|
||||||
|
export async function getInviteCodes(tenantIds?: string[]) {
|
||||||
|
const client = await getClient(redis.utils.Databases.INVITATIONS)
|
||||||
|
const invites: any[] = await client.scan()
|
||||||
|
|
||||||
|
const results = invites.map(invite => {
|
||||||
|
return {
|
||||||
|
...invite.value,
|
||||||
|
code: invite.key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return results.reduce((acc, invite) => {
|
||||||
|
if (tenantIds?.length && tenantIds.includes(invite.info.tenantId)) {
|
||||||
|
acc.push(invite)
|
||||||
|
} else {
|
||||||
|
acc.push(invite)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue