Merge branch 'develop' of github.com:Budibase/budibase into frontend-core
This commit is contained in:
commit
c7cd6b923d
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -8,7 +8,6 @@ exports.Cookies = {
|
||||||
Auth: "budibase:auth",
|
Auth: "budibase:auth",
|
||||||
Init: "budibase:init",
|
Init: "budibase:init",
|
||||||
OIDC_CONFIG: "budibase:oidc:config",
|
OIDC_CONFIG: "budibase:oidc:config",
|
||||||
RETURN_URL: "budibase:returnurl",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.Headers = {
|
exports.Headers = {
|
||||||
|
|
|
@ -96,12 +96,7 @@ exports.getCookie = (ctx, name) => {
|
||||||
* @param {string|object} value The value of cookie which will be set.
|
* @param {string|object} value The value of cookie which will be set.
|
||||||
* @param {object} opts options like whether to sign.
|
* @param {object} opts options like whether to sign.
|
||||||
*/
|
*/
|
||||||
exports.setCookie = (
|
exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => {
|
||||||
ctx,
|
|
||||||
value,
|
|
||||||
name = "builder",
|
|
||||||
opts = { sign: true, requestDomain: false }
|
|
||||||
) => {
|
|
||||||
if (value && opts && opts.sign) {
|
if (value && opts && opts.sign) {
|
||||||
value = jwt.sign(value, options.secretOrKey)
|
value = jwt.sign(value, options.secretOrKey)
|
||||||
}
|
}
|
||||||
|
@ -113,7 +108,7 @@ exports.setCookie = (
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment.COOKIE_DOMAIN && !opts.requestDomain) {
|
if (environment.COOKIE_DOMAIN) {
|
||||||
config.domain = environment.COOKIE_DOMAIN
|
config.domain = environment.COOKIE_DOMAIN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3410,9 +3410,9 @@ node-fetch@2.6.0:
|
||||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||||
|
|
||||||
node-fetch@^2.6.1:
|
node-fetch@^2.6.1:
|
||||||
version "2.6.6"
|
version "2.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||||
integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==
|
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
whatwg-url "^5.0.0"
|
||||||
|
|
||||||
|
|
|
@ -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": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,11 +65,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.46-alpha.4",
|
"@budibase/bbui": "^1.0.46-alpha.5",
|
||||||
"@budibase/client": "^1.0.46-alpha.4",
|
"@budibase/client": "^1.0.46-alpha.5",
|
||||||
"@budibase/frontend-core": "^1.0.46-alpha.4",
|
"@budibase/frontend-core": "^1.0.46-alpha.5",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^1.0.46-alpha.4",
|
"@budibase/string-templates": "^1.0.46-alpha.5",
|
||||||
"@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,6 +1,5 @@
|
||||||
import { getFrontendStore } from "./store/frontend"
|
import { getFrontendStore } from "./store/frontend"
|
||||||
import { getAutomationStore } from "./store/automation"
|
import { getAutomationStore } from "./store/automation"
|
||||||
import { getHostingStore } from "./store/hosting"
|
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
import { derived, writable } from "svelte/store"
|
import { derived, writable } from "svelte/store"
|
||||||
import { FrontendTypes, LAYOUT_NAMES } from "../constants"
|
import { FrontendTypes, LAYOUT_NAMES } from "../constants"
|
||||||
|
@ -9,7 +8,6 @@ import { findComponent } from "./componentUtils"
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
export const themeStore = getThemeStore()
|
export const themeStore = getThemeStore()
|
||||||
export const hostingStore = getHostingStore()
|
|
||||||
|
|
||||||
export const currentAsset = derived(store, $store => {
|
export const currentAsset = derived(store, $store => {
|
||||||
const type = $store.currentFrontEndType
|
const type = $store.currentFrontEndType
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { get, writable } from "svelte/store"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import {
|
import {
|
||||||
allScreens,
|
allScreens,
|
||||||
hostingStore,
|
|
||||||
currentAsset,
|
currentAsset,
|
||||||
mainLayout,
|
mainLayout,
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
|
@ -99,7 +98,6 @@ export const getFrontendStore = () => {
|
||||||
|
|
||||||
// Initialise backend stores
|
// Initialise backend stores
|
||||||
database.set(application.instance)
|
database.set(application.instance)
|
||||||
await hostingStore.actions.fetch()
|
|
||||||
await datasources.init()
|
await datasources.init()
|
||||||
await integrations.init()
|
await integrations.init()
|
||||||
await queries.init()
|
await queries.init()
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { writable } from "svelte/store"
|
|
||||||
import { API } from "api"
|
|
||||||
import { notifications } from "@budibase/bbui"
|
|
||||||
|
|
||||||
const INITIAL_HOSTING_UI_STATE = {
|
|
||||||
appUrl: "",
|
|
||||||
deployedApps: {},
|
|
||||||
deployedAppNames: [],
|
|
||||||
deployedAppUrls: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getHostingStore = () => {
|
|
||||||
const store = writable({ ...INITIAL_HOSTING_UI_STATE })
|
|
||||||
store.actions = {
|
|
||||||
fetch: async () => {
|
|
||||||
try {
|
|
||||||
const urls = await API.getHostingURLs()
|
|
||||||
store.update(state => {
|
|
||||||
state.appUrl = urls.app
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
store.update(state => {
|
|
||||||
state.appUrl = ""
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
notifications.error("Error fetching hosting URLs")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchDeployedApps: async () => {
|
|
||||||
try {
|
|
||||||
const deployments = await API.getDeployedApps()
|
|
||||||
store.update(state => {
|
|
||||||
state.deployedApps = deployments
|
|
||||||
state.deployedAppNames = Object.values(deployments).map(
|
|
||||||
app => app.name
|
|
||||||
)
|
|
||||||
state.deployedAppUrls = Object.values(deployments).map(app => app.url)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
store.update(state => {
|
|
||||||
state.deployedApps = {}
|
|
||||||
state.deployedAppNames = []
|
|
||||||
state.deployedAppUrls = []
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
notifications.error("Failed detching deployed apps")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return store
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte"
|
import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte"
|
||||||
import { store, hostingStore } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
|
||||||
const DeploymentStatus = {
|
const DeploymentStatus = {
|
||||||
SUCCESS: "SUCCESS",
|
SUCCESS: "SUCCESS",
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
let poll
|
let poll
|
||||||
let deployments = []
|
let deployments = []
|
||||||
let urlComponent = $store.url || `/${appId}`
|
let urlComponent = $store.url || `/${appId}`
|
||||||
let deploymentUrl = `${$hostingStore.appUrl}${urlComponent}`
|
let deploymentUrl = `${urlComponent}`
|
||||||
|
|
||||||
const formatDate = (date, format) =>
|
const formatDate = (date, format) =>
|
||||||
Intl.DateTimeFormat("en-GB", DATE_OPTIONS[format]).format(date)
|
Intl.DateTimeFormat("en-GB", DATE_OPTIONS[format]).format(date)
|
||||||
|
|
|
@ -1,100 +1,46 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable, get as svelteGet } from "svelte/store"
|
import { writable, get as svelteGet } from "svelte/store"
|
||||||
|
|
||||||
import { notifications, Input, ModalContent, Dropzone } from "@budibase/bbui"
|
import { notifications, Input, ModalContent, Dropzone } from "@budibase/bbui"
|
||||||
import { store, automationStore, hostingStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { admin, auth } from "stores/portal"
|
|
||||||
import { string, mixed, object } from "yup"
|
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
import { apps, admin, auth } from "stores/portal"
|
||||||
import analytics, { Events } from "analytics"
|
import analytics, { Events } from "analytics"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { capitalise } from "helpers"
|
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { APP_NAME_REGEX } from "constants"
|
import { createValidationStore } from "helpers/validation/yup"
|
||||||
|
import * as appValidation from "helpers/validation/yup/app"
|
||||||
|
|
||||||
export let template
|
export let template
|
||||||
export let inline
|
|
||||||
|
|
||||||
const values = writable({ name: null })
|
const values = writable({ name: "", url: null })
|
||||||
const errors = writable({})
|
const validation = createValidationStore()
|
||||||
const touched = writable({})
|
$: validation.check($values)
|
||||||
const validator = {
|
|
||||||
name: string()
|
|
||||||
.trim()
|
|
||||||
.required("Your application must have a name")
|
|
||||||
.matches(
|
|
||||||
APP_NAME_REGEX,
|
|
||||||
"App name must be letters, numbers and spaces only"
|
|
||||||
),
|
|
||||||
file: template?.fromFile
|
|
||||||
? mixed().required("Please choose a file to import")
|
|
||||||
: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
let submitting = false
|
|
||||||
let valid = false
|
|
||||||
let initialTemplateInfo = template?.fromFile || template?.key
|
|
||||||
|
|
||||||
$: checkValidity($values, validator)
|
|
||||||
$: showTemplateSelection = !template && !initialTemplateInfo
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await hostingStore.actions.fetchDeployedApps()
|
await setupValidation()
|
||||||
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
|
||||||
validator.name = string()
|
|
||||||
.trim()
|
|
||||||
.required("Your application must have a name")
|
|
||||||
.matches(APP_NAME_REGEX, "App name must be letters and numbers only")
|
|
||||||
.test(
|
|
||||||
"non-existing-app-name",
|
|
||||||
"Another app with the same name already exists",
|
|
||||||
value => {
|
|
||||||
return !existingAppNames.some(
|
|
||||||
appName => appName?.toLowerCase() === value.toLowerCase()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkValidity = async (values, validator) => {
|
const setupValidation = async () => {
|
||||||
const obj = object().shape(validator)
|
const applications = svelteGet(apps)
|
||||||
Object.keys(validator).forEach(key => ($errors[key] = null))
|
appValidation.name(validation, { apps: applications })
|
||||||
if (template?.fromFile && values.file == null) {
|
appValidation.url(validation, { apps: applications })
|
||||||
valid = false
|
appValidation.file(validation, { template })
|
||||||
return
|
// init validation
|
||||||
}
|
validation.check($values)
|
||||||
|
|
||||||
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 createNewApp() {
|
async function createNewApp() {
|
||||||
const templateToUse = Object.keys(template).length === 0 ? null : template
|
|
||||||
submitting = true
|
|
||||||
|
|
||||||
// Check a template exists if we are important
|
|
||||||
if (templateToUse?.fromFile && !$values.file) {
|
|
||||||
$errors.file = "Please choose a file to import"
|
|
||||||
valid = false
|
|
||||||
submitting = false
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create form data to create app
|
// Create form data to create app
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
data.append("name", $values.name.trim())
|
data.append("name", $values.name.trim())
|
||||||
data.append("useTemplate", templateToUse != null)
|
if ($values.url) {
|
||||||
if (templateToUse) {
|
data.append("url", $values.url.trim())
|
||||||
data.append("templateName", templateToUse.name)
|
}
|
||||||
data.append("templateKey", templateToUse.key)
|
data.append("useTemplate", template != null)
|
||||||
|
if (template) {
|
||||||
|
data.append("templateName", template.name)
|
||||||
|
data.append("templateKey", template.key)
|
||||||
data.append("templateFile", $values.file)
|
data.append("templateFile", $values.file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +49,7 @@
|
||||||
analytics.captureEvent(Events.APP.CREATED, {
|
analytics.captureEvent(Events.APP.CREATED, {
|
||||||
name: $values.name,
|
name: $values.name,
|
||||||
appId: createdApp.instance._id,
|
appId: createdApp.instance._id,
|
||||||
templateToUse,
|
templateToUse: template,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Select Correct Application/DB in prep for creating user
|
// Select Correct Application/DB in prep for creating user
|
||||||
|
@ -118,49 +64,53 @@
|
||||||
await auth.setInitInfo({})
|
await auth.setInitInfo({})
|
||||||
$goto(`/builder/app/${createdApp.instance._id}`)
|
$goto(`/builder/app/${createdApp.instance._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
notifications.error("Error creating app")
|
notifications.error("Error creating app")
|
||||||
submitting = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onCancel() {
|
// auto add slash to url
|
||||||
template = null
|
$: {
|
||||||
try {
|
if ($values.url && !$values.url.startsWith("/")) {
|
||||||
await auth.setInitInfo({})
|
$values.url = `/${$values.url}`
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error setting init info")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={"Name your app"}
|
title={"Create your app"}
|
||||||
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
||||||
onConfirm={createNewApp}
|
onConfirm={createNewApp}
|
||||||
onCancel={inline ? onCancel : null}
|
disabled={!$validation.valid}
|
||||||
cancelText={inline ? "Back" : undefined}
|
|
||||||
showCloseIcon={!inline}
|
|
||||||
disabled={!valid}
|
|
||||||
>
|
>
|
||||||
{#if template?.fromFile}
|
{#if template?.fromFile}
|
||||||
<Dropzone
|
<Dropzone
|
||||||
error={$touched.file && $errors.file}
|
error={$validation.touched.file && $validation.errors.file}
|
||||||
gallery={false}
|
gallery={false}
|
||||||
label="File to import"
|
label="File to import"
|
||||||
value={[$values.file]}
|
value={[$values.file]}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
$values.file = e.detail?.[0]
|
$values.file = e.detail?.[0]
|
||||||
$touched.file = true
|
$validation.touched.file = true
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<Input
|
<Input
|
||||||
bind:value={$values.name}
|
bind:value={$values.name}
|
||||||
error={$touched.name && $errors.name}
|
error={$validation.touched.name && $validation.errors.name}
|
||||||
on:blur={() => ($touched.name = true)}
|
on:blur={() => ($validation.touched.name = true)}
|
||||||
label="Name"
|
label="Name"
|
||||||
placeholder={$auth.user.firstName
|
placeholder={$auth.user.firstName
|
||||||
? `${$auth.user.firstName}'s app`
|
? `${$auth.user.firstName}s app`
|
||||||
: "My app"}
|
: "My app"}
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
bind:value={$values.url}
|
||||||
|
error={$validation.touched.url && $validation.errors.url}
|
||||||
|
on:blur={() => ($validation.touched.url = true)}
|
||||||
|
label="URL"
|
||||||
|
placeholder={$values.name
|
||||||
|
? "/" + encodeURIComponent($values.name).toLowerCase()
|
||||||
|
: "/"}
|
||||||
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -1,119 +1,75 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable, get as svelteGet } from "svelte/store"
|
import { writable, get as svelteGet } from "svelte/store"
|
||||||
import {
|
import { notifications, Input, ModalContent, Body } from "@budibase/bbui"
|
||||||
notifications,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalContent,
|
|
||||||
Body,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { hostingStore } from "builderStore"
|
|
||||||
import { apps } from "stores/portal"
|
import { apps } from "stores/portal"
|
||||||
import { string, object } from "yup"
|
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { capitalise } from "helpers"
|
import { createValidationStore } from "helpers/validation/yup"
|
||||||
import { APP_NAME_REGEX } from "constants"
|
import * as appValidation from "helpers/validation/yup/app"
|
||||||
|
|
||||||
const values = writable({ name: null })
|
|
||||||
const errors = writable({})
|
|
||||||
const touched = writable({})
|
|
||||||
const validator = {
|
|
||||||
name: string()
|
|
||||||
.trim()
|
|
||||||
.required("Your application must have a name")
|
|
||||||
.matches(
|
|
||||||
APP_NAME_REGEX,
|
|
||||||
"App name must be letters, numbers and spaces only"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
|
|
||||||
let modal
|
const values = writable({ name: "", url: null })
|
||||||
let valid = false
|
const validation = createValidationStore()
|
||||||
let dirty = false
|
$: validation.check($values)
|
||||||
$: checkValidity($values, validator)
|
|
||||||
$: {
|
|
||||||
// prevent validation by setting name to undefined without an app
|
|
||||||
if (app) {
|
|
||||||
$values.name = app?.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await hostingStore.actions.fetchDeployedApps()
|
$values.name = app.name
|
||||||
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
$values.url = app.url
|
||||||
validator.name = string()
|
setupValidation()
|
||||||
.trim()
|
|
||||||
.required("Your application must have a name")
|
|
||||||
.matches(
|
|
||||||
APP_NAME_REGEX,
|
|
||||||
"App name must be letters, numbers and spaces only"
|
|
||||||
)
|
|
||||||
.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 setupValidation = async () => {
|
||||||
const obj = object().shape(validator)
|
const applications = svelteGet(apps)
|
||||||
Object.keys(validator).forEach(key => ($errors[key] = null))
|
appValidation.name(validation, { apps: applications, currentApp: app })
|
||||||
try {
|
appValidation.url(validation, { apps: applications, currentApp: app })
|
||||||
await obj.validate(values, { abortEarly: false })
|
// init validation
|
||||||
} catch (validationErrors) {
|
validation.check($values)
|
||||||
validationErrors.inner?.forEach(error => {
|
|
||||||
$errors[error.path] = capitalise(error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
valid = await obj.isValid(values)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateApp() {
|
async function updateApp() {
|
||||||
try {
|
try {
|
||||||
// Update App
|
// Update App
|
||||||
await apps.update(app.instance._id, { name: $values.name.trim() })
|
const body = {
|
||||||
hide()
|
name: $values.name.trim(),
|
||||||
|
}
|
||||||
|
if ($values.url) {
|
||||||
|
body.url = $values.url.trim()
|
||||||
|
}
|
||||||
|
await apps.update(app.instance._id, body)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
notifications.error("Error updating app")
|
notifications.error("Error updating app")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const show = () => {
|
// auto add slash to url
|
||||||
modal.show()
|
$: {
|
||||||
}
|
if ($values.url && !$values.url.startsWith("/")) {
|
||||||
export const hide = () => {
|
$values.url = `/${$values.url}`
|
||||||
modal.hide()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const onCancel = () => {
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onShow = () => {
|
|
||||||
dirty = false
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal} on:hide={onCancel} on:show={onShow}>
|
<ModalContent
|
||||||
<ModalContent
|
title={"Edit app"}
|
||||||
title={"Edit app"}
|
confirmText={"Save"}
|
||||||
confirmText={"Save"}
|
onConfirm={updateApp}
|
||||||
onConfirm={updateApp}
|
disabled={!$validation.valid}
|
||||||
disabled={!(valid && dirty)}
|
>
|
||||||
>
|
<Body size="S">Update the name of your app.</Body>
|
||||||
<Body size="S">Update the name of your app.</Body>
|
<Input
|
||||||
<Input
|
bind:value={$values.name}
|
||||||
bind:value={$values.name}
|
error={$validation.touched.name && $validation.errors.name}
|
||||||
error={$touched.name && $errors.name}
|
on:blur={() => ($validation.touched.name = true)}
|
||||||
on:blur={() => ($touched.name = true)}
|
label="Name"
|
||||||
on:change={() => (dirty = true)}
|
/>
|
||||||
label="Name"
|
<Input
|
||||||
/>
|
bind:value={$values.url}
|
||||||
</ModalContent>
|
error={$validation.touched.url && $validation.errors.url}
|
||||||
</Modal>
|
on:blur={() => ($validation.touched.url = true)}
|
||||||
|
label="URL"
|
||||||
|
placeholder={$values.name
|
||||||
|
? "/" + encodeURIComponent($values.name).toLowerCase()
|
||||||
|
: "/"}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
|
|
@ -36,4 +36,7 @@ export const LAYOUT_NAMES = {
|
||||||
|
|
||||||
export const BUDIBASE_INTERNAL_DB = "bb_internal"
|
export const BUDIBASE_INTERNAL_DB = "bb_internal"
|
||||||
|
|
||||||
|
// one or more word characters and whitespace
|
||||||
export const APP_NAME_REGEX = /^[\w\s]+$/
|
export const APP_NAME_REGEX = /^[\w\s]+$/
|
||||||
|
// zero or more non-whitespace characters
|
||||||
|
export const APP_URL_REGEX = /^\S*$/
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { writable, derived } from "svelte/store"
|
import { writable, derived } from "svelte/store"
|
||||||
|
|
||||||
|
// DEPRECATED - Use the yup based validators for future validation
|
||||||
|
|
||||||
export function createValidationStore(initialValue, ...validators) {
|
export function createValidationStore(initialValue, ...validators) {
|
||||||
let touched = false
|
let touched = false
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// TODO: Convert to yup based validators
|
||||||
|
|
||||||
export function emailValidator(value) {
|
export function emailValidator(value) {
|
||||||
return (
|
return (
|
||||||
(value &&
|
(value &&
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { string, mixed } from "yup"
|
||||||
|
import { APP_NAME_REGEX, APP_URL_REGEX } from "constants"
|
||||||
|
|
||||||
|
export const name = (validation, { apps, currentApp } = { apps: [] }) => {
|
||||||
|
validation.addValidator(
|
||||||
|
"name",
|
||||||
|
string()
|
||||||
|
.trim()
|
||||||
|
.required("Your application must have a name")
|
||||||
|
.matches(
|
||||||
|
APP_NAME_REGEX,
|
||||||
|
"App name must be letters, numbers and spaces only"
|
||||||
|
)
|
||||||
|
.test(
|
||||||
|
"non-existing-app-name",
|
||||||
|
"Another app with the same name already exists",
|
||||||
|
value => {
|
||||||
|
if (!value) {
|
||||||
|
// exit early, above validator will fail
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (currentApp) {
|
||||||
|
// filter out the current app if present
|
||||||
|
apps = apps.filter(app => app.appId !== currentApp.appId)
|
||||||
|
}
|
||||||
|
return !apps
|
||||||
|
.map(app => app.name)
|
||||||
|
.some(appName => appName.toLowerCase() === value.toLowerCase())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const url = (validation, { apps, currentApp } = { apps: [] }) => {
|
||||||
|
validation.addValidator(
|
||||||
|
"url",
|
||||||
|
string()
|
||||||
|
.nullable()
|
||||||
|
.matches(APP_URL_REGEX, "App URL must not contain spaces")
|
||||||
|
.test(
|
||||||
|
"non-existing-app-url",
|
||||||
|
"Another app with the same URL already exists",
|
||||||
|
value => {
|
||||||
|
// url is nullable
|
||||||
|
if (!value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (currentApp) {
|
||||||
|
// filter out the current app if present
|
||||||
|
apps = apps.filter(app => app.appId !== currentApp.appId)
|
||||||
|
}
|
||||||
|
return !apps
|
||||||
|
.map(app => app.url)
|
||||||
|
.some(appUrl => appUrl?.toLowerCase() === value.toLowerCase())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.test("valid-url", "Not a valid URL", value => {
|
||||||
|
// url is nullable
|
||||||
|
if (!value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// make it clear that this is a url path and cannot be a full url
|
||||||
|
return (
|
||||||
|
value.startsWith("/") &&
|
||||||
|
!value.includes("http") &&
|
||||||
|
!value.includes("www") &&
|
||||||
|
!value.includes(".") &&
|
||||||
|
value.length > 1 // just '/' is not valid
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const file = (validation, { template } = {}) => {
|
||||||
|
const templateToUse =
|
||||||
|
template && Object.keys(template).length === 0 ? null : template
|
||||||
|
validation.addValidator(
|
||||||
|
"file",
|
||||||
|
templateToUse?.fromFile
|
||||||
|
? mixed().required("Please choose a file to import")
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
import { object } from "yup"
|
||||||
|
import { writable, get } from "svelte/store"
|
||||||
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export const createValidationStore = () => {
|
||||||
|
const DEFAULT = {
|
||||||
|
errors: {},
|
||||||
|
touched: {},
|
||||||
|
valid: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const validator = {}
|
||||||
|
const validation = writable(DEFAULT)
|
||||||
|
|
||||||
|
const addValidator = (propertyName, propertyValidator) => {
|
||||||
|
if (!propertyValidator || !propertyName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
validator[propertyName] = propertyValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
const check = async values => {
|
||||||
|
const obj = object().shape(validator)
|
||||||
|
// clear the previous errors
|
||||||
|
const properties = Object.keys(validator)
|
||||||
|
properties.forEach(property => (get(validation).errors[property] = null))
|
||||||
|
|
||||||
|
let validationError = false
|
||||||
|
try {
|
||||||
|
await obj.validate(values, { abortEarly: false })
|
||||||
|
} catch (error) {
|
||||||
|
if (!error.inner) {
|
||||||
|
notifications.error("Unexpected validation error", error)
|
||||||
|
validationError = true
|
||||||
|
} else {
|
||||||
|
error.inner.forEach(err => {
|
||||||
|
validation.update(store => {
|
||||||
|
store.errors[err.path] = capitalise(err.message)
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let valid
|
||||||
|
if (properties.length && !validationError) {
|
||||||
|
valid = await obj.isValid(values)
|
||||||
|
} else {
|
||||||
|
// don't say valid until validators have been loaded
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
validation.update(store => {
|
||||||
|
store.valid = valid
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: validation.subscribe,
|
||||||
|
set: validation.set,
|
||||||
|
check,
|
||||||
|
addValidator,
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { apps, organisation, auth, admin } from "stores/portal"
|
import { apps, organisation, auth } from "stores/portal"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
|
@ -39,7 +39,6 @@
|
||||||
const publishedAppsOnly = app => app.status === AppStatus.DEPLOYED
|
const publishedAppsOnly = app => app.status === AppStatus.DEPLOYED
|
||||||
|
|
||||||
$: publishedApps = $apps.filter(publishedAppsOnly)
|
$: publishedApps = $apps.filter(publishedAppsOnly)
|
||||||
$: isCloud = $admin.cloud
|
|
||||||
$: userApps = $auth.user?.builder?.global
|
$: userApps = $auth.user?.builder?.global
|
||||||
? publishedApps
|
? publishedApps
|
||||||
: publishedApps.filter(app =>
|
: publishedApps.filter(app =>
|
||||||
|
@ -47,7 +46,11 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
function getUrl(app) {
|
function getUrl(app) {
|
||||||
return !isCloud ? `/app/${encodeURIComponent(app.name)}` : `/${app.prodId}`
|
if (app.url) {
|
||||||
|
return `/app${app.url}`
|
||||||
|
} else {
|
||||||
|
return `/${app.prodId}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
$: filteredApps = enrichedApps.filter(app =>
|
$: filteredApps = enrichedApps.filter(app =>
|
||||||
app?.name?.toLowerCase().includes(searchTerm.toLowerCase())
|
app?.name?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
)
|
)
|
||||||
$: isCloud = $admin.cloud
|
|
||||||
|
|
||||||
const enrichApps = (apps, user, sortBy) => {
|
const enrichApps = (apps, user, sortBy) => {
|
||||||
const enrichedApps = apps.map(app => ({
|
const enrichedApps = apps.map(app => ({
|
||||||
|
@ -80,7 +79,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const initiateAppCreation = () => {
|
const initiateAppCreation = () => {
|
||||||
template = {}
|
template = null
|
||||||
creationModal.show()
|
creationModal.show()
|
||||||
creatingApp = true
|
creatingApp = true
|
||||||
}
|
}
|
||||||
|
@ -148,12 +147,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewApp = app => {
|
const viewApp = app => {
|
||||||
if (!isCloud && app.deployed) {
|
if (app.url) {
|
||||||
// special case to use the short form name if self hosted
|
window.open(`/app${app.url}`)
|
||||||
window.open(`/app/${encodeURIComponent(app.name)}`)
|
|
||||||
} else {
|
} else {
|
||||||
const id = app.deployed ? app.prodId : app.devId
|
window.open(`/${app.prodId}`)
|
||||||
window.open(`/${id}`, "_blank")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,6 +417,11 @@
|
||||||
>
|
>
|
||||||
<CreateAppModal {template} />
|
<CreateAppModal {template} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={updatingModal} padding={false} width="600px">
|
||||||
|
<UpdateAppModal app={selectedApp} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={deletionModal}
|
bind:this={deletionModal}
|
||||||
title="Confirm deletion"
|
title="Confirm deletion"
|
||||||
|
@ -446,7 +448,6 @@
|
||||||
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<UpdateAppModal app={selectedApp} bind:this={updatingModal} />
|
|
||||||
<ChooseIconModal app={selectedApp} bind:this={iconModal} />
|
<ChooseIconModal app={selectedApp} bind:this={iconModal} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"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": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"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,10 +19,10 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.46-alpha.4",
|
"@budibase/bbui": "^1.0.46-alpha.5",
|
||||||
"@budibase/frontend-core": "^1.0.46-alpha.4",
|
"@budibase/frontend-core": "^1.0.46-alpha.5",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^1.0.46-alpha.4",
|
"@budibase/string-templates": "^1.0.46-alpha.5",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"rollup-plugin-polyfill-node": "^0.8.0",
|
"rollup-plugin-polyfill-node": "^0.8.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"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",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"watch": ["src", "../auth"],
|
"watch": ["src", "../backend-core"],
|
||||||
"ext": "js,ts,json",
|
"ext": "js,ts,json",
|
||||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
|
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
|
||||||
"exec": "ts-node src/index.ts"
|
"exec": "ts-node src/index.ts"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -70,9 +70,9 @@
|
||||||
"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": "^1.0.46-alpha.4",
|
"@budibase/backend-core": "^1.0.46-alpha.5",
|
||||||
"@budibase/client": "^1.0.46-alpha.4",
|
"@budibase/client": "^1.0.46-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.46-alpha.4",
|
"@budibase/string-templates": "^1.0.46-alpha.5",
|
||||||
"@bull-board/api": "^3.7.0",
|
"@bull-board/api": "^3.7.0",
|
||||||
"@bull-board/koa": "^3.7.0",
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
"mongodb": "3.6.3",
|
"mongodb": "3.6.3",
|
||||||
"mssql": "6.2.3",
|
"mssql": "6.2.3",
|
||||||
"mysql2": "^2.3.1",
|
"mysql2": "^2.3.1",
|
||||||
"node-fetch": "2.6.0",
|
"node-fetch": "2.6.7",
|
||||||
"open": "^8.4.0",
|
"open": "^8.4.0",
|
||||||
"pg": "8.5.1",
|
"pg": "8.5.1",
|
||||||
"pino-pretty": "4.0.0",
|
"pino-pretty": "4.0.0",
|
||||||
|
|
|
@ -33,10 +33,7 @@ const {
|
||||||
Replication,
|
Replication,
|
||||||
} = require("@budibase/backend-core/db")
|
} = require("@budibase/backend-core/db")
|
||||||
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
||||||
const {
|
const { removeAppFromUserRoles } = require("../../utilities/workerRequests")
|
||||||
getDeployedApps,
|
|
||||||
removeAppFromUserRoles,
|
|
||||||
} = require("../../utilities/workerRequests")
|
|
||||||
const { clientLibraryPath, stringToReadStream } = require("../../utilities")
|
const { clientLibraryPath, stringToReadStream } = require("../../utilities")
|
||||||
const { getAllLocks } = require("../../utilities/redis")
|
const { getAllLocks } = require("../../utilities/redis")
|
||||||
const {
|
const {
|
||||||
|
@ -78,31 +75,43 @@ function getUserRoleId(ctx) {
|
||||||
: ctx.user.role._id
|
: ctx.user.role._id
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAppUrlIfNotInUse(ctx) {
|
async function getAppUrl(ctx) {
|
||||||
|
// construct the url
|
||||||
let url
|
let url
|
||||||
if (ctx.request.body.url) {
|
if (ctx.request.body.url) {
|
||||||
|
// if the url is provided, use that
|
||||||
url = encodeURI(ctx.request.body.url)
|
url = encodeURI(ctx.request.body.url)
|
||||||
} else if (ctx.request.body.name) {
|
} else {
|
||||||
|
// otherwise use the name
|
||||||
url = encodeURI(`${ctx.request.body.name}`)
|
url = encodeURI(`${ctx.request.body.name}`)
|
||||||
}
|
}
|
||||||
if (url) {
|
url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
|
||||||
url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
|
|
||||||
}
|
|
||||||
if (!env.SELF_HOSTED) {
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
const deployedApps = await getDeployedApps()
|
|
||||||
if (
|
|
||||||
url &&
|
|
||||||
deployedApps[url] != null &&
|
|
||||||
ctx.params != null &&
|
|
||||||
deployedApps[url].appId !== ctx.params.appId
|
|
||||||
) {
|
|
||||||
ctx.throw(400, "App name/URL is already in use.")
|
|
||||||
}
|
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkAppUrl = (ctx, apps, url, currentAppId) => {
|
||||||
|
if (currentAppId) {
|
||||||
|
apps = apps.filter(app => app.appId !== currentAppId)
|
||||||
|
}
|
||||||
|
if (apps.some(app => app.url === url)) {
|
||||||
|
ctx.throw(400, "App URL is already in use.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkAppName = (ctx, apps, name, currentAppId) => {
|
||||||
|
// TODO: Replace with Joi
|
||||||
|
if (!name) {
|
||||||
|
ctx.throw(400, "Name is required")
|
||||||
|
}
|
||||||
|
if (currentAppId) {
|
||||||
|
apps = apps.filter(app => app.appId !== currentAppId)
|
||||||
|
}
|
||||||
|
if (apps.some(app => app.name === name)) {
|
||||||
|
ctx.throw(400, "App name is already in use.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createInstance(template) {
|
async function createInstance(template) {
|
||||||
const tenantId = isMultiTenant() ? getTenantId() : null
|
const tenantId = isMultiTenant() ? getTenantId() : null
|
||||||
const baseAppId = generateAppID(tenantId)
|
const baseAppId = generateAppID(tenantId)
|
||||||
|
@ -206,6 +215,12 @@ exports.fetchAppPackage = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = async ctx => {
|
exports.create = async ctx => {
|
||||||
|
const apps = await getAllApps(CouchDB, { dev: true })
|
||||||
|
const name = ctx.request.body.name
|
||||||
|
checkAppName(ctx, apps, name)
|
||||||
|
const url = await getAppUrl(ctx)
|
||||||
|
checkAppUrl(ctx, apps, url)
|
||||||
|
|
||||||
const { useTemplate, templateKey, templateString } = ctx.request.body
|
const { useTemplate, templateKey, templateString } = ctx.request.body
|
||||||
const instanceConfig = {
|
const instanceConfig = {
|
||||||
useTemplate,
|
useTemplate,
|
||||||
|
@ -218,7 +233,6 @@ exports.create = async ctx => {
|
||||||
const instance = await createInstance(instanceConfig)
|
const instance = await createInstance(instanceConfig)
|
||||||
const appId = instance._id
|
const appId = instance._id
|
||||||
|
|
||||||
const url = await getAppUrlIfNotInUse(ctx)
|
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
let _rev
|
let _rev
|
||||||
try {
|
try {
|
||||||
|
@ -235,7 +249,7 @@ exports.create = async ctx => {
|
||||||
type: "app",
|
type: "app",
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
componentLibraries: ["@budibase/standard-components"],
|
componentLibraries: ["@budibase/standard-components"],
|
||||||
name: ctx.request.body.name,
|
name: name,
|
||||||
url: url,
|
url: url,
|
||||||
template: ctx.request.body.template,
|
template: ctx.request.body.template,
|
||||||
instance: instance,
|
instance: instance,
|
||||||
|
@ -263,7 +277,15 @@ exports.create = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.update = async ctx => {
|
exports.update = async ctx => {
|
||||||
const data = await updateAppPackage(ctx, ctx.request.body, ctx.params.appId)
|
const apps = await getAllApps(CouchDB, { dev: true })
|
||||||
|
// validation
|
||||||
|
const name = ctx.request.body.name
|
||||||
|
checkAppName(ctx, apps, name, ctx.params.appId)
|
||||||
|
const url = await getAppUrl(ctx)
|
||||||
|
checkAppUrl(ctx, apps, url, ctx.params.appId)
|
||||||
|
|
||||||
|
const appPackageUpdates = { name, url }
|
||||||
|
const data = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = data
|
ctx.body = data
|
||||||
}
|
}
|
||||||
|
@ -285,7 +307,7 @@ exports.updateClient = async ctx => {
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
revertableVersion: currentVersion,
|
revertableVersion: currentVersion,
|
||||||
}
|
}
|
||||||
const data = await updateAppPackage(ctx, appPackageUpdates, ctx.params.appId)
|
const data = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = data
|
ctx.body = data
|
||||||
}
|
}
|
||||||
|
@ -308,7 +330,7 @@ exports.revertClient = async ctx => {
|
||||||
version: application.revertableVersion,
|
version: application.revertableVersion,
|
||||||
revertableVersion: null,
|
revertableVersion: null,
|
||||||
}
|
}
|
||||||
const data = await updateAppPackage(ctx, appPackageUpdates, ctx.params.appId)
|
const data = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = data
|
ctx.body = data
|
||||||
}
|
}
|
||||||
|
@ -381,12 +403,11 @@ exports.sync = async (ctx, next) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateAppPackage = async (ctx, appPackage, appId) => {
|
const updateAppPackage = async (appPackage, appId) => {
|
||||||
const url = await getAppUrlIfNotInUse(ctx)
|
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
const newAppPackage = { ...application, ...appPackage, url }
|
const newAppPackage = { ...application, ...appPackage }
|
||||||
if (appPackage._rev !== application._rev) {
|
if (appPackage._rev !== application._rev) {
|
||||||
newAppPackage._rev = application._rev
|
newAppPackage._rev = application._rev
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { getDeployedApps } = require("../../utilities/workerRequests")
|
|
||||||
const { getScopedConfig } = require("@budibase/backend-core/db")
|
|
||||||
const { Configs } = require("@budibase/backend-core/constants")
|
|
||||||
const { checkSlashesInUrl } = require("../../utilities")
|
|
||||||
|
|
||||||
exports.fetchUrls = async ctx => {
|
|
||||||
const appId = ctx.appId
|
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const settings = await getScopedConfig(db, { type: Configs.SETTINGS })
|
|
||||||
let appUrl = "http://localhost:10000/app"
|
|
||||||
if (settings && settings["platformUrl"]) {
|
|
||||||
appUrl = checkSlashesInUrl(`${settings["platformUrl"]}/app`)
|
|
||||||
}
|
|
||||||
ctx.body = {
|
|
||||||
app: appUrl,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getDeployedApps = async ctx => {
|
|
||||||
ctx.body = await getDeployedApps()
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ const { resolve, join } = require("../../../utilities/centralPath")
|
||||||
const uuid = require("uuid")
|
const uuid = require("uuid")
|
||||||
const { ObjectStoreBuckets } = require("../../../constants")
|
const { ObjectStoreBuckets } = require("../../../constants")
|
||||||
const { processString } = require("@budibase/string-templates")
|
const { processString } = require("@budibase/string-templates")
|
||||||
const { getDeployedApps } = require("../../../utilities/workerRequests")
|
const { getAllApps } = require("@budibase/backend-core/db")
|
||||||
const CouchDB = require("../../../db")
|
const CouchDB = require("../../../db")
|
||||||
const {
|
const {
|
||||||
loadHandlebarsFile,
|
loadHandlebarsFile,
|
||||||
|
@ -39,12 +39,18 @@ async function prepareUpload({ s3Key, bucket, metadata, file }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkForSelfHostedURL(ctx) {
|
async function getAppIdFromUrl(ctx) {
|
||||||
// the "appId" component of the URL may actually be a specific self hosted URL
|
// the "appId" component of the URL can be the id or the custom url
|
||||||
let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}`
|
let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}`
|
||||||
const apps = await getDeployedApps()
|
|
||||||
if (apps[possibleAppUrl] && apps[possibleAppUrl].appId) {
|
// search prod apps for a url that matches, exclude dev where id is always used
|
||||||
return apps[possibleAppUrl].appId
|
const apps = await getAllApps(CouchDB, { dev: false })
|
||||||
|
const app = apps.filter(
|
||||||
|
a => a.url && a.url.toLowerCase() === possibleAppUrl
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
if (app && app.appId) {
|
||||||
|
return app.appId
|
||||||
} else {
|
} else {
|
||||||
return ctx.params.appId
|
return ctx.params.appId
|
||||||
}
|
}
|
||||||
|
@ -77,10 +83,7 @@ exports.uploadFile = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.serveApp = async function (ctx) {
|
exports.serveApp = async function (ctx) {
|
||||||
let appId = ctx.params.appId
|
let appId = await getAppIdFromUrl(ctx)
|
||||||
if (env.SELF_HOSTED) {
|
|
||||||
appId = await checkForSelfHostedURL(ctx)
|
|
||||||
}
|
|
||||||
const App = require("./templates/BudibaseApp.svelte").default
|
const App = require("./templates/BudibaseApp.svelte").default
|
||||||
const db = new CouchDB(appId, { skip_setup: true })
|
const db = new CouchDB(appId, { skip_setup: true })
|
||||||
const appInfo = await db.get(DocumentTypes.APP_METADATA)
|
const appInfo = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
const Router = require("@koa/router")
|
|
||||||
const controller = require("../controllers/hosting")
|
|
||||||
const authorized = require("../../middleware/authorized")
|
|
||||||
const { BUILDER } = require("@budibase/backend-core/permissions")
|
|
||||||
|
|
||||||
const router = Router()
|
|
||||||
|
|
||||||
router
|
|
||||||
.get("/api/hosting/urls", authorized(BUILDER), controller.fetchUrls)
|
|
||||||
// this isn't risky, doesn't return anything about apps other than names and URLs
|
|
||||||
.get("/api/hosting/apps", controller.getDeployedApps)
|
|
||||||
|
|
||||||
module.exports = router
|
|
|
@ -20,7 +20,6 @@ const integrationRoutes = require("./integration")
|
||||||
const permissionRoutes = require("./permission")
|
const permissionRoutes = require("./permission")
|
||||||
const datasourceRoutes = require("./datasource")
|
const datasourceRoutes = require("./datasource")
|
||||||
const queryRoutes = require("./query")
|
const queryRoutes = require("./query")
|
||||||
const hostingRoutes = require("./hosting")
|
|
||||||
const backupRoutes = require("./backup")
|
const backupRoutes = require("./backup")
|
||||||
const metadataRoutes = require("./metadata")
|
const metadataRoutes = require("./metadata")
|
||||||
const devRoutes = require("./dev")
|
const devRoutes = require("./dev")
|
||||||
|
@ -46,7 +45,6 @@ exports.mainRoutes = [
|
||||||
permissionRoutes,
|
permissionRoutes,
|
||||||
datasourceRoutes,
|
datasourceRoutes,
|
||||||
queryRoutes,
|
queryRoutes,
|
||||||
hostingRoutes,
|
|
||||||
backupRoutes,
|
backupRoutes,
|
||||||
metadataRoutes,
|
metadataRoutes,
|
||||||
devRoutes,
|
devRoutes,
|
||||||
|
|
|
@ -53,8 +53,8 @@ describe("/applications", () => {
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
it("lists all applications", async () => {
|
it("lists all applications", async () => {
|
||||||
await config.createApp(request, "app1")
|
await config.createApp("app1")
|
||||||
await config.createApp(request, "app2")
|
await config.createApp("app2")
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/applications?status=${AppStatus.DEV}`)
|
.get(`/api/applications?status=${AppStatus.DEV}`)
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
// mock out node fetch for this
|
|
||||||
jest.mock("node-fetch")
|
|
||||||
|
|
||||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
|
||||||
const setup = require("./utilities")
|
|
||||||
|
|
||||||
describe("/hosting", () => {
|
|
||||||
let request = setup.getRequest()
|
|
||||||
let config = setup.getConfig()
|
|
||||||
let app
|
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
app = await config.init()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("fetchUrls", () => {
|
|
||||||
it("should be able to fetch current app URLs", async () => {
|
|
||||||
const res = await request
|
|
||||||
.get(`/api/hosting/urls`)
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body.app).toEqual(`http://localhost:10000/app`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
|
||||||
await checkBuilderEndpoint({
|
|
||||||
config,
|
|
||||||
method: "GET",
|
|
||||||
url: `/api/hosting/urls`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -43,8 +43,8 @@ const coreFields = {
|
||||||
enum: Object.values(BodyTypes),
|
enum: Object.values(BodyTypes),
|
||||||
},
|
},
|
||||||
pagination: {
|
pagination: {
|
||||||
type: DatasourceFieldTypes.OBJECT,
|
type: DatasourceFieldTypes.OBJECT
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module RestModule {
|
module RestModule {
|
||||||
|
@ -178,17 +178,12 @@ module RestModule {
|
||||||
headers,
|
headers,
|
||||||
},
|
},
|
||||||
pagination: {
|
pagination: {
|
||||||
cursor: nextCursor,
|
cursor: nextCursor
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUrl(
|
getUrl(path: string, queryString: string, pagination: PaginationConfig | null, paginationValues: PaginationValues | null): string {
|
||||||
path: string,
|
|
||||||
queryString: string,
|
|
||||||
pagination: PaginationConfig | null,
|
|
||||||
paginationValues: PaginationValues | null
|
|
||||||
): string {
|
|
||||||
// Add pagination params to query string if required
|
// Add pagination params to query string if required
|
||||||
if (pagination?.location === "query" && paginationValues) {
|
if (pagination?.location === "query" && paginationValues) {
|
||||||
const { pageParam, sizeParam } = pagination
|
const { pageParam, sizeParam } = pagination
|
||||||
|
@ -222,22 +217,14 @@ module RestModule {
|
||||||
return complete
|
return complete
|
||||||
}
|
}
|
||||||
|
|
||||||
addBody(
|
addBody(bodyType: string, body: string | any, input: any, pagination: PaginationConfig | null, paginationValues: PaginationValues | null) {
|
||||||
bodyType: string,
|
|
||||||
body: string | any,
|
|
||||||
input: any,
|
|
||||||
pagination: PaginationConfig | null,
|
|
||||||
paginationValues: PaginationValues | null
|
|
||||||
) {
|
|
||||||
if (!input.headers) {
|
if (!input.headers) {
|
||||||
input.headers = {}
|
input.headers = {}
|
||||||
}
|
}
|
||||||
if (bodyType === BodyTypes.NONE) {
|
if (bodyType === BodyTypes.NONE) {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
let error,
|
let error, object: any = {}, string = ""
|
||||||
object: any = {},
|
|
||||||
string = ""
|
|
||||||
try {
|
try {
|
||||||
if (body) {
|
if (body) {
|
||||||
string = typeof body !== "string" ? JSON.stringify(body) : body
|
string = typeof body !== "string" ? JSON.stringify(body) : body
|
||||||
|
@ -346,7 +333,7 @@ module RestModule {
|
||||||
requestBody,
|
requestBody,
|
||||||
authConfigId,
|
authConfigId,
|
||||||
pagination,
|
pagination,
|
||||||
paginationValues,
|
paginationValues
|
||||||
} = query
|
} = query
|
||||||
const authHeaders = this.getAuthHeaders(authConfigId)
|
const authHeaders = this.getAuthHeaders(authConfigId)
|
||||||
|
|
||||||
|
@ -365,13 +352,7 @@ module RestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
let input: any = { method, headers: this.headers }
|
let input: any = { method, headers: this.headers }
|
||||||
input = this.addBody(
|
input = this.addBody(bodyType, requestBody, input, pagination, paginationValues)
|
||||||
bodyType,
|
|
||||||
requestBody,
|
|
||||||
input,
|
|
||||||
pagination,
|
|
||||||
paginationValues
|
|
||||||
)
|
|
||||||
|
|
||||||
this.startTimeMs = performance.now()
|
this.startTimeMs = performance.now()
|
||||||
const url = this.getUrl(path, queryString, pagination, paginationValues)
|
const url = this.getUrl(path, queryString, pagination, paginationValues)
|
||||||
|
|
|
@ -38,7 +38,7 @@ module S3Module {
|
||||||
signatureVersion: {
|
signatureVersion: {
|
||||||
type: "string",
|
type: "string",
|
||||||
required: false,
|
required: false,
|
||||||
default: "v4",
|
default: "v4"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
|
|
|
@ -153,8 +153,15 @@ export function isIsoDateString(str: string) {
|
||||||
* @param column The column to check, to see if it is a valid relationship.
|
* @param column The column to check, to see if it is a valid relationship.
|
||||||
* @param tableIds The IDs of the tables which currently exist.
|
* @param tableIds The IDs of the tables which currently exist.
|
||||||
*/
|
*/
|
||||||
function shouldCopyRelationship(column: { type: string, tableId?: string }, tableIds: [string]) {
|
function shouldCopyRelationship(
|
||||||
return column.type === FieldTypes.LINK && column.tableId && tableIds.includes(column.tableId)
|
column: { type: string; tableId?: string },
|
||||||
|
tableIds: [string]
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
column.type === FieldTypes.LINK &&
|
||||||
|
column.tableId &&
|
||||||
|
tableIds.includes(column.tableId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -165,9 +172,15 @@ function shouldCopyRelationship(column: { type: string, tableId?: string }, tabl
|
||||||
* @param column The column to check for options or boolean type.
|
* @param column The column to check for options or boolean type.
|
||||||
* @param fetchedColumn The fetched column to check for the type in the external database.
|
* @param fetchedColumn The fetched column to check for the type in the external database.
|
||||||
*/
|
*/
|
||||||
function shouldCopySpecialColumn(column: { type: string }, fetchedColumn: { type: string } | undefined) {
|
function shouldCopySpecialColumn(
|
||||||
return column.type === FieldTypes.OPTIONS ||
|
column: { type: string },
|
||||||
((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) && column.type === FieldTypes.BOOLEAN)
|
fetchedColumn: { type: string } | undefined
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
column.type === FieldTypes.OPTIONS ||
|
||||||
|
((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) &&
|
||||||
|
column.type === FieldTypes.BOOLEAN)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -47,15 +47,6 @@ module.exports = async (ctx, next) => {
|
||||||
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
|
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
|
||||||
) {
|
) {
|
||||||
clearCookie(ctx, Cookies.CurrentApp)
|
clearCookie(ctx, Cookies.CurrentApp)
|
||||||
// have to set the return url on the server side as client side is not available
|
|
||||||
setCookie(ctx, ctx.url, Cookies.RETURN_URL, {
|
|
||||||
// don't sign so the browser can easily read
|
|
||||||
sign: false,
|
|
||||||
// use the request domain to match how ui handles the return url cookie.
|
|
||||||
// it's important we don't use the shared domain here as the builder
|
|
||||||
// can't delete from it without awareness of the domain.
|
|
||||||
requestDomain: true,
|
|
||||||
})
|
|
||||||
return ctx.redirect("/")
|
return ctx.redirect("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe("syncRows", () => {
|
||||||
await config.createTable()
|
await config.createTable()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
// app 2
|
// app 2
|
||||||
await config.createApp()
|
await config.createApp("second-app")
|
||||||
await config.createTable()
|
await config.createTable()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
|
|
|
@ -22,6 +22,7 @@ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
||||||
const { createASession } = require("@budibase/backend-core/sessions")
|
const { createASession } = require("@budibase/backend-core/sessions")
|
||||||
const { user: userCache } = require("@budibase/backend-core/cache")
|
const { user: userCache } = require("@budibase/backend-core/cache")
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
|
const newid = require("../../db/newid")
|
||||||
core.init(CouchDB)
|
core.init(CouchDB)
|
||||||
|
|
||||||
const GLOBAL_USER_ID = "us_uuid1"
|
const GLOBAL_USER_ID = "us_uuid1"
|
||||||
|
@ -98,7 +99,8 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(appName = "test_application") {
|
// use a new id as the name to avoid name collisions
|
||||||
|
async init(appName = newid()) {
|
||||||
await this.globalUser()
|
await this.globalUser()
|
||||||
return this.createApp(appName)
|
return this.createApp(appName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,11 @@ class QueryRunner {
|
||||||
return this.execute()
|
return this.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for undefined response
|
||||||
|
if (!rows) {
|
||||||
|
rows = []
|
||||||
|
}
|
||||||
|
|
||||||
// needs to an array for next step
|
// needs to an array for next step
|
||||||
if (!Array.isArray(rows)) {
|
if (!Array.isArray(rows)) {
|
||||||
rows = [rows]
|
rows = [rows]
|
||||||
|
|
|
@ -58,29 +58,6 @@ exports.sendSmtpEmail = async (to, from, subject, contents, automation) => {
|
||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getDeployedApps = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
checkSlashesInUrl(env.WORKER_URL + `/api/apps`),
|
|
||||||
request(null, {
|
|
||||||
method: "GET",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const json = await response.json()
|
|
||||||
const apps = {}
|
|
||||||
for (let [key, value] of Object.entries(json)) {
|
|
||||||
if (value.url) {
|
|
||||||
value.url = value.url.toLowerCase()
|
|
||||||
apps[key.toLowerCase()] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return apps
|
|
||||||
} catch (err) {
|
|
||||||
// error, cannot determine deployed apps, don't stop app creation - sort this later
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getGlobalSelf = async (ctx, appId = null) => {
|
exports.getGlobalSelf = async (ctx, appId = null) => {
|
||||||
const endpoint = `/api/global/users/self`
|
const endpoint = `/api/global/users/self`
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
|
|
@ -983,10 +983,10 @@
|
||||||
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@^1.0.27-alpha.13":
|
"@budibase/backend-core@^1.0.46-alpha.3":
|
||||||
version "1.0.27-alpha.13"
|
version "1.0.46"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.27-alpha.13.tgz#89f46e081eb7b342f483fd0eccd72c42b2b2fa6c"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.46.tgz#795e80038e11c054bb1aa313c16716a7035f3000"
|
||||||
integrity sha512-NiasBvZ5wTpvANG9AjuO34DHMTqWQWSpabLcgwBY0tNG4ekh+wvSCPjCcUvN/bBpOzrVMQ8C4hmS4pvv342BhQ==
|
integrity sha512-vXDjTOMlTaGx1Vm6ste7D7ZXwC+NgLzzu+8Ji7T0Pz2WXj+05vWpPha6L5CkNxRYTwUGoU1BAOvMYrChbGOftQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@techpass/passport-openidconnect" "^0.3.0"
|
"@techpass/passport-openidconnect" "^0.3.0"
|
||||||
aws-sdk "^2.901.0"
|
aws-sdk "^2.901.0"
|
||||||
|
@ -1056,10 +1056,10 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/bbui@^1.0.35":
|
"@budibase/bbui@^1.0.46":
|
||||||
version "1.0.35"
|
version "1.0.46"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.0.35.tgz#a51886886772257d31e2c6346dbec46fe0c9fd85"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.0.46.tgz#7306d4eda7f2c827577a4affa1fd314b38ba1198"
|
||||||
integrity sha512-8qeAzTujtO7uvhj+dMiyW4BTkQ7dC4xF1CNIwyuTnDwIeFDlXYgNb09VVRs3+nWcX2e2eC53EUs1RnLUoSlTsw==
|
integrity sha512-padm0qq2SBNIslXEQW+HIv32pkIHFzloR93FDzSXh0sO43Q+/d2gbAhjI9ZUSAVncx9JNc46dolL1CwrvHFElg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
|
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
|
||||||
"@spectrum-css/actionbutton" "^1.0.1"
|
"@spectrum-css/actionbutton" "^1.0.1"
|
||||||
|
@ -1106,14 +1106,14 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/client@^1.0.27-alpha.13":
|
"@budibase/client@^1.0.46-alpha.3":
|
||||||
version "1.0.35"
|
version "1.0.46"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.35.tgz#b832e7e7e35032fb35fe5492fbb721db1da15394"
|
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.46.tgz#e6ef8945b9d7046b6e6d6761628aa1d85387acca"
|
||||||
integrity sha512-maL3V29PQb9VjgnPZq44GSDZCuamAGp01bheUeJxEeskjQqZUdf8QC7Frf1mT+ZjgKJf3gU6qtFOxmWRbVzVbw==
|
integrity sha512-jI3z1G/EsfJNCQCvrqzsR4vR1zLoVefzCXCEASIPg9BPzdiAFSwuUJVLijLFIIKfuDVeveUll94fgu7XNY8U2w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/bbui" "^1.0.35"
|
"@budibase/bbui" "^1.0.46"
|
||||||
"@budibase/standard-components" "^0.9.139"
|
"@budibase/standard-components" "^0.9.139"
|
||||||
"@budibase/string-templates" "^1.0.35"
|
"@budibase/string-templates" "^1.0.46"
|
||||||
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"
|
||||||
|
@ -1163,10 +1163,10 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/string-templates@^1.0.27-alpha.13", "@budibase/string-templates@^1.0.35":
|
"@budibase/string-templates@^1.0.46", "@budibase/string-templates@^1.0.46-alpha.3":
|
||||||
version "1.0.35"
|
version "1.0.46"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.35.tgz#a888f1e9327bb36416336a91a95a43cb34e6a42d"
|
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.46.tgz#5beef1687b451e4512a465b4e143c8ab46234006"
|
||||||
integrity sha512-8HxSv0ru+cgSmphqtOm1pmBM8rc0TRC/6RQGzQefmFFQFfm/SBLAVLLWRmZxAOYTxt4mittGWeL4y05FqEuocg==
|
integrity sha512-t4ZAUkSz2XatjAN0faex5ovmD3mFz672lV/aBk7tfLFzZiKlWjngqdwpLLQNnsqeGvYo75JP2J06j86SX6O83w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/handlebars-helpers" "^0.11.7"
|
"@budibase/handlebars-helpers" "^0.11.7"
|
||||||
dayjs "^1.10.4"
|
dayjs "^1.10.4"
|
||||||
|
@ -9313,15 +9313,15 @@ node-fetch@2.4.1:
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.4.1.tgz#b2e38f1117b8acbedbe0524f041fb3177188255d"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.4.1.tgz#b2e38f1117b8acbedbe0524f041fb3177188255d"
|
||||||
integrity sha512-P9UbpFK87NyqBZzUuDBDz4f6Yiys8xm8j7ACDbi6usvFm6KItklQUKjeoqTrYS/S1k6I8oaOC2YLLDr/gg26Mw==
|
integrity sha512-P9UbpFK87NyqBZzUuDBDz4f6Yiys8xm8j7ACDbi6usvFm6KItklQUKjeoqTrYS/S1k6I8oaOC2YLLDr/gg26Mw==
|
||||||
|
|
||||||
node-fetch@2.6.0, node-fetch@^2.6.0:
|
node-fetch@2.6.0:
|
||||||
version "2.6.0"
|
version "2.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||||
|
|
||||||
node-fetch@^2.6.1:
|
node-fetch@2.6.7, node-fetch@^2.6.0, node-fetch@^2.6.1:
|
||||||
version "2.6.6"
|
version "2.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||||
integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==
|
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
whatwg-url "^5.0.0"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"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,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"watch": ["src", "../auth"]
|
"watch": ["src", "../backend-core"]
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.46-alpha.4",
|
"version": "1.0.46-alpha.5",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "^1.0.46-alpha.4",
|
"@budibase/backend-core": "^1.0.46-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.46-alpha.4",
|
"@budibase/string-templates": "^1.0.46-alpha.5",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sentry/node": "^6.0.0",
|
"@sentry/node": "^6.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
const {
|
|
||||||
getAllApps,
|
|
||||||
getDeployedAppID,
|
|
||||||
isProdAppID,
|
|
||||||
} = require("@budibase/backend-core/db")
|
|
||||||
const CouchDB = require("../../db")
|
|
||||||
|
|
||||||
const URL_REGEX_SLASH = /\/|\\/g
|
|
||||||
|
|
||||||
exports.getApps = async ctx => {
|
|
||||||
const apps = await getAllApps(CouchDB, { all: true })
|
|
||||||
const body = {}
|
|
||||||
for (let app of apps) {
|
|
||||||
let url = app.url || encodeURI(`${app.name}`)
|
|
||||||
url = `/${url.replace(URL_REGEX_SLASH, "")}`
|
|
||||||
const appId = app.appId,
|
|
||||||
isProd = isProdAppID(app.appId)
|
|
||||||
if (!body[url]) {
|
|
||||||
body[url] = {
|
|
||||||
appId: getDeployedAppID(appId),
|
|
||||||
name: app.name,
|
|
||||||
url,
|
|
||||||
deployed: isProd,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
body[url].deployed = isProd || body[url].deployed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.body = body
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
const Router = require("@koa/router")
|
|
||||||
const controller = require("../controllers/app")
|
|
||||||
|
|
||||||
const router = Router()
|
|
||||||
|
|
||||||
router.get("/api/apps", controller.getApps)
|
|
||||||
|
|
||||||
module.exports = router
|
|
|
@ -8,14 +8,12 @@ const roleRoutes = require("./global/roles")
|
||||||
const sessionRoutes = require("./global/sessions")
|
const sessionRoutes = require("./global/sessions")
|
||||||
const environmentRoutes = require("./system/environment")
|
const environmentRoutes = require("./system/environment")
|
||||||
const tenantsRoutes = require("./system/tenants")
|
const tenantsRoutes = require("./system/tenants")
|
||||||
const appRoutes = require("./app")
|
|
||||||
|
|
||||||
exports.routes = [
|
exports.routes = [
|
||||||
configRoutes,
|
configRoutes,
|
||||||
userRoutes,
|
userRoutes,
|
||||||
workspaceRoutes,
|
workspaceRoutes,
|
||||||
authRoutes,
|
authRoutes,
|
||||||
appRoutes,
|
|
||||||
templateRoutes,
|
templateRoutes,
|
||||||
tenantsRoutes,
|
tenantsRoutes,
|
||||||
emailRoutes,
|
emailRoutes,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue