commit
ef23047084
|
@ -50,6 +50,11 @@ static_resources:
|
|||
route:
|
||||
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
|
||||
- match: { prefix: "/api/" }
|
||||
route:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
"multi:disable": "lerna run multi:disable",
|
||||
"selfhost:enable": "lerna run selfhost:enable",
|
||||
"selfhost:disable": "lerna run selfhost:disable",
|
||||
"localdomain:enable": "lerna run localdomain:enable",
|
||||
"localdomain:disable": "lerna run localdomain:disable",
|
||||
"postinstall": "husky install"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/auth",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"description": "Authentication middlewares for budibase builder and apps",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"license": "AGPL-3.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
on:input={onInput}
|
||||
on:keyup={updateValueOnEnter}
|
||||
{type}
|
||||
inputmode={type === "number" ? "decimal" : "text"}
|
||||
class="spectrum-Textfield-input"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -24,9 +24,7 @@ context("Create a Table", () => {
|
|||
it("updates a column on the table", () => {
|
||||
cy.get(".title").click()
|
||||
cy.get(".spectrum-Table-editIcon > use").click()
|
||||
cy.get("input")
|
||||
.eq(1)
|
||||
.type("updated", { force: true })
|
||||
cy.get("input").eq(1).type("updated", { force: true })
|
||||
// Unset table display column
|
||||
cy.get(".spectrum-Switch-input").eq(1).click()
|
||||
cy.contains("Save Column").click()
|
||||
|
@ -45,9 +43,7 @@ context("Create a Table", () => {
|
|||
it("deletes a row", () => {
|
||||
cy.get(".spectrum-Checkbox-input").check({ force: true })
|
||||
cy.contains("Delete 1 row(s)").click()
|
||||
cy.get(".spectrum-Modal")
|
||||
.contains("Delete")
|
||||
.click()
|
||||
cy.get(".spectrum-Modal").contains("Delete").click()
|
||||
cy.contains("RoverUpdated").should("not.exist")
|
||||
})
|
||||
|
||||
|
@ -56,15 +52,18 @@ context("Create a Table", () => {
|
|||
cy.get(".spectrum-Table-editIcon > use").click()
|
||||
cy.contains("Delete").click()
|
||||
cy.wait(50)
|
||||
cy.contains("Delete Column")
|
||||
.click()
|
||||
cy.contains("Delete Column").click()
|
||||
cy.contains("nameupdated").should("not.exist")
|
||||
})
|
||||
|
||||
it("deletes a table", () => {
|
||||
cy.get(".actions > :nth-child(1) > .icon > .spectrum-Icon > use")
|
||||
.eq(1)
|
||||
.click({ force: true })
|
||||
cy.get(".nav-item")
|
||||
.contains("dog")
|
||||
.parents(".nav-item")
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(".actions .spectrum-Icon").click({ force: true })
|
||||
})
|
||||
cy.get(".spectrum-Menu > :nth-child(2)").click()
|
||||
cy.contains("Delete Table").click()
|
||||
cy.contains("dog").should("not.exist")
|
||||
|
|
|
@ -28,11 +28,7 @@ context("Create a View", () => {
|
|||
const headers = Array.from($headers).map(header =>
|
||||
header.textContent.trim()
|
||||
)
|
||||
expect(removeSpacing(headers)).to.deep.eq([
|
||||
"group",
|
||||
"age",
|
||||
"rating",
|
||||
])
|
||||
expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"])
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -57,11 +53,12 @@ context("Create a View", () => {
|
|||
})
|
||||
|
||||
it("creates a stats calculation view based on age", () => {
|
||||
cy.wait(1000)
|
||||
cy.contains("Calculate").click()
|
||||
cy.get(".modal-inner-wrapper").within(() => {
|
||||
cy.get(".spectrum-Picker-label").eq(0).click()
|
||||
cy.contains("Statistics").click()
|
||||
|
||||
|
||||
cy.get(".spectrum-Picker-label").eq(1).click()
|
||||
cy.contains("age").click({ force: true })
|
||||
|
||||
|
@ -104,20 +101,20 @@ context("Create a View", () => {
|
|||
cy.get(".spectrum-Table-cell").then($values => {
|
||||
let values = Array.from($values).map(header => header.textContent.trim())
|
||||
expect(values).to.deep.eq([
|
||||
"Students",
|
||||
"70",
|
||||
"20",
|
||||
"25",
|
||||
"3",
|
||||
"1650",
|
||||
"23.333333333333332",
|
||||
"Teachers",
|
||||
"85",
|
||||
"36",
|
||||
"49",
|
||||
"2",
|
||||
"3697",
|
||||
"42.5",
|
||||
"Students",
|
||||
"70",
|
||||
"20",
|
||||
"25",
|
||||
"3",
|
||||
"1650",
|
||||
"23.333333333333332",
|
||||
"Teachers",
|
||||
"85",
|
||||
"36",
|
||||
"49",
|
||||
"2",
|
||||
"3697",
|
||||
"42.5",
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -65,10 +65,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.148",
|
||||
"@budibase/client": "^0.9.148",
|
||||
"@budibase/bbui": "^0.9.149-alpha.3",
|
||||
"@budibase/client": "^0.9.149-alpha.3",
|
||||
"@budibase/colorpicker": "1.1.2",
|
||||
"@budibase/string-templates": "^0.9.148",
|
||||
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { cloneDeep } from "lodash/fp"
|
||||
import { get } from "svelte/store"
|
||||
import {
|
||||
findAllMatchingComponents,
|
||||
findComponent,
|
||||
findComponentPath,
|
||||
findAllMatchingComponents,
|
||||
} from "./storeUtils"
|
||||
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 { TableNames } from "../constants"
|
||||
|
||||
|
@ -422,6 +422,10 @@ function shouldReplaceBinding(currentValue, from, convertTo) {
|
|||
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.
|
||||
*/
|
||||
|
@ -431,6 +435,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
|||
if (typeof textWithBindings !== "string") {
|
||||
return textWithBindings
|
||||
}
|
||||
// work from longest to shortest
|
||||
const convertFromProps = bindableProperties
|
||||
.map(el => el[convertFrom])
|
||||
.sort((a, b) => {
|
||||
|
@ -440,12 +445,29 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
|||
let result = textWithBindings
|
||||
for (let boundValue of boundValues) {
|
||||
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) {
|
||||
if (shouldReplaceBinding(newBoundValue, from, convertTo)) {
|
||||
const binding = bindableProperties.find(el => el[convertFrom] === from)
|
||||
while (newBoundValue.includes(from)) {
|
||||
newBoundValue = newBoundValue.replace(from, binding[convertTo])
|
||||
}
|
||||
let idx
|
||||
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)
|
||||
|
|
|
@ -1,21 +1,10 @@
|
|||
<script>
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
import analytics, { Events } from "analytics"
|
||||
import { store } from "builderStore"
|
||||
|
||||
const DeploymentStatus = {
|
||||
SUCCESS: "SUCCESS",
|
||||
PENDING: "PENDING",
|
||||
FAILURE: "FAILURE",
|
||||
}
|
||||
|
||||
const POLL_INTERVAL = 10000
|
||||
|
||||
let feedbackModal
|
||||
let deployments = []
|
||||
let poll
|
||||
let publishModal
|
||||
|
||||
async function deployApp() {
|
||||
|
@ -34,62 +23,6 @@
|
|||
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>
|
||||
|
||||
<Button secondary on:click={publishModal.show}>Publish</Button>
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<Modal bind:this={modal}>
|
||||
<ModalContent
|
||||
showConfirmButton={false}
|
||||
cancelText="Close"
|
||||
cancelText="View changes"
|
||||
showCloseIcon={false}
|
||||
title="Theme settings"
|
||||
>
|
||||
|
|
|
@ -21,12 +21,12 @@
|
|||
<ModalContent
|
||||
size="M"
|
||||
{onConfirm}
|
||||
title="Upgrade to self-hosted"
|
||||
confirmText="Upgrade"
|
||||
title="Self-host Budibase"
|
||||
confirmText="Self-host Budibase"
|
||||
>
|
||||
<span
|
||||
>Upgrade to Budibase self-hosting for free, and get SSO, unlimited apps,
|
||||
and more - and it only takes a few minutes!</span
|
||||
>Self-host budibase for free, and get SSO, unlimited apps, and more - and
|
||||
it only takes a few minutes!</span
|
||||
>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
import { onMount } from "svelte"
|
||||
|
||||
let loaded = false
|
||||
// don't react to these
|
||||
let cloud = $admin.cloud
|
||||
let shouldRedirect = !cloud || $admin.disableAccountPortal
|
||||
|
||||
$: multiTenancyEnabled = $admin.multiTenancy
|
||||
$: hasAdminUser = $admin?.checklist?.adminUser?.checked
|
||||
|
@ -14,17 +11,33 @@
|
|||
$: cloud = $admin.cloud
|
||||
$: user = $auth.user
|
||||
|
||||
const validateTenantId = async () => {
|
||||
// set the tenant from the url in the cloud
|
||||
const tenantId = window.location.host.split(".")[0]
|
||||
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||
|
||||
if (!tenantId.includes("localhost:")) {
|
||||
// user doesn't have permission to access this tenant - kick them out
|
||||
if (user && user.tenantId !== tenantId) {
|
||||
await auth.logout()
|
||||
await auth.setOrganisation(null)
|
||||
const validateTenantId = async () => {
|
||||
const host = window.location.host
|
||||
if (host.includes("localhost:")) {
|
||||
// ignore local dev
|
||||
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 {
|
||||
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 admin.init()
|
||||
|
||||
if (cloud && multiTenancyEnabled) {
|
||||
if (useAccountPortal && multiTenancyEnabled) {
|
||||
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
|
||||
// if tenant is not set go to it
|
||||
if (
|
||||
loaded &&
|
||||
shouldRedirect &&
|
||||
!useAccountPortal &&
|
||||
apiReady &&
|
||||
multiTenancyEnabled &&
|
||||
!tenantSet
|
||||
|
@ -54,7 +66,7 @@
|
|||
$redirect("./auth/org")
|
||||
}
|
||||
// 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 to log in at any time if the user isn't authenticated
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
|
||||
let loaded = false
|
||||
|
||||
$: cloud = $admin.cloud
|
||||
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||
|
||||
onMount(() => {
|
||||
if ($admin?.checklist?.adminUser.checked) {
|
||||
if ($admin?.checklist?.adminUser.checked || useAccountPortal) {
|
||||
$redirect("../")
|
||||
} else {
|
||||
loaded = true
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
let tenantId = get(auth).tenantSet ? get(auth).tenantId : ""
|
||||
$: multiTenancyEnabled = $admin.multiTenancy
|
||||
$: cloud = $admin.cloud
|
||||
$: disableAccountPortal = $admin.disableAccountPortal
|
||||
|
||||
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||
|
||||
async function setOrg() {
|
||||
if (tenantId == null || tenantId === "") {
|
||||
|
@ -27,7 +28,7 @@
|
|||
|
||||
onMount(async () => {
|
||||
await auth.checkQueryString()
|
||||
if (!multiTenancyEnabled || (cloud && !disableAccountPortal)) {
|
||||
if (!multiTenancyEnabled || useAccountPortal) {
|
||||
$goto("../")
|
||||
} else {
|
||||
admin.unload()
|
||||
|
|
|
@ -216,6 +216,7 @@
|
|||
<div class="filter">
|
||||
<div class="select">
|
||||
<Select
|
||||
autoWidth
|
||||
bind:value={sortBy}
|
||||
placeholder={null}
|
||||
options={[
|
||||
|
@ -224,7 +225,9 @@
|
|||
{ label: "Sort by status", value: "status" },
|
||||
]}
|
||||
/>
|
||||
<Search placeholder="Search" bind:value={searchTerm} />
|
||||
<div class="desktop-search">
|
||||
<Search placeholder="Search" bind:value={searchTerm} />
|
||||
</div>
|
||||
</div>
|
||||
<ActionGroup>
|
||||
<ActionButton
|
||||
|
@ -241,6 +244,9 @@
|
|||
/>
|
||||
</ActionGroup>
|
||||
</div>
|
||||
<div class="mobile-search">
|
||||
<Search placeholder="Search" bind:value={searchTerm} />
|
||||
</div>
|
||||
<div
|
||||
class:appGrid={layout === "grid"}
|
||||
class:appTable={layout === "table"}
|
||||
|
@ -318,6 +324,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.select {
|
||||
|
@ -325,6 +332,12 @@
|
|||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
.filter :global(.spectrum-ActionGroup) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.mobile-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.appGrid {
|
||||
display: grid;
|
||||
|
@ -364,5 +377,11 @@
|
|||
.appTable {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
.desktop-search {
|
||||
display: none;
|
||||
}
|
||||
.mobile-search {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.148",
|
||||
"@budibase/bbui": "^0.9.149-alpha.3",
|
||||
"@budibase/standard-components": "^0.9.139",
|
||||
"@budibase/string-templates": "^0.9.148",
|
||||
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||
"regexparam": "^1.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-spa-router": "^3.0.5"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -64,9 +64,9 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.148",
|
||||
"@budibase/client": "^0.9.148",
|
||||
"@budibase/string-templates": "^0.9.148",
|
||||
"@budibase/auth": "^0.9.149-alpha.3",
|
||||
"@budibase/client": "^0.9.149-alpha.3",
|
||||
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
"@koa/router": "8.0.0",
|
||||
"@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)
|
||||
}
|
||||
|
||||
exports.isEnabled = async function (ctx) {
|
||||
exports.isEnabled = async ctx => {
|
||||
ctx.body = {
|
||||
enabled: !env.SELF_HOSTED && env.ENABLE_ANALYTICS === "true",
|
||||
}
|
||||
}
|
||||
|
||||
exports.endUserPing = async (ctx, next) => {
|
||||
if (!posthogClient) return next()
|
||||
exports.endUserPing = async ctx => {
|
||||
if (!posthogClient) {
|
||||
ctx.body = {
|
||||
ping: false,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
posthogClient.capture("budibase:end_user_ping", {
|
||||
userId: ctx.user && ctx.user._id,
|
||||
|
|
|
@ -64,6 +64,7 @@ async function storeDeploymentHistory(deployment) {
|
|||
|
||||
async function initDeployedApp(prodAppId) {
|
||||
const db = new CouchDB(prodAppId)
|
||||
console.log("Reading automation docs")
|
||||
const automations = (
|
||||
await db.allDocs(
|
||||
getAutomationParams(null, {
|
||||
|
@ -71,12 +72,17 @@ async function initDeployedApp(prodAppId) {
|
|||
})
|
||||
)
|
||||
).rows.map(row => row.doc)
|
||||
console.log("You have " + automations.length + " automations")
|
||||
const promises = []
|
||||
console.log("Disabling prod crons..")
|
||||
await disableAllCrons(prodAppId)
|
||||
console.log("Prod Cron triggers disabled..")
|
||||
console.log("Enabling cron triggers for deployed app..")
|
||||
for (let automation of automations) {
|
||||
promises.push(enableCronTrigger(prodAppId, automation))
|
||||
}
|
||||
await Promise.all(promises)
|
||||
console.log("Enabled cron triggers for deployed app..")
|
||||
}
|
||||
|
||||
async function deployApp(deployment) {
|
||||
|
@ -88,13 +94,18 @@ async function deployApp(deployment) {
|
|||
target: productionAppId,
|
||||
})
|
||||
|
||||
console.log("Replication object created")
|
||||
|
||||
await replication.replicate()
|
||||
console.log("replication complete.. replacing app meta doc")
|
||||
const db = new CouchDB(productionAppId)
|
||||
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||
appDoc.appId = productionAppId
|
||||
appDoc.instance._id = productionAppId
|
||||
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
|
||||
const liveReplication = new Replication({
|
||||
source: productionAppId,
|
||||
|
@ -105,8 +116,11 @@ async function deployApp(deployment) {
|
|||
return doc._id !== DocumentTypes.APP_METADATA
|
||||
},
|
||||
})
|
||||
console.log("Set up live repl between dev and prod")
|
||||
|
||||
console.log("Initialising deployed app")
|
||||
await initDeployedApp(productionAppId)
|
||||
console.log("Init complete, setting deployment to successful")
|
||||
deployment.setStatus(DeploymentStatus.SUCCESS)
|
||||
await storeDeploymentHistory(deployment)
|
||||
} catch (err) {
|
||||
|
@ -153,9 +167,13 @@ exports.deploymentProgress = async function (ctx) {
|
|||
|
||||
exports.deployApp = async function (ctx) {
|
||||
let deployment = new Deployment(ctx.appId)
|
||||
console.log("Deployment object created")
|
||||
deployment.setStatus(DeploymentStatus.PENDING)
|
||||
console.log("Deployment object set to pending")
|
||||
deployment = await storeDeploymentHistory(deployment)
|
||||
console.log("Stored deployment history")
|
||||
|
||||
console.log("Deploying app...")
|
||||
await deployApp(deployment)
|
||||
|
||||
ctx.body = deployment
|
||||
|
|
|
@ -403,16 +403,32 @@ exports.fetchEnrichedRow = async ctx => {
|
|||
rowId,
|
||||
})
|
||||
// look up the actual rows based on the ids
|
||||
const response = await db.allDocs({
|
||||
include_docs: true,
|
||||
keys: linkVals.map(linkVal => linkVal.id),
|
||||
})
|
||||
// need to include the IDs in these rows for any links they may have
|
||||
let linkedRows = await outputProcessing(
|
||||
ctx,
|
||||
table,
|
||||
response.rows.map(row => row.doc)
|
||||
)
|
||||
let response = (
|
||||
await db.allDocs({
|
||||
include_docs: true,
|
||||
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
|
||||
linkedRows = linkedRows.concat(
|
||||
await outputProcessing(ctx, tables[tableId], rows)
|
||||
)
|
||||
}
|
||||
|
||||
// insert the link rows in the correct place throughout the main row
|
||||
for (let fieldName of Object.keys(table.schema)) {
|
||||
let field = table.schema[fieldName]
|
||||
|
|
|
@ -3,7 +3,8 @@ const controller = require("../controllers/analytics")
|
|||
|
||||
const router = Router()
|
||||
|
||||
router.get("/api/analytics", controller.isEnabled)
|
||||
router.post("/api/analytics/ping", controller.endUserPing)
|
||||
router
|
||||
.get("/api/analytics", controller.isEnabled)
|
||||
.post("/api/analytics/ping", controller.endUserPing)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -99,6 +99,7 @@ function processAutoColumn(
|
|||
row,
|
||||
opts = { reprocessing: false, noAutoRelationships: false }
|
||||
) {
|
||||
let noUser = !user || !user.userId
|
||||
let now = new Date().toISOString()
|
||||
// if a row doesn't have a revision then it doesn't exist yet
|
||||
const creating = !row._rev
|
||||
|
@ -108,7 +109,12 @@ function processAutoColumn(
|
|||
}
|
||||
switch (schema.subtype) {
|
||||
case AutoFieldSubTypes.CREATED_BY:
|
||||
if (creating && !opts.reprocessing && !opts.noAutoRelationships) {
|
||||
if (
|
||||
creating &&
|
||||
!opts.reprocessing &&
|
||||
!opts.noAutoRelationships &&
|
||||
!noUser
|
||||
) {
|
||||
row[key] = [user.userId]
|
||||
}
|
||||
break
|
||||
|
@ -118,7 +124,7 @@ function processAutoColumn(
|
|||
}
|
||||
break
|
||||
case AutoFieldSubTypes.UPDATED_BY:
|
||||
if (!opts.reprocessing && !opts.noAutoRelationships) {
|
||||
if (!opts.reprocessing && !opts.noAutoRelationships && !noUser) {
|
||||
row[key] = [user.userId]
|
||||
}
|
||||
break
|
||||
|
|
|
@ -52,7 +52,8 @@ exports.sendSmtpEmail = async (to, from, subject, contents, automation) => {
|
|||
)
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw "Unable to send email."
|
||||
const error = await response.text()
|
||||
throw `Unable to send email - ${error}`
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.148",
|
||||
"version": "0.9.149-alpha.3",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -20,13 +20,15 @@
|
|||
"multi:enable": "node scripts/multiTenancy.js enable",
|
||||
"multi:disable": "node scripts/multiTenancy.js disable",
|
||||
"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",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.148",
|
||||
"@budibase/string-templates": "^0.9.148",
|
||||
"@budibase/auth": "^0.9.149-alpha.3",
|
||||
"@budibase/string-templates": "^0.9.149-alpha.3",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@techpass/passport-openidconnect": "^0.3.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) {
|
||||
return next()
|
||||
}
|
||||
if (!ctx.isAuthenticated || !ctx.user.budibaseAccess) {
|
||||
if ((!ctx.isAuthenticated || !ctx.user.budibaseAccess) && !ctx.internal) {
|
||||
ctx.throw(403, "Unauthorized - no public worker access")
|
||||
}
|
||||
return next()
|
||||
|
|
Loading…
Reference in New Issue