forked from tsai/budibase
Browse Source
# Conflicts: # packages/builder/src/components/design/PropertiesPanel/ScreenSettingsSection.sveltemaster
236 changed files with 3916 additions and 1204 deletions
@ -0,0 +1,14 @@ |
|||||
|
--- |
||||
|
name: Feature Request |
||||
|
about: Request a new budibase feature or enhancement |
||||
|
title: '' |
||||
|
labels: enhancement |
||||
|
assignees: '' |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Describe the feature request** |
||||
|
A clear and concise description of what the feature request. |
||||
|
|
||||
|
**Screenshots** |
||||
|
If applicable, add screenshots to help explain your problem. |
||||
@ -1 +1,4 @@ |
|||||
module.exports = require("./src/db/utils") |
module.exports = { |
||||
|
...require("./src/db/utils"), |
||||
|
...require("./src/db/constants"), |
||||
|
} |
||||
|
|||||
@ -0,0 +1,17 @@ |
|||||
|
exports.SEPARATOR = "_" |
||||
|
|
||||
|
exports.StaticDatabases = { |
||||
|
GLOBAL: { |
||||
|
name: "global-db", |
||||
|
docs: { |
||||
|
apiKeys: "apikeys", |
||||
|
}, |
||||
|
}, |
||||
|
// contains information about tenancy and so on
|
||||
|
PLATFORM_INFO: { |
||||
|
name: "global-info", |
||||
|
docs: { |
||||
|
tenants: "tenants", |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g |
||||
|
|
||||
|
exports.buildMatcherRegex = patterns => { |
||||
|
if (!patterns) { |
||||
|
return [] |
||||
|
} |
||||
|
return patterns.map(pattern => { |
||||
|
const isObj = typeof pattern === "object" && pattern.route |
||||
|
const method = isObj ? pattern.method : "GET" |
||||
|
let route = isObj ? pattern.route : pattern |
||||
|
|
||||
|
const matches = route.match(PARAM_REGEX) |
||||
|
if (matches) { |
||||
|
for (let match of matches) { |
||||
|
const pattern = "/.*" + (match.endsWith("/") ? "/" : "") |
||||
|
route = route.replace(match, pattern) |
||||
|
} |
||||
|
} |
||||
|
return { regex: new RegExp(route), method } |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
exports.matches = (ctx, options) => { |
||||
|
return options.find(({ regex, method }) => { |
||||
|
const urlMatch = regex.test(ctx.request.url) |
||||
|
const methodMatch = |
||||
|
method === "ALL" |
||||
|
? true |
||||
|
: ctx.request.method.toLowerCase() === method.toLowerCase() |
||||
|
|
||||
|
return urlMatch && methodMatch |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
const { setTenantId } = require("../tenancy") |
||||
|
const ContextFactory = require("../tenancy/FunctionContext") |
||||
|
const { buildMatcherRegex, matches } = require("./matchers") |
||||
|
|
||||
|
module.exports = (allowQueryStringPatterns, noTenancyPatterns) => { |
||||
|
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns) |
||||
|
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns) |
||||
|
|
||||
|
return ContextFactory.getMiddleware(ctx => { |
||||
|
const allowNoTenant = !!matches(ctx, noTenancyOptions) |
||||
|
const allowQs = !!matches(ctx, allowQsOptions) |
||||
|
setTenantId(ctx, { allowQs, allowNoTenant }) |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
const cls = require("cls-hooked") |
||||
|
const { newid } = require("../hashing") |
||||
|
|
||||
|
const REQUEST_ID_KEY = "requestId" |
||||
|
|
||||
|
class FunctionContext { |
||||
|
static getMiddleware(updateCtxFn = null) { |
||||
|
const namespace = this.createNamespace() |
||||
|
|
||||
|
return async function (ctx, next) { |
||||
|
await new Promise( |
||||
|
namespace.bind(function (resolve, reject) { |
||||
|
// store a contextual request ID that can be used anywhere (audit logs)
|
||||
|
namespace.set(REQUEST_ID_KEY, newid()) |
||||
|
namespace.bindEmitter(ctx.req) |
||||
|
namespace.bindEmitter(ctx.res) |
||||
|
|
||||
|
if (updateCtxFn) { |
||||
|
updateCtxFn(ctx) |
||||
|
} |
||||
|
next().then(resolve).catch(reject) |
||||
|
}) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static run(callback) { |
||||
|
const namespace = this.createNamespace() |
||||
|
|
||||
|
return namespace.runAndReturn(callback) |
||||
|
} |
||||
|
|
||||
|
static setOnContext(key, value) { |
||||
|
const namespace = this.createNamespace() |
||||
|
namespace.set(key, value) |
||||
|
} |
||||
|
|
||||
|
static getContextStorage() { |
||||
|
if (this._namespace && this._namespace.active) { |
||||
|
let contextData = this._namespace.active |
||||
|
delete contextData.id |
||||
|
delete contextData._ns_name |
||||
|
return contextData |
||||
|
} |
||||
|
|
||||
|
return {} |
||||
|
} |
||||
|
|
||||
|
static getFromContext(key) { |
||||
|
const context = this.getContextStorage() |
||||
|
if (context) { |
||||
|
return context[key] |
||||
|
} else { |
||||
|
return null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static destroyNamespace() { |
||||
|
if (this._namespace) { |
||||
|
cls.destroyNamespace("session") |
||||
|
this._namespace = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static createNamespace() { |
||||
|
if (!this._namespace) { |
||||
|
this._namespace = cls.createNamespace("session") |
||||
|
} |
||||
|
return this._namespace |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = FunctionContext |
||||
@ -0,0 +1,81 @@ |
|||||
|
const env = require("../environment") |
||||
|
const { Headers } = require("../../constants") |
||||
|
const cls = require("./FunctionContext") |
||||
|
|
||||
|
exports.DEFAULT_TENANT_ID = "default" |
||||
|
|
||||
|
exports.isDefaultTenant = () => { |
||||
|
return exports.getTenantId() === exports.DEFAULT_TENANT_ID |
||||
|
} |
||||
|
|
||||
|
exports.isMultiTenant = () => { |
||||
|
return env.MULTI_TENANCY |
||||
|
} |
||||
|
|
||||
|
const TENANT_ID = "tenantId" |
||||
|
|
||||
|
// used for automations, API endpoints should always be in context already
|
||||
|
exports.doInTenant = (tenantId, task) => { |
||||
|
return cls.run(() => { |
||||
|
// set the tenant id
|
||||
|
cls.setOnContext(TENANT_ID, tenantId) |
||||
|
|
||||
|
// invoke the task
|
||||
|
const result = task() |
||||
|
|
||||
|
return result |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
exports.updateTenantId = tenantId => { |
||||
|
cls.setOnContext(TENANT_ID, tenantId) |
||||
|
} |
||||
|
|
||||
|
exports.setTenantId = ( |
||||
|
ctx, |
||||
|
opts = { allowQs: false, allowNoTenant: false } |
||||
|
) => { |
||||
|
let tenantId |
||||
|
// exit early if not multi-tenant
|
||||
|
if (!exports.isMultiTenant()) { |
||||
|
cls.setOnContext(TENANT_ID, this.DEFAULT_TENANT_ID) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const allowQs = opts && opts.allowQs |
||||
|
const allowNoTenant = opts && opts.allowNoTenant |
||||
|
const header = ctx.request.headers[Headers.TENANT_ID] |
||||
|
const user = ctx.user || {} |
||||
|
if (allowQs) { |
||||
|
const query = ctx.request.query || {} |
||||
|
tenantId = query.tenantId |
||||
|
} |
||||
|
// override query string (if allowed) by user, or header
|
||||
|
// URL params cannot be used in a middleware, as they are
|
||||
|
// processed later in the chain
|
||||
|
tenantId = user.tenantId || header || tenantId |
||||
|
|
||||
|
if (!tenantId && !allowNoTenant) { |
||||
|
ctx.throw(403, "Tenant id not set") |
||||
|
} |
||||
|
// check tenant ID just incase no tenant was allowed
|
||||
|
if (tenantId) { |
||||
|
cls.setOnContext(TENANT_ID, tenantId) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
exports.isTenantIdSet = () => { |
||||
|
const tenantId = cls.getFromContext(TENANT_ID) |
||||
|
return !!tenantId |
||||
|
} |
||||
|
|
||||
|
exports.getTenantId = () => { |
||||
|
if (!exports.isMultiTenant()) { |
||||
|
return exports.DEFAULT_TENANT_ID |
||||
|
} |
||||
|
const tenantId = cls.getFromContext(TENANT_ID) |
||||
|
if (!tenantId) { |
||||
|
throw Error("Tenant id not found") |
||||
|
} |
||||
|
return tenantId |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
module.exports = { |
||||
|
...require("./context"), |
||||
|
...require("./tenancy"), |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
const { getDB } = require("../db") |
||||
|
const { SEPARATOR, StaticDatabases } = require("../db/constants") |
||||
|
const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context") |
||||
|
const env = require("../environment") |
||||
|
|
||||
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants |
||||
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name |
||||
|
|
||||
|
exports.addTenantToUrl = url => { |
||||
|
const tenantId = getTenantId() |
||||
|
|
||||
|
if (isMultiTenant()) { |
||||
|
const char = url.indexOf("?") === -1 ? "?" : "&" |
||||
|
url += `${char}tenantId=${tenantId}` |
||||
|
} |
||||
|
|
||||
|
return url |
||||
|
} |
||||
|
|
||||
|
exports.doesTenantExist = async tenantId => { |
||||
|
const db = getDB(PLATFORM_INFO_DB) |
||||
|
let tenants |
||||
|
try { |
||||
|
tenants = await db.get(TENANT_DOC) |
||||
|
} catch (err) { |
||||
|
// if theres an error the doc doesn't exist, no tenants exist
|
||||
|
return false |
||||
|
} |
||||
|
return ( |
||||
|
tenants && |
||||
|
Array.isArray(tenants.tenantIds) && |
||||
|
tenants.tenantIds.indexOf(tenantId) !== -1 |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
exports.tryAddTenant = async (tenantId, userId, email) => { |
||||
|
const db = getDB(PLATFORM_INFO_DB) |
||||
|
const getDoc = async id => { |
||||
|
if (!id) { |
||||
|
return null |
||||
|
} |
||||
|
try { |
||||
|
return await db.get(id) |
||||
|
} catch (err) { |
||||
|
return { _id: id } |
||||
|
} |
||||
|
} |
||||
|
let [tenants, userIdDoc, emailDoc] = await Promise.all([ |
||||
|
getDoc(TENANT_DOC), |
||||
|
getDoc(userId), |
||||
|
getDoc(email), |
||||
|
]) |
||||
|
if (!Array.isArray(tenants.tenantIds)) { |
||||
|
tenants = { |
||||
|
_id: TENANT_DOC, |
||||
|
tenantIds: [], |
||||
|
} |
||||
|
} |
||||
|
let promises = [] |
||||
|
if (userIdDoc) { |
||||
|
userIdDoc.tenantId = tenantId |
||||
|
promises.push(db.put(userIdDoc)) |
||||
|
} |
||||
|
if (emailDoc) { |
||||
|
emailDoc.tenantId = tenantId |
||||
|
promises.push(db.put(emailDoc)) |
||||
|
} |
||||
|
if (tenants.tenantIds.indexOf(tenantId) === -1) { |
||||
|
tenants.tenantIds.push(tenantId) |
||||
|
promises.push(db.put(tenants)) |
||||
|
} |
||||
|
await Promise.all(promises) |
||||
|
} |
||||
|
|
||||
|
exports.getGlobalDB = (tenantId = null) => { |
||||
|
// tenant ID can be set externally, for example user API where
|
||||
|
// new tenants are being created, this may be the case
|
||||
|
if (!tenantId) { |
||||
|
tenantId = getTenantId() |
||||
|
} |
||||
|
|
||||
|
let dbName |
||||
|
|
||||
|
if (tenantId === DEFAULT_TENANT_ID) { |
||||
|
dbName = StaticDatabases.GLOBAL.name |
||||
|
} else { |
||||
|
dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}` |
||||
|
} |
||||
|
|
||||
|
return getDB(dbName) |
||||
|
} |
||||
|
|
||||
|
exports.lookupTenantId = async userId => { |
||||
|
const db = getDB(StaticDatabases.PLATFORM_INFO.name) |
||||
|
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null |
||||
|
try { |
||||
|
const doc = await db.get(userId) |
||||
|
if (doc && doc.tenantId) { |
||||
|
tenantId = doc.tenantId |
||||
|
} |
||||
|
} catch (err) { |
||||
|
// just return the default
|
||||
|
} |
||||
|
return tenantId |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
module.exports = require("./src/tenancy") |
||||
@ -0,0 +1,20 @@ |
|||||
|
<script> |
||||
|
export let type = "info" |
||||
|
export let icon = "Info" |
||||
|
export let message = "" |
||||
|
</script> |
||||
|
|
||||
|
<div class="spectrum-Toast spectrum-Toast--{type}"> |
||||
|
{#if icon} |
||||
|
<svg |
||||
|
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon" |
||||
|
focusable="false" |
||||
|
aria-hidden="true" |
||||
|
> |
||||
|
<use xlink:href="#spectrum-icon-18-{icon}" /> |
||||
|
</svg> |
||||
|
{/if} |
||||
|
<div class="spectrum-Toast-body"> |
||||
|
<div class="spectrum-Toast-content">{message || ""}</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,35 @@ |
|||||
|
<script> |
||||
|
import { Select, Label } from "@budibase/bbui" |
||||
|
import { currentAsset, store } from "builderStore" |
||||
|
import { getActionProviderComponents } from "builderStore/dataBinding" |
||||
|
|
||||
|
export let parameters |
||||
|
|
||||
|
$: actionProviders = getActionProviderComponents( |
||||
|
$currentAsset, |
||||
|
$store.selectedComponentId, |
||||
|
"ClearForm" |
||||
|
) |
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<Label small>Form</Label> |
||||
|
<Select |
||||
|
bind:value={parameters.componentId} |
||||
|
options={actionProviders} |
||||
|
getOptionLabel={x => x._instanceName} |
||||
|
getOptionValue={x => x._id} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
display: grid; |
||||
|
column-gap: var(--spacing-l); |
||||
|
row-gap: var(--spacing-s); |
||||
|
grid-template-columns: 60px 1fr; |
||||
|
align-items: center; |
||||
|
max-width: 800px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,17 @@ |
|||||
|
<script> |
||||
|
import { Body } from "@budibase/bbui" |
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<Body size="S">This action doesn't require any additional settings.</Body> |
||||
|
<Body size="S"> |
||||
|
This action won't do anything if there isn't a screen modal open. |
||||
|
</Body> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
max-width: 800px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,48 @@ |
|||||
|
<script> |
||||
|
import { Select, Label, Input } from "@budibase/bbui" |
||||
|
|
||||
|
/** |
||||
|
* This component takes the query object and populates the 'extra' property |
||||
|
* when a datasource has specified a configuration for these fields in SCHEMA.extra |
||||
|
*/ |
||||
|
export let populateExtraQuery |
||||
|
export let config |
||||
|
export let query |
||||
|
|
||||
|
$: extraFields = Object.keys(config).map(key => ({ |
||||
|
...config[key], |
||||
|
key, |
||||
|
})) |
||||
|
|
||||
|
$: extraQueryFields = query.fields.extra || {} |
||||
|
</script> |
||||
|
|
||||
|
{#each extraFields as { key, displayName, type }} |
||||
|
<div class="config-field"> |
||||
|
<Label>{displayName}</Label> |
||||
|
{#if type === "string"} |
||||
|
<Input |
||||
|
on:change={() => populateExtraQuery(extraQueryFields)} |
||||
|
bind:value={extraQueryFields[key]} |
||||
|
/> |
||||
|
{/if} |
||||
|
|
||||
|
{#if type === "list"} |
||||
|
<Select |
||||
|
on:change={() => populateExtraQuery(extraQueryFields)} |
||||
|
bind:value={extraQueryFields[key]} |
||||
|
options={config[key].data[query.queryVerb]} |
||||
|
getOptionLabel={current => current} |
||||
|
/> |
||||
|
{/if} |
||||
|
</div> |
||||
|
{/each} |
||||
|
|
||||
|
<style> |
||||
|
.config-field { |
||||
|
display: grid; |
||||
|
grid-template-columns: 20% 1fr; |
||||
|
grid-gap: var(--spacing-l); |
||||
|
align-items: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,111 @@ |
|||||
|
<script> |
||||
|
import { writable, get as svelteGet } from "svelte/store" |
||||
|
import { |
||||
|
notifications, |
||||
|
Input, |
||||
|
Modal, |
||||
|
ModalContent, |
||||
|
Body, |
||||
|
} from "@budibase/bbui" |
||||
|
import { hostingStore } from "builderStore" |
||||
|
import { apps } from "stores/portal" |
||||
|
import { string, object } from "yup" |
||||
|
import { onMount } from "svelte" |
||||
|
import { capitalise } from "helpers" |
||||
|
|
||||
|
const values = writable({ name: null }) |
||||
|
const errors = writable({}) |
||||
|
const touched = writable({}) |
||||
|
const validator = { |
||||
|
name: string().required("Your application must have a name"), |
||||
|
} |
||||
|
|
||||
|
export let app |
||||
|
|
||||
|
let modal |
||||
|
let valid = false |
||||
|
let dirty = false |
||||
|
$: checkValidity($values, validator) |
||||
|
$: { |
||||
|
// prevent validation by setting name to undefined without an app |
||||
|
if (app) { |
||||
|
$values.name = app?.name |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMount(async () => { |
||||
|
await hostingStore.actions.fetchDeployedApps() |
||||
|
const existingAppNames = svelteGet(hostingStore).deployedAppNames |
||||
|
validator.name = string() |
||||
|
.required("Your application must have a name") |
||||
|
.test( |
||||
|
"non-existing-app-name", |
||||
|
"Another app with the same name already exists", |
||||
|
value => { |
||||
|
return !existingAppNames.some( |
||||
|
appName => dirty && appName.toLowerCase() === value.toLowerCase() |
||||
|
) |
||||
|
} |
||||
|
) |
||||
|
}) |
||||
|
|
||||
|
const checkValidity = async (values, validator) => { |
||||
|
const obj = object().shape(validator) |
||||
|
Object.keys(validator).forEach(key => ($errors[key] = null)) |
||||
|
try { |
||||
|
await obj.validate(values, { abortEarly: false }) |
||||
|
} catch (validationErrors) { |
||||
|
validationErrors.inner.forEach(error => { |
||||
|
$errors[error.path] = capitalise(error.message) |
||||
|
}) |
||||
|
} |
||||
|
valid = await obj.isValid(values) |
||||
|
} |
||||
|
|
||||
|
async function updateApp() { |
||||
|
try { |
||||
|
// Update App |
||||
|
await apps.update(app.instance._id, $values.name) |
||||
|
hide() |
||||
|
} catch (error) { |
||||
|
console.error(error) |
||||
|
notifications.error(error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const show = () => { |
||||
|
modal.show() |
||||
|
} |
||||
|
export const hide = () => { |
||||
|
modal.hide() |
||||
|
} |
||||
|
|
||||
|
const onCancel = () => { |
||||
|
hide() |
||||
|
} |
||||
|
|
||||
|
const onShow = () => { |
||||
|
dirty = false |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<Modal bind:this={modal} on:hide={onCancel} on:show={onShow}> |
||||
|
<ModalContent |
||||
|
title={"Update app"} |
||||
|
confirmText={"Update app"} |
||||
|
onConfirm={updateApp} |
||||
|
disabled={!(valid && dirty)} |
||||
|
> |
||||
|
<Body size="S"> |
||||
|
Give your new app a name, and choose which groups have access (paid plans |
||||
|
only). |
||||
|
</Body> |
||||
|
<Input |
||||
|
bind:value={$values.name} |
||||
|
error={$touched.name && $errors.name} |
||||
|
on:blur={() => ($touched.name = true)} |
||||
|
on:change={() => (dirty = true)} |
||||
|
label="Name" |
||||
|
/> |
||||
|
</ModalContent> |
||||
|
</Modal> |
||||
@ -1,4 +1,24 @@ |
|||||
<script> |
<script> |
||||
import { redirect } from "@roxi/routify" |
import { redirect } from "@roxi/routify" |
||||
$redirect("./login") |
import { auth, admin } from "stores/portal" |
||||
|
import { onMount } from "svelte" |
||||
|
|
||||
|
$: tenantSet = $auth.tenantSet |
||||
|
$: multiTenancyEnabled = $admin.multiTenancy |
||||
|
|
||||
|
let loaded = false |
||||
|
|
||||
|
$: { |
||||
|
if (loaded && multiTenancyEnabled && !tenantSet) { |
||||
|
$redirect("./org") |
||||
|
} else if (loaded) { |
||||
|
$redirect("./login") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMount(async () => { |
||||
|
await admin.init() |
||||
|
await auth.checkQueryString() |
||||
|
loaded = true |
||||
|
}) |
||||
</script> |
</script> |
||||
|
|||||
@ -0,0 +1,73 @@ |
|||||
|
<script> |
||||
|
import { Body, Button, Divider, Heading, Input, Layout } from "@budibase/bbui" |
||||
|
import { goto } from "@roxi/routify" |
||||
|
import { auth, admin } from "stores/portal" |
||||
|
import Logo from "assets/bb-emblem.svg" |
||||
|
import { get } from "svelte/store" |
||||
|
import { onMount } from "svelte" |
||||
|
|
||||
|
let tenantId = get(auth).tenantSet ? get(auth).tenantId : "" |
||||
|
$: multiTenancyEnabled = $admin.multiTenancy |
||||
|
|
||||
|
async function setOrg() { |
||||
|
if (tenantId == null || tenantId === "") { |
||||
|
tenantId = "default" |
||||
|
} |
||||
|
await auth.setOrg(tenantId) |
||||
|
// re-init now org selected |
||||
|
await admin.init() |
||||
|
$goto("../") |
||||
|
} |
||||
|
|
||||
|
function handleKeydown(evt) { |
||||
|
if (evt.key === "Enter") setOrg() |
||||
|
} |
||||
|
|
||||
|
onMount(async () => { |
||||
|
await auth.checkQueryString() |
||||
|
if (!multiTenancyEnabled) { |
||||
|
$goto("../") |
||||
|
} else { |
||||
|
admin.unload() |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<svelte:window on:keydown={handleKeydown} /> |
||||
|
<div class="login"> |
||||
|
<div class="main"> |
||||
|
<Layout> |
||||
|
<Layout noPadding justifyItems="center"> |
||||
|
<img alt="logo" src={Logo} /> |
||||
|
<Heading>Set Budibase organisation</Heading> |
||||
|
</Layout> |
||||
|
<Divider noGrid /> |
||||
|
<Layout gap="XS" noPadding> |
||||
|
<Body size="S" textAlign="center">Set organisation</Body> |
||||
|
<Input label="Organisation" bind:value={tenantId} /> |
||||
|
</Layout> |
||||
|
<Layout gap="XS" noPadding> |
||||
|
<Button cta on:click={setOrg}>Set organisation</Button> |
||||
|
</Layout> |
||||
|
</Layout> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.login { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.main { |
||||
|
width: 300px; |
||||
|
} |
||||
|
|
||||
|
img { |
||||
|
width: 48px; |
||||
|
} |
||||
|
</style> |
||||
@ -1,4 +1,11 @@ |
|||||
<script> |
<script> |
||||
import { redirect } from "@roxi/routify" |
import { redirect } from "@roxi/routify" |
||||
$redirect("./builder") |
import { auth } from "../stores/portal" |
||||
|
import { onMount } from "svelte" |
||||
|
|
||||
|
auth.checkQueryString() |
||||
|
|
||||
|
onMount(() => { |
||||
|
$redirect(`./builder`) |
||||
|
}) |
||||
</script> |
</script> |
||||
|
|||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue