commit
2bebadcfb4
|
@ -50,6 +50,11 @@ static_resources:
|
||||||
route:
|
route:
|
||||||
cluster: app-service
|
cluster: app-service
|
||||||
|
|
||||||
|
- match: { path: "/api/deploy" }
|
||||||
|
route:
|
||||||
|
timeout: 60s
|
||||||
|
cluster: app-service
|
||||||
|
|
||||||
# special case for when API requests are made, can just forward, not to minio
|
# special case for when API requests are made, can just forward, not to minio
|
||||||
- match: { prefix: "/api/" }
|
- match: { prefix: "/api/" }
|
||||||
route:
|
route:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
"multi:disable": "lerna run multi:disable",
|
"multi:disable": "lerna run multi:disable",
|
||||||
"selfhost:enable": "lerna run selfhost:enable",
|
"selfhost:enable": "lerna run selfhost:enable",
|
||||||
"selfhost:disable": "lerna run selfhost:disable",
|
"selfhost:disable": "lerna run selfhost:disable",
|
||||||
|
"localdomain:enable": "lerna run localdomain:enable",
|
||||||
|
"localdomain:disable": "lerna run localdomain:disable",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -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": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
on:keyup={updateValueOnEnter}
|
on:keyup={updateValueOnEnter}
|
||||||
{type}
|
{type}
|
||||||
|
inputmode={type === "number" ? "decimal" : "text"}
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,9 +24,7 @@ context("Create a Table", () => {
|
||||||
it("updates a column on the table", () => {
|
it("updates a column on the table", () => {
|
||||||
cy.get(".title").click()
|
cy.get(".title").click()
|
||||||
cy.get(".spectrum-Table-editIcon > use").click()
|
cy.get(".spectrum-Table-editIcon > use").click()
|
||||||
cy.get("input")
|
cy.get("input").eq(1).type("updated", { force: true })
|
||||||
.eq(1)
|
|
||||||
.type("updated", { force: true })
|
|
||||||
// Unset table display column
|
// Unset table display column
|
||||||
cy.get(".spectrum-Switch-input").eq(1).click()
|
cy.get(".spectrum-Switch-input").eq(1).click()
|
||||||
cy.contains("Save Column").click()
|
cy.contains("Save Column").click()
|
||||||
|
@ -45,9 +43,7 @@ context("Create a Table", () => {
|
||||||
it("deletes a row", () => {
|
it("deletes a row", () => {
|
||||||
cy.get(".spectrum-Checkbox-input").check({ force: true })
|
cy.get(".spectrum-Checkbox-input").check({ force: true })
|
||||||
cy.contains("Delete 1 row(s)").click()
|
cy.contains("Delete 1 row(s)").click()
|
||||||
cy.get(".spectrum-Modal")
|
cy.get(".spectrum-Modal").contains("Delete").click()
|
||||||
.contains("Delete")
|
|
||||||
.click()
|
|
||||||
cy.contains("RoverUpdated").should("not.exist")
|
cy.contains("RoverUpdated").should("not.exist")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -56,15 +52,18 @@ context("Create a Table", () => {
|
||||||
cy.get(".spectrum-Table-editIcon > use").click()
|
cy.get(".spectrum-Table-editIcon > use").click()
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.wait(50)
|
cy.wait(50)
|
||||||
cy.contains("Delete Column")
|
cy.contains("Delete Column").click()
|
||||||
.click()
|
|
||||||
cy.contains("nameupdated").should("not.exist")
|
cy.contains("nameupdated").should("not.exist")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a table", () => {
|
it("deletes a table", () => {
|
||||||
cy.get(".actions > :nth-child(1) > .icon > .spectrum-Icon > use")
|
cy.get(".nav-item")
|
||||||
.eq(1)
|
.contains("dog")
|
||||||
.click({ force: true })
|
.parents(".nav-item")
|
||||||
|
.first()
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".actions .spectrum-Icon").click({ force: true })
|
||||||
|
})
|
||||||
cy.get(".spectrum-Menu > :nth-child(2)").click()
|
cy.get(".spectrum-Menu > :nth-child(2)").click()
|
||||||
cy.contains("Delete Table").click()
|
cy.contains("Delete Table").click()
|
||||||
cy.contains("dog").should("not.exist")
|
cy.contains("dog").should("not.exist")
|
||||||
|
|
|
@ -28,11 +28,7 @@ context("Create a View", () => {
|
||||||
const headers = Array.from($headers).map(header =>
|
const headers = Array.from($headers).map(header =>
|
||||||
header.textContent.trim()
|
header.textContent.trim()
|
||||||
)
|
)
|
||||||
expect(removeSpacing(headers)).to.deep.eq([
|
expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"])
|
||||||
"group",
|
|
||||||
"age",
|
|
||||||
"rating",
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -57,6 +53,7 @@ context("Create a View", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates a stats calculation view based on age", () => {
|
it("creates a stats calculation view based on age", () => {
|
||||||
|
cy.wait(1000)
|
||||||
cy.contains("Calculate").click()
|
cy.contains("Calculate").click()
|
||||||
cy.get(".modal-inner-wrapper").within(() => {
|
cy.get(".modal-inner-wrapper").within(() => {
|
||||||
cy.get(".spectrum-Picker-label").eq(0).click()
|
cy.get(".spectrum-Picker-label").eq(0).click()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.148",
|
"@budibase/bbui": "^0.9.149-alpha.3",
|
||||||
"@budibase/client": "^0.9.148",
|
"@budibase/client": "^0.9.149-alpha.3",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^0.9.148",
|
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import {
|
import {
|
||||||
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
findComponentPath,
|
findComponentPath,
|
||||||
findAllMatchingComponents,
|
|
||||||
} from "./storeUtils"
|
} from "./storeUtils"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { tables as tablesStore, queries as queriesStores } from "stores/backend"
|
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
|
||||||
import { makePropSafe } from "@budibase/string-templates"
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
import { TableNames } from "../constants"
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
|
@ -422,6 +422,10 @@ function shouldReplaceBinding(currentValue, from, convertTo) {
|
||||||
return !invalids.find(invalid => noSpaces?.includes(invalid))
|
return !invalids.find(invalid => noSpaces?.includes(invalid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function replaceBetween(string, start, end, replacement) {
|
||||||
|
return string.substring(0, start) + replacement + string.substring(end)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
|
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
|
||||||
*/
|
*/
|
||||||
|
@ -431,6 +435,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
||||||
if (typeof textWithBindings !== "string") {
|
if (typeof textWithBindings !== "string") {
|
||||||
return textWithBindings
|
return textWithBindings
|
||||||
}
|
}
|
||||||
|
// work from longest to shortest
|
||||||
const convertFromProps = bindableProperties
|
const convertFromProps = bindableProperties
|
||||||
.map(el => el[convertFrom])
|
.map(el => el[convertFrom])
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
|
@ -440,12 +445,29 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
||||||
let result = textWithBindings
|
let result = textWithBindings
|
||||||
for (let boundValue of boundValues) {
|
for (let boundValue of boundValues) {
|
||||||
let newBoundValue = boundValue
|
let newBoundValue = boundValue
|
||||||
|
// we use a search string, where any time we replace something we blank it out
|
||||||
|
// in the search, working from longest to shortest so always use best match first
|
||||||
|
let searchString = newBoundValue
|
||||||
for (let from of convertFromProps) {
|
for (let from of convertFromProps) {
|
||||||
if (shouldReplaceBinding(newBoundValue, from, convertTo)) {
|
if (shouldReplaceBinding(newBoundValue, from, convertTo)) {
|
||||||
const binding = bindableProperties.find(el => el[convertFrom] === from)
|
const binding = bindableProperties.find(el => el[convertFrom] === from)
|
||||||
while (newBoundValue.includes(from)) {
|
let idx
|
||||||
newBoundValue = newBoundValue.replace(from, binding[convertTo])
|
do {
|
||||||
|
// see if any instances of this binding exist in the search string
|
||||||
|
idx = searchString.indexOf(from)
|
||||||
|
if (idx !== -1) {
|
||||||
|
let end = idx + from.length,
|
||||||
|
searchReplace = Array(binding[convertTo].length).join("*")
|
||||||
|
// blank out parts of the search string
|
||||||
|
searchString = replaceBetween(searchString, idx, end, searchReplace)
|
||||||
|
newBoundValue = replaceBetween(
|
||||||
|
newBoundValue,
|
||||||
|
idx,
|
||||||
|
end,
|
||||||
|
binding[convertTo]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
} while (idx !== -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = result.replace(boundValue, newBoundValue)
|
result = result.replace(boundValue, newBoundValue)
|
||||||
|
|
|
@ -1,21 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from "svelte"
|
|
||||||
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import analytics, { Events } from "analytics"
|
import analytics, { Events } from "analytics"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
|
||||||
const DeploymentStatus = {
|
|
||||||
SUCCESS: "SUCCESS",
|
|
||||||
PENDING: "PENDING",
|
|
||||||
FAILURE: "FAILURE",
|
|
||||||
}
|
|
||||||
|
|
||||||
const POLL_INTERVAL = 10000
|
|
||||||
|
|
||||||
let feedbackModal
|
let feedbackModal
|
||||||
let deployments = []
|
|
||||||
let poll
|
|
||||||
let publishModal
|
let publishModal
|
||||||
|
|
||||||
async function deployApp() {
|
async function deployApp() {
|
||||||
|
@ -34,62 +23,6 @@
|
||||||
notifications.error(`Error publishing app: ${err}`)
|
notifications.error(`Error publishing app: ${err}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchDeployments() {
|
|
||||||
try {
|
|
||||||
const response = await api.get(`/api/deployments`)
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
if (deployments.length > 0) {
|
|
||||||
checkIncomingDeploymentStatus(deployments, json)
|
|
||||||
}
|
|
||||||
|
|
||||||
deployments = json
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
clearInterval(poll)
|
|
||||||
notifications.error(
|
|
||||||
"Error fetching deployment history. Please try again."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required to check any updated deployment statuses between polls
|
|
||||||
function checkIncomingDeploymentStatus(current, incoming) {
|
|
||||||
for (let incomingDeployment of incoming) {
|
|
||||||
if (
|
|
||||||
incomingDeployment.status === DeploymentStatus.FAILURE ||
|
|
||||||
incomingDeployment.status === DeploymentStatus.SUCCESS
|
|
||||||
) {
|
|
||||||
const currentDeployment = current.find(
|
|
||||||
deployment => deployment._id === incomingDeployment._id
|
|
||||||
)
|
|
||||||
|
|
||||||
// We have just been notified of an ongoing deployments status change
|
|
||||||
if (
|
|
||||||
!currentDeployment ||
|
|
||||||
currentDeployment.status === DeploymentStatus.PENDING
|
|
||||||
) {
|
|
||||||
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
|
|
||||||
notifications.error(incomingDeployment.err)
|
|
||||||
} else {
|
|
||||||
notifications.send(
|
|
||||||
"Published to Production.",
|
|
||||||
"success",
|
|
||||||
"CheckmarkCircle"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
fetchDeployments()
|
|
||||||
poll = setInterval(fetchDeployments, POLL_INTERVAL)
|
|
||||||
})
|
|
||||||
|
|
||||||
onDestroy(() => clearInterval(poll))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary on:click={publishModal.show}>Publish</Button>
|
<Button secondary on:click={publishModal.show}>Publish</Button>
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
showConfirmButton={false}
|
showConfirmButton={false}
|
||||||
cancelText="Close"
|
cancelText="View changes"
|
||||||
showCloseIcon={false}
|
showCloseIcon={false}
|
||||||
title="Theme settings"
|
title="Theme settings"
|
||||||
>
|
>
|
||||||
|
|
|
@ -21,12 +21,12 @@
|
||||||
<ModalContent
|
<ModalContent
|
||||||
size="M"
|
size="M"
|
||||||
{onConfirm}
|
{onConfirm}
|
||||||
title="Upgrade to self-hosted"
|
title="Self-host Budibase"
|
||||||
confirmText="Upgrade"
|
confirmText="Self-host Budibase"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>Upgrade to Budibase self-hosting for free, and get SSO, unlimited apps,
|
>Self-host budibase for free, and get SSO, unlimited apps, and more - and
|
||||||
and more - and it only takes a few minutes!</span
|
it only takes a few minutes!</span
|
||||||
>
|
>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -4,9 +4,6 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
// don't react to these
|
|
||||||
let cloud = $admin.cloud
|
|
||||||
let shouldRedirect = !cloud || $admin.disableAccountPortal
|
|
||||||
|
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
$: multiTenancyEnabled = $admin.multiTenancy
|
||||||
$: hasAdminUser = $admin?.checklist?.adminUser?.checked
|
$: hasAdminUser = $admin?.checklist?.adminUser?.checked
|
||||||
|
@ -14,17 +11,33 @@
|
||||||
$: cloud = $admin.cloud
|
$: cloud = $admin.cloud
|
||||||
$: user = $auth.user
|
$: user = $auth.user
|
||||||
|
|
||||||
const validateTenantId = async () => {
|
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||||
// set the tenant from the url in the cloud
|
|
||||||
const tenantId = window.location.host.split(".")[0]
|
|
||||||
|
|
||||||
if (!tenantId.includes("localhost:")) {
|
const validateTenantId = async () => {
|
||||||
// user doesn't have permission to access this tenant - kick them out
|
const host = window.location.host
|
||||||
if (user && user.tenantId !== tenantId) {
|
if (host.includes("localhost:")) {
|
||||||
await auth.logout()
|
// ignore local dev
|
||||||
await auth.setOrganisation(null)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user && user.tenantId) {
|
||||||
|
let urlTenantId
|
||||||
|
const hostParts = host.split(".")
|
||||||
|
|
||||||
|
// only run validation when we know we are in a tenant url
|
||||||
|
// not when we visit the root budibase.app domain
|
||||||
|
// e.g. ['tenant', 'budibase', 'app'] vs ['budibase', 'app']
|
||||||
|
if (hostParts.length > 2) {
|
||||||
|
urlTenantId = hostParts[0]
|
||||||
} else {
|
} else {
|
||||||
await auth.setOrganisation(tenantId)
|
// no tenant in the url - send to account portal to fix this
|
||||||
|
window.location.href = $admin.accountPortalUrl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.tenantId !== urlTenantId) {
|
||||||
|
// user should not be here - play it safe and log them out
|
||||||
|
await auth.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +46,7 @@
|
||||||
await auth.checkAuth()
|
await auth.checkAuth()
|
||||||
await admin.init()
|
await admin.init()
|
||||||
|
|
||||||
if (cloud && multiTenancyEnabled) {
|
if (useAccountPortal && multiTenancyEnabled) {
|
||||||
await validateTenantId()
|
await validateTenantId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +54,11 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// We should never see the org or admin user creation screens in the cloud
|
|
||||||
const apiReady = $admin.loaded && $auth.loaded
|
const apiReady = $admin.loaded && $auth.loaded
|
||||||
// if tenant is not set go to it
|
// if tenant is not set go to it
|
||||||
if (
|
if (
|
||||||
loaded &&
|
loaded &&
|
||||||
shouldRedirect &&
|
!useAccountPortal &&
|
||||||
apiReady &&
|
apiReady &&
|
||||||
multiTenancyEnabled &&
|
multiTenancyEnabled &&
|
||||||
!tenantSet
|
!tenantSet
|
||||||
|
@ -54,7 +66,7 @@
|
||||||
$redirect("./auth/org")
|
$redirect("./auth/org")
|
||||||
}
|
}
|
||||||
// Force creation of an admin user if one doesn't exist
|
// Force creation of an admin user if one doesn't exist
|
||||||
else if (loaded && shouldRedirect && apiReady && !hasAdminUser) {
|
else if (loaded && !useAccountPortal && apiReady && !hasAdminUser) {
|
||||||
$redirect("./admin")
|
$redirect("./admin")
|
||||||
}
|
}
|
||||||
// Redirect to log in at any time if the user isn't authenticated
|
// Redirect to log in at any time if the user isn't authenticated
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
|
$: cloud = $admin.cloud
|
||||||
|
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if ($admin?.checklist?.adminUser.checked) {
|
if ($admin?.checklist?.adminUser.checked || useAccountPortal) {
|
||||||
$redirect("../")
|
$redirect("../")
|
||||||
} else {
|
} else {
|
||||||
loaded = true
|
loaded = true
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
let tenantId = get(auth).tenantSet ? get(auth).tenantId : ""
|
let tenantId = get(auth).tenantSet ? get(auth).tenantId : ""
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
$: multiTenancyEnabled = $admin.multiTenancy
|
||||||
$: cloud = $admin.cloud
|
$: cloud = $admin.cloud
|
||||||
$: disableAccountPortal = $admin.disableAccountPortal
|
|
||||||
|
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||||
|
|
||||||
async function setOrg() {
|
async function setOrg() {
|
||||||
if (tenantId == null || tenantId === "") {
|
if (tenantId == null || tenantId === "") {
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await auth.checkQueryString()
|
await auth.checkQueryString()
|
||||||
if (!multiTenancyEnabled || (cloud && !disableAccountPortal)) {
|
if (!multiTenancyEnabled || useAccountPortal) {
|
||||||
$goto("../")
|
$goto("../")
|
||||||
} else {
|
} else {
|
||||||
admin.unload()
|
admin.unload()
|
||||||
|
|
|
@ -216,6 +216,7 @@
|
||||||
<div class="filter">
|
<div class="filter">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<Select
|
<Select
|
||||||
|
autoWidth
|
||||||
bind:value={sortBy}
|
bind:value={sortBy}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
options={[
|
options={[
|
||||||
|
@ -224,8 +225,10 @@
|
||||||
{ label: "Sort by status", value: "status" },
|
{ label: "Sort by status", value: "status" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<div class="desktop-search">
|
||||||
<Search placeholder="Search" bind:value={searchTerm} />
|
<Search placeholder="Search" bind:value={searchTerm} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
on:click={() => (layout = "grid")}
|
on:click={() => (layout = "grid")}
|
||||||
|
@ -241,6 +244,9 @@
|
||||||
/>
|
/>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mobile-search">
|
||||||
|
<Search placeholder="Search" bind:value={searchTerm} />
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class:appGrid={layout === "grid"}
|
class:appGrid={layout === "grid"}
|
||||||
class:appTable={layout === "table"}
|
class:appTable={layout === "table"}
|
||||||
|
@ -318,6 +324,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select {
|
.select {
|
||||||
|
@ -325,6 +332,12 @@
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-gap: 10px;
|
grid-gap: 10px;
|
||||||
}
|
}
|
||||||
|
.filter :global(.spectrum-ActionGroup) {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.mobile-search {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.appGrid {
|
.appGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -364,5 +377,11 @@
|
||||||
.appTable {
|
.appTable {
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
}
|
}
|
||||||
|
.desktop-search {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.mobile-search {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"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": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"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": "^0.9.148",
|
"@budibase/bbui": "^0.9.149-alpha.3",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^0.9.148",
|
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -64,9 +64,9 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.148",
|
"@budibase/auth": "^0.9.149-alpha.3",
|
||||||
"@budibase/client": "^0.9.148",
|
"@budibase/client": "^0.9.149-alpha.3",
|
||||||
"@budibase/string-templates": "^0.9.148",
|
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
"@koa/router": "8.0.0",
|
"@koa/router": "8.0.0",
|
||||||
"@sendgrid/mail": "7.1.1",
|
"@sendgrid/mail": "7.1.1",
|
||||||
|
|
|
@ -7,14 +7,19 @@ if (env.POSTHOG_TOKEN && env.ENABLE_ANALYTICS && !env.SELF_HOSTED) {
|
||||||
posthogClient = new PostHog(env.POSTHOG_TOKEN)
|
posthogClient = new PostHog(env.POSTHOG_TOKEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isEnabled = async function (ctx) {
|
exports.isEnabled = async ctx => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
enabled: !env.SELF_HOSTED && env.ENABLE_ANALYTICS === "true",
|
enabled: !env.SELF_HOSTED && env.ENABLE_ANALYTICS === "true",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.endUserPing = async (ctx, next) => {
|
exports.endUserPing = async ctx => {
|
||||||
if (!posthogClient) return next()
|
if (!posthogClient) {
|
||||||
|
ctx.body = {
|
||||||
|
ping: false,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
posthogClient.capture("budibase:end_user_ping", {
|
posthogClient.capture("budibase:end_user_ping", {
|
||||||
userId: ctx.user && ctx.user._id,
|
userId: ctx.user && ctx.user._id,
|
||||||
|
|
|
@ -64,6 +64,7 @@ async function storeDeploymentHistory(deployment) {
|
||||||
|
|
||||||
async function initDeployedApp(prodAppId) {
|
async function initDeployedApp(prodAppId) {
|
||||||
const db = new CouchDB(prodAppId)
|
const db = new CouchDB(prodAppId)
|
||||||
|
console.log("Reading automation docs")
|
||||||
const automations = (
|
const automations = (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
getAutomationParams(null, {
|
getAutomationParams(null, {
|
||||||
|
@ -71,12 +72,17 @@ async function initDeployedApp(prodAppId) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).rows.map(row => row.doc)
|
).rows.map(row => row.doc)
|
||||||
|
console.log("You have " + automations.length + " automations")
|
||||||
const promises = []
|
const promises = []
|
||||||
|
console.log("Disabling prod crons..")
|
||||||
await disableAllCrons(prodAppId)
|
await disableAllCrons(prodAppId)
|
||||||
|
console.log("Prod Cron triggers disabled..")
|
||||||
|
console.log("Enabling cron triggers for deployed app..")
|
||||||
for (let automation of automations) {
|
for (let automation of automations) {
|
||||||
promises.push(enableCronTrigger(prodAppId, automation))
|
promises.push(enableCronTrigger(prodAppId, automation))
|
||||||
}
|
}
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
console.log("Enabled cron triggers for deployed app..")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deployApp(deployment) {
|
async function deployApp(deployment) {
|
||||||
|
@ -88,13 +94,18 @@ async function deployApp(deployment) {
|
||||||
target: productionAppId,
|
target: productionAppId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log("Replication object created")
|
||||||
|
|
||||||
await replication.replicate()
|
await replication.replicate()
|
||||||
|
console.log("replication complete.. replacing app meta doc")
|
||||||
const db = new CouchDB(productionAppId)
|
const db = new CouchDB(productionAppId)
|
||||||
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||||
appDoc.appId = productionAppId
|
appDoc.appId = productionAppId
|
||||||
appDoc.instance._id = productionAppId
|
appDoc.instance._id = productionAppId
|
||||||
await db.put(appDoc)
|
await db.put(appDoc)
|
||||||
|
console.log("New app doc written successfully.")
|
||||||
|
|
||||||
|
console.log("Setting up live repl between dev and prod")
|
||||||
// Set up live sync between the live and dev instances
|
// Set up live sync between the live and dev instances
|
||||||
const liveReplication = new Replication({
|
const liveReplication = new Replication({
|
||||||
source: productionAppId,
|
source: productionAppId,
|
||||||
|
@ -105,8 +116,11 @@ async function deployApp(deployment) {
|
||||||
return doc._id !== DocumentTypes.APP_METADATA
|
return doc._id !== DocumentTypes.APP_METADATA
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
console.log("Set up live repl between dev and prod")
|
||||||
|
|
||||||
|
console.log("Initialising deployed app")
|
||||||
await initDeployedApp(productionAppId)
|
await initDeployedApp(productionAppId)
|
||||||
|
console.log("Init complete, setting deployment to successful")
|
||||||
deployment.setStatus(DeploymentStatus.SUCCESS)
|
deployment.setStatus(DeploymentStatus.SUCCESS)
|
||||||
await storeDeploymentHistory(deployment)
|
await storeDeploymentHistory(deployment)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -153,9 +167,13 @@ exports.deploymentProgress = async function (ctx) {
|
||||||
|
|
||||||
exports.deployApp = async function (ctx) {
|
exports.deployApp = async function (ctx) {
|
||||||
let deployment = new Deployment(ctx.appId)
|
let deployment = new Deployment(ctx.appId)
|
||||||
|
console.log("Deployment object created")
|
||||||
deployment.setStatus(DeploymentStatus.PENDING)
|
deployment.setStatus(DeploymentStatus.PENDING)
|
||||||
|
console.log("Deployment object set to pending")
|
||||||
deployment = await storeDeploymentHistory(deployment)
|
deployment = await storeDeploymentHistory(deployment)
|
||||||
|
console.log("Stored deployment history")
|
||||||
|
|
||||||
|
console.log("Deploying app...")
|
||||||
await deployApp(deployment)
|
await deployApp(deployment)
|
||||||
|
|
||||||
ctx.body = deployment
|
ctx.body = deployment
|
||||||
|
|
|
@ -403,16 +403,32 @@ exports.fetchEnrichedRow = async ctx => {
|
||||||
rowId,
|
rowId,
|
||||||
})
|
})
|
||||||
// look up the actual rows based on the ids
|
// look up the actual rows based on the ids
|
||||||
const response = await db.allDocs({
|
let response = (
|
||||||
|
await db.allDocs({
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
keys: linkVals.map(linkVal => linkVal.id),
|
keys: linkVals.map(linkVal => linkVal.id),
|
||||||
})
|
})
|
||||||
|
).rows.map(row => row.doc)
|
||||||
|
// group responses by table
|
||||||
|
let groups = {},
|
||||||
|
tables = {}
|
||||||
|
for (let row of response) {
|
||||||
|
const linkedTableId = row.tableId
|
||||||
|
if (groups[linkedTableId] == null) {
|
||||||
|
groups[linkedTableId] = [row]
|
||||||
|
tables[linkedTableId] = await db.get(linkedTableId)
|
||||||
|
} else {
|
||||||
|
groups[linkedTableId].push(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let linkedRows = []
|
||||||
|
for (let [tableId, rows] of Object.entries(groups)) {
|
||||||
// need to include the IDs in these rows for any links they may have
|
// need to include the IDs in these rows for any links they may have
|
||||||
let linkedRows = await outputProcessing(
|
linkedRows = linkedRows.concat(
|
||||||
ctx,
|
await outputProcessing(ctx, tables[tableId], rows)
|
||||||
table,
|
|
||||||
response.rows.map(row => row.doc)
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// insert the link rows in the correct place throughout the main row
|
// insert the link rows in the correct place throughout the main row
|
||||||
for (let fieldName of Object.keys(table.schema)) {
|
for (let fieldName of Object.keys(table.schema)) {
|
||||||
let field = table.schema[fieldName]
|
let field = table.schema[fieldName]
|
||||||
|
|
|
@ -3,7 +3,8 @@ const controller = require("../controllers/analytics")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router.get("/api/analytics", controller.isEnabled)
|
router
|
||||||
router.post("/api/analytics/ping", controller.endUserPing)
|
.get("/api/analytics", controller.isEnabled)
|
||||||
|
.post("/api/analytics/ping", controller.endUserPing)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -99,6 +99,7 @@ function processAutoColumn(
|
||||||
row,
|
row,
|
||||||
opts = { reprocessing: false, noAutoRelationships: false }
|
opts = { reprocessing: false, noAutoRelationships: false }
|
||||||
) {
|
) {
|
||||||
|
let noUser = !user || !user.userId
|
||||||
let now = new Date().toISOString()
|
let now = new Date().toISOString()
|
||||||
// if a row doesn't have a revision then it doesn't exist yet
|
// if a row doesn't have a revision then it doesn't exist yet
|
||||||
const creating = !row._rev
|
const creating = !row._rev
|
||||||
|
@ -108,7 +109,12 @@ function processAutoColumn(
|
||||||
}
|
}
|
||||||
switch (schema.subtype) {
|
switch (schema.subtype) {
|
||||||
case AutoFieldSubTypes.CREATED_BY:
|
case AutoFieldSubTypes.CREATED_BY:
|
||||||
if (creating && !opts.reprocessing && !opts.noAutoRelationships) {
|
if (
|
||||||
|
creating &&
|
||||||
|
!opts.reprocessing &&
|
||||||
|
!opts.noAutoRelationships &&
|
||||||
|
!noUser
|
||||||
|
) {
|
||||||
row[key] = [user.userId]
|
row[key] = [user.userId]
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -118,7 +124,7 @@ function processAutoColumn(
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case AutoFieldSubTypes.UPDATED_BY:
|
case AutoFieldSubTypes.UPDATED_BY:
|
||||||
if (!opts.reprocessing && !opts.noAutoRelationships) {
|
if (!opts.reprocessing && !opts.noAutoRelationships && !noUser) {
|
||||||
row[key] = [user.userId]
|
row[key] = [user.userId]
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -52,7 +52,8 @@ exports.sendSmtpEmail = async (to, from, subject, contents, automation) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw "Unable to send email."
|
const error = await response.text()
|
||||||
|
throw `Unable to send email - ${error}`
|
||||||
}
|
}
|
||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"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,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.148",
|
"version": "0.9.149-alpha.3",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -20,13 +20,15 @@
|
||||||
"multi:enable": "node scripts/multiTenancy.js enable",
|
"multi:enable": "node scripts/multiTenancy.js enable",
|
||||||
"multi:disable": "node scripts/multiTenancy.js disable",
|
"multi:disable": "node scripts/multiTenancy.js disable",
|
||||||
"selfhost:enable": "node scripts/selfhost.js enable",
|
"selfhost:enable": "node scripts/selfhost.js enable",
|
||||||
"selfhost:disable": "node scripts/selfhost.js disable"
|
"selfhost:disable": "node scripts/selfhost.js disable",
|
||||||
|
"localdomain:enable": "node scripts/localdomain.js enable",
|
||||||
|
"localdomain:disable": "node scripts/localdomain.js disable"
|
||||||
},
|
},
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.148",
|
"@budibase/auth": "^0.9.149-alpha.3",
|
||||||
"@budibase/string-templates": "^0.9.148",
|
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
"aws-sdk": "^2.811.0",
|
"aws-sdk": "^2.811.0",
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const updateDotEnv = require("update-dotenv")
|
||||||
|
|
||||||
|
const arg = process.argv.slice(2)[0]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For testing multi tenancy sub domains locally.
|
||||||
|
*
|
||||||
|
* Relies on an entry in /etc/hosts e.g:
|
||||||
|
*
|
||||||
|
* 127.0.0.1 local.com
|
||||||
|
*
|
||||||
|
* and an entry for each tenant you wish to test locally e.g:
|
||||||
|
*
|
||||||
|
* 127.0.0.1 t1.local.com
|
||||||
|
* 127.0.0.1 t2.local.com
|
||||||
|
*/
|
||||||
|
updateDotEnv({
|
||||||
|
ACCOUNT_PORTAL_URL:
|
||||||
|
arg === "enable" ? "http://local.com:10001" : "http://localhost:10001",
|
||||||
|
COOKIE_DOMAIN: arg === "enable" ? ".local.com" : "",
|
||||||
|
}).then(() => console.log("Updated worker!"))
|
|
@ -87,7 +87,7 @@ router
|
||||||
if (ctx.publicEndpoint) {
|
if (ctx.publicEndpoint) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
if (!ctx.isAuthenticated || !ctx.user.budibaseAccess) {
|
if ((!ctx.isAuthenticated || !ctx.user.budibaseAccess) && !ctx.internal) {
|
||||||
ctx.throw(403, "Unauthorized - no public worker access")
|
ctx.throw(403, "Unauthorized - no public worker access")
|
||||||
}
|
}
|
||||||
return next()
|
return next()
|
||||||
|
|
Loading…
Reference in New Issue