Add lots more work on refactoring builder to use core API

This commit is contained in:
Andrew Kingston 2022-01-21 15:09:27 +00:00
parent f2e45e892c
commit ca6fa1334a
16 changed files with 442 additions and 300 deletions

View File

@ -1,95 +1,130 @@
import { writable } from "svelte/store"
import api from "../../api"
import { API } from "api"
import Automation from "./Automation"
import { cloneDeep } from "lodash/fp"
import analytics, { Events } from "analytics"
import { notifications } from "@budibase/bbui"
const initialAutomationState = {
automations: [],
blockDefinitions: {
TRIGGER: [],
ACTION: [],
},
selectedAutomation: null,
}
export const getAutomationStore = () => {
const store = writable(initialAutomationState)
store.actions = automationActions(store)
return store
}
const automationActions = store => ({
fetch: async () => {
const responses = await Promise.all([
api.get(`/api/automations`),
api.get(`/api/automations/definitions/list`),
])
const jsonResponses = await Promise.all(responses.map(x => x.json()))
store.update(state => {
let selected = state.selectedAutomation?.automation
state.automations = jsonResponses[0]
state.blockDefinitions = {
TRIGGER: jsonResponses[1].trigger,
ACTION: jsonResponses[1].action,
}
// if previously selected find the new obj and select it
if (selected) {
selected = jsonResponses[0].filter(
automation => automation._id === selected._id
)
state.selectedAutomation = new Automation(selected[0])
}
return state
})
try {
const responses = await Promise.all([
API.getAutomations(),
API.getAutomationDefinitions(),
])
store.update(state => {
let selected = state.selectedAutomation?.automation
state.automations = responses[0]
state.blockDefinitions = {
TRIGGER: responses[1].trigger,
ACTION: responses[1].action,
}
// If previously selected find the new obj and select it
if (selected) {
selected = responses[0].filter(
automation => automation._id === selected._id
)
state.selectedAutomation = new Automation(selected[0])
}
return state
})
} catch (error) {
notifications.error("Error fetching automations")
store.set(initialAutomationState)
}
},
create: async ({ name }) => {
const automation = {
name,
type: "automation",
definition: {
steps: [],
},
try {
const automation = {
name,
type: "automation",
definition: {
steps: [],
},
}
const response = await API.createAutomation(automation)
store.update(state => {
state.automations = [...state.automations, response.automation]
store.actions.select(response.automation)
return state
})
} catch (error) {
notifications.error("Error creating automation")
}
const CREATE_AUTOMATION_URL = `/api/automations`
const response = await api.post(CREATE_AUTOMATION_URL, automation)
const json = await response.json()
store.update(state => {
state.automations = [...state.automations, json.automation]
store.actions.select(json.automation)
return state
})
},
save: async automation => {
const UPDATE_AUTOMATION_URL = `/api/automations`
const response = await api.put(UPDATE_AUTOMATION_URL, automation)
const json = await response.json()
store.update(state => {
const newAutomation = json.automation
const existingIdx = state.automations.findIndex(
existing => existing._id === automation._id
)
if (existingIdx !== -1) {
state.automations.splice(existingIdx, 1, newAutomation)
state.automations = [...state.automations]
store.actions.select(newAutomation)
return state
}
})
try {
const response = await API.updateAutomation(automation)
store.update(state => {
const updatedAutomation = response.automation
const existingIdx = state.automations.findIndex(
existing => existing._id === automation._id
)
if (existingIdx !== -1) {
state.automations.splice(existingIdx, 1, updatedAutomation)
state.automations = [...state.automations]
store.actions.select(updatedAutomation)
return state
}
})
notifications.success("Automation saved successfully")
} catch (error) {
notifications.error("Error saving automation")
}
},
delete: async automation => {
const { _id, _rev } = automation
const DELETE_AUTOMATION_URL = `/api/automations/${_id}/${_rev}`
await api.delete(DELETE_AUTOMATION_URL)
store.update(state => {
const existingIdx = state.automations.findIndex(
existing => existing._id === _id
)
state.automations.splice(existingIdx, 1)
state.automations = [...state.automations]
state.selectedAutomation = null
state.selectedBlock = null
return state
})
},
trigger: async automation => {
const { _id } = automation
return await api.post(`/api/automations/${_id}/trigger`)
try {
await API.deleteAutomation({
automationId: automation?._id,
automationRev: automation?._rev,
})
store.update(state => {
const existingIdx = state.automations.findIndex(
existing => existing._id === automation?._id
)
state.automations.splice(existingIdx, 1)
state.automations = [...state.automations]
state.selectedAutomation = null
state.selectedBlock = null
return state
})
notifications.success("Automation deleted successfully")
} catch (error) {
notifications.error("Error deleting automation")
}
},
test: async (automation, testData) => {
const { _id } = automation
const response = await api.post(`/api/automations/${_id}/test`, testData)
const json = await response.json()
store.update(state => {
state.selectedAutomation.testResults = json
return state
})
try {
const result = await API.testAutomation({
automationId: automation?._id,
testData,
})
store.update(state => {
state.selectedAutomation.testResults = result
return state
})
} catch (error) {
notifications.error("Error testing automation")
store.update(state => {
state.selectedAutomation.testResults = null
return state
})
}
},
select: automation => {
store.update(state => {
@ -143,17 +178,3 @@ const automationActions = store => ({
})
},
})
export const getAutomationStore = () => {
const INITIAL_AUTOMATION_STATE = {
automations: [],
blockDefinitions: {
TRIGGER: [],
ACTION: [],
},
selectedAutomation: null,
}
const store = writable(INITIAL_AUTOMATION_STATE)
store.actions = automationActions(store)
return store
}

View File

@ -1,5 +1,6 @@
import { writable } from "svelte/store"
import api, { get } from "../api"
import { API } from "api"
import { notifications } from "@budibase/bbui"
const INITIAL_HOSTING_UI_STATE = {
appUrl: "",
@ -12,22 +13,40 @@ export const getHostingStore = () => {
const store = writable({ ...INITIAL_HOSTING_UI_STATE })
store.actions = {
fetch: async () => {
const response = await api.get("/api/hosting/urls")
const urls = await response.json()
store.update(state => {
state.appUrl = urls.app
return state
})
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 () => {
let deployments = await (await get("/api/hosting/apps")).json()
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
})
return deployments
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

View File

@ -33,23 +33,6 @@
await automationStore.actions.delete(
$automationStore.selectedAutomation?.automation
)
notifications.success("Automation deleted.")
}
async function testAutomation() {
const result = await automationStore.actions.trigger(
$automationStore.selectedAutomation.automation
)
if (result.status === 200) {
notifications.success(
`Automation ${$automationStore.selectedAutomation.automation.name} triggered successfully.`
)
} else {
notifications.error(
`Failed to trigger automation ${$automationStore.selectedAutomation.automation.name}.`
)
}
return result
}
</script>
@ -85,7 +68,7 @@
animate:flip={{ duration: 500 }}
in:fly|local={{ x: 500, duration: 1500 }}
>
<FlowItem {testDataModal} {testAutomation} {onSelect} {block} />
<FlowItem {testDataModal} {onSelect} {block} />
</div>
{/each}
</div>
@ -101,7 +84,7 @@
</ConfirmDialog>
<Modal bind:this={testDataModal} width="30%">
<TestDataModal {testAutomation} />
<TestDataModal />
</Modal>
</div>

View File

@ -1,34 +0,0 @@
import api from "builderStore/api"
export async function createUser(user) {
const CREATE_USER_URL = `/api/users/metadata`
const response = await api.post(CREATE_USER_URL, user)
return await response.json()
}
export async function saveRow(row, tableId) {
const SAVE_ROW_URL = `/api/${tableId}/rows`
const response = await api.post(SAVE_ROW_URL, row)
return await response.json()
}
export async function deleteRow(row) {
const DELETE_ROWS_URL = `/api/${row.tableId}/rows`
return api.delete(DELETE_ROWS_URL, {
_id: row._id,
_rev: row._rev,
})
}
export async function fetchDataForTable(tableId) {
const FETCH_ROWS_URL = `/api/${tableId}/rows`
const response = await api.get(FETCH_ROWS_URL)
const json = await response.json()
if (response.status !== 200) {
throw new Error(json.message)
}
return json
}

View File

@ -3,7 +3,7 @@
import { tables, rows } from "stores/backend"
import { notifications } from "@budibase/bbui"
import RowFieldControl from "../RowFieldControl.svelte"
import * as api from "../api"
import { API } from "api"
import { ModalContent } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte"
import { FIELDS } from "constants/backend"
@ -22,30 +22,30 @@
$: tableSchema = Object.entries(table?.schema ?? {})
async function saveRow() {
const rowResponse = await api.saveRow(
{ ...row, tableId: table._id },
table._id
)
if (rowResponse.errors) {
errors = Object.entries(rowResponse.errors)
.map(([key, error]) => ({ dataPath: key, message: error }))
.flat()
errors = []
try {
await API.saveRow({ ...row, tableId: table._id })
notifications.success("Row saved successfully")
rows.save()
dispatch("updaterows")
} catch (error) {
if (error.handled) {
const response = error.json
if (response?.errors) {
errors = Object.entries(response.errors)
.map(([key, error]) => ({ dataPath: key, message: error }))
.flat()
} else if (error.status === 400 && response?.validationErrors) {
errors = Object.keys(response.validationErrors).map(field => ({
message: `${field} ${response.validationErrors[field][0]}`,
}))
}
} else {
notifications.error("Failed to save row")
}
// Prevent modal closing if there were errors
return false
} else if (rowResponse.status === 400 && rowResponse.validationErrors) {
errors = Object.keys(rowResponse.validationErrors).map(field => ({
message: `${field} ${rowResponse.validationErrors[field][0]}`,
}))
return false
} else if (rowResponse.status >= 400) {
errors = [{ message: rowResponse.message }]
return false
}
notifications.success("Row saved successfully.")
rows.save(rowResponse)
dispatch("updaterows")
}
</script>

View File

@ -4,7 +4,7 @@
import { roles } from "stores/backend"
import { notifications } from "@budibase/bbui"
import RowFieldControl from "../RowFieldControl.svelte"
import * as backendApi from "../api"
import { API } from "api"
import { ModalContent, Select } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte"
@ -53,27 +53,31 @@
return false
}
const rowResponse = await backendApi.saveRow(
{ ...row, tableId: table._id },
table._id
)
if (rowResponse.errors) {
if (Array.isArray(rowResponse.errors)) {
errors = rowResponse.errors.map(error => ({ message: error }))
try {
await API.saveRow({ ...row, tableId: table._id })
notifications.success("User saved successfully")
rows.save()
dispatch("updaterows")
} catch (error) {
if (error.handled) {
const response = error.json
if (response?.errors) {
if (Array.isArray(response.errors)) {
errors = response.errors.map(error => ({ message: error }))
} else {
errors = Object.entries(response.errors)
.map(([key, error]) => ({ dataPath: key, message: error }))
.flat()
}
} else if (error.status === 400) {
errors = [{ message: response?.message || "Unknown error" }]
}
} else {
errors = Object.entries(rowResponse.errors)
.map(([key, error]) => ({ dataPath: key, message: error }))
.flat()
notifications.error("Error saving user")
}
return false
} else if (rowResponse.status === 400 || rowResponse.status === 500) {
errors = [{ message: rowResponse.message }]
// Prevent closing the modal on errors
return false
}
notifications.success("User saved successfully")
rows.save(rowResponse)
dispatch("updaterows")
}
</script>

View File

@ -6,25 +6,26 @@
import RevertModal from "components/deploy/RevertModal.svelte"
import VersionModal from "components/deploy/VersionModal.svelte"
import NPSFeedbackForm from "components/feedback/NPSFeedbackForm.svelte"
import { get, post } from "builderStore/api"
import { API } from "api"
import { auth, admin } from "stores/portal"
import { isActive, goto, layout, redirect } from "@roxi/routify"
import Logo from "assets/bb-emblem.svg"
import { capitalise } from "helpers"
import UpgradeModal from "../../../../components/upgrade/UpgradeModal.svelte"
import UpgradeModal from "components/upgrade/UpgradeModal.svelte"
import { onMount } from "svelte"
// Get Package and set store
export let application
// Get Package and set store
let promise = getPackage()
// sync once when you load the app
// Sync once when you load the app
let hasSynced = false
let userShouldPostFeedback = false
$: selected = capitalise(
$layout.children.find(layout => $isActive(layout.path))?.title ?? "data"
)
let userShouldPostFeedback = false
function previewApp() {
if (!$auth?.user?.flags?.feedbackSubmitted) {
userShouldPostFeedback = true
@ -33,34 +34,25 @@
}
async function getPackage() {
const res = await get(`/api/applications/${application}/appPackage`)
const pkg = await res.json()
if (res.ok) {
try {
await store.actions.initialise(pkg)
// edge case, lock wasn't known to client when it re-directed, or user went directly
} catch (err) {
if (!err.ok && err.reason === "locked") {
$redirect("../../")
} else {
throw err
}
}
try {
const pkg = await API.fetchAppPackage(application)
await store.actions.initialise(pkg)
await automationStore.actions.fetch()
await roles.fetch()
await flags.fetch()
return pkg
} else {
throw new Error(pkg)
} catch (error) {
// TODO: some stuff about redirecting if locked
// $redirect("../../")
notifications.error("Error initialising app")
}
}
// handles navigation between frontend, backend, automation.
// this remembers your last place on each of the sections
// Handles navigation between frontend, backend, automation.
// This remembers your last place on each of the sections
// e.g. if one of your screens is selected on front end, then
// you browse to backend, when you click frontend, you will be
// brought back to the same screen
// brought back to the same screen.
const topItemNavigate = path => () => {
const activeTopNav = $layout.children.find(c => $isActive(c.path))
if (!activeTopNav) return
@ -74,8 +66,9 @@
onMount(async () => {
if (!hasSynced && application) {
const res = await post(`/api/applications/${application}/sync`)
if (res.status !== 200) {
try {
await API.syncApp(application)
} catch (error) {
notifications.error("Failed to sync with production database")
}
hasSynced = true

View File

@ -20,7 +20,7 @@
Toggle,
} from "@budibase/bbui"
import { onMount } from "svelte"
import api from "builderStore/api"
import { API } from "api"
import { organisation, admin } from "stores/portal"
import { Helpers } from "@budibase/bbui"
import analytics, { Events } from "analytics"
@ -137,17 +137,6 @@
providers.oidc?.config?.configs[0].clientID &&
providers.oidc?.config?.configs[0].clientSecret
async function uploadLogo(file) {
let data = new FormData()
data.append("file", file)
const res = await api.post(
`/api/global/configs/upload/logos_oidc/${file.name}`,
data,
{}
)
return await res.json()
}
const onFileSelected = e => {
let fileName = e.target.files[0].name
image = e.target.files[0]
@ -156,17 +145,28 @@
}
async function save(docs) {
// only if the user has provided an image, upload it.
image && uploadLogo(image)
let calls = []
// Only if the user has provided an image, upload it
if (image) {
let data = new FormData()
data.append("file", image)
calls.push(
API.uploadOIDCLogo({
name: image.name,
data,
})
)
}
docs.forEach(element => {
if (element.type === ConfigTypes.OIDC) {
//Add a UUID here so each config is distinguishable when it arrives at the login page
// Add a UUID here so each config is distinguishable when it arrives at the login page
for (let config of element.config.configs) {
if (!config.uuid) {
config.uuid = Helpers.uuid()
}
// callback urls shouldn't be included
// Callback urls shouldn't be included
delete config.callbackURL
}
if (partialOidc) {
@ -175,8 +175,8 @@
`Please fill in all required ${ConfigTypes.OIDC} fields`
)
} else {
calls.push(api.post(`/api/global/configs`, element))
// turn the save button grey when clicked
calls.push(API.saveConfig(element))
// Turn the save button grey when clicked
oidcSaveButtonDisabled = true
originalOidcDoc = cloneDeep(providers.oidc)
}
@ -189,71 +189,70 @@
`Please fill in all required ${ConfigTypes.Google} fields`
)
} else {
calls.push(api.post(`/api/global/configs`, element))
calls.push(API.saveConfig(element))
googleSaveButtonDisabled = true
originalGoogleDoc = cloneDeep(providers.google)
}
}
}
})
calls.length &&
if (calls.length) {
Promise.all(calls)
.then(responses => {
return Promise.all(
responses.map(response => {
return response.json()
})
)
})
.then(data => {
data.forEach(res => {
providers[res.type]._rev = res._rev
providers[res.type]._id = res._id
})
notifications.success(`Settings saved.`)
notifications.success(`Settings saved`)
analytics.captureEvent(Events.SSO.SAVED)
})
.catch(err => {
notifications.error(`Failed to update auth settings. ${err}`)
throw new Error(err.message)
.catch(error => {
notifications.error(`Failed to update auth settings`)
console.error(error.message)
})
}
}
onMount(async () => {
await organisation.init()
// fetch the configs for oauth
const googleResponse = await api.get(
`/api/global/configs/${ConfigTypes.Google}`
)
const googleDoc = await googleResponse.json()
if (!googleDoc._id) {
// Fetch Google config
let googleDoc
try {
googleDoc = await API.getConfig(ConfigTypes.Google)
} catch (error) {
notifications.error("Error fetching Google OAuth config")
}
if (!googleDoc?._id) {
providers.google = {
type: ConfigTypes.Google,
config: { activated: true },
}
originalGoogleDoc = cloneDeep(googleDoc)
} else {
// default activated to true for older configs
// Default activated to true for older configs
if (googleDoc.config.activated === undefined) {
googleDoc.config.activated = true
}
originalGoogleDoc = cloneDeep(googleDoc)
providers.google = googleDoc
}
googleCallbackUrl = providers?.google?.config?.callbackURL
//Get the list of user uploaded logos and push it to the dropdown options.
//This needs to be done before the config call so they're available when the dropdown renders
const res = await api.get(`/api/global/configs/logos_oidc`)
const configSettings = await res.json()
if (configSettings.config) {
const logoKeys = Object.keys(configSettings.config)
// Get the list of user uploaded logos and push it to the dropdown options.
// This needs to be done before the config call so they're available when
// the dropdown renders.
let oidcLogos
try {
oidcLogos = await API.getOIDCLogos()
} catch (error) {
notifications.error("Error fetching OIDC logos")
}
if (oidcLogos?.config) {
const logoKeys = Object.keys(oidcLogos.config)
logoKeys.map(logoKey => {
const logoUrl = configSettings.config[logoKey]
const logoUrl = oidcLogos.config[logoKey]
iconDropdownOptions.unshift({
label: logoKey,
value: logoKey,
@ -261,11 +260,15 @@
})
})
}
const oidcResponse = await api.get(
`/api/global/configs/${ConfigTypes.OIDC}`
)
const oidcDoc = await oidcResponse.json()
if (!oidcDoc._id) {
// Fetch OIDC config
let oidcDoc
try {
oidcDoc = await API.getConfig(ConfigTypes.OIDC)
} catch (error) {
notifications.error("Error fetching OIDC config")
}
if (!oidcDoc?._id) {
providers.oidc = {
type: ConfigTypes.OIDC,
config: { configs: [{ activated: true }] },

View File

@ -11,7 +11,7 @@
notifications,
} from "@budibase/bbui"
import { auth, organisation, admin } from "stores/portal"
import { post } from "builderStore/api"
import { API } from "api"
import { writable } from "svelte/store"
import { redirect } from "@roxi/routify"
@ -32,14 +32,13 @@
let loading = false
async function uploadLogo(file) {
let data = new FormData()
data.append("file", file)
const res = await post(
"/api/global/configs/upload/settings/logoUrl",
data,
{}
)
return await res.json()
try {
let data = new FormData()
data.append("file", file)
await API.uploadLogo(data)
} catch (error) {
notifications.error("Error uploading logo")
}
}
async function saveConfig() {
@ -55,19 +54,14 @@
company: $values.company ?? "",
platformUrl: $values.platformUrl ?? "",
}
// remove logo if required
// Remove logo if required
if (!$values.logo) {
config.logoUrl = ""
}
// Update settings
const res = await organisation.save(config)
if (res.status === 200) {
notifications.success("Settings saved successfully")
} else {
notifications.error(res.message)
}
await organisation.save(config)
loading = false
}
</script>

View File

@ -9,7 +9,7 @@
notifications,
Label,
} from "@budibase/bbui"
import api from "builderStore/api"
import { API } from "api"
import { auth, admin } from "stores/portal"
import { redirect } from "@roxi/routify"
@ -38,8 +38,12 @@
}
async function getVersion() {
const response = await api.get("/api/dev/version")
version = await response.text()
try {
version = await API.getBudibaseVersion()
} catch (error) {
notifications.error("Error getting Budibase version")
version = null
}
}
onMount(() => {

View File

@ -122,4 +122,14 @@ export const buildAppEndpoints = API => ({
url: `/api/dev/${appId}/lock`,
})
},
/**
* Syncs an app with the production database.
* @param appId the ID of the app to sync
*/
syncApp: async appId => {
return await API.post({
url: `/api/applications/${appId}/sync`,
})
},
})

View File

@ -36,6 +36,17 @@ export const buildAuthEndpoints = API => ({
})
},
/**
* Creates a user for an app.
* @param user the user to create
*/
createAppUser: async user => {
return await API.post({
url: "/api/users/metadata",
body: user,
})
},
/**
* Updates the current user metadata.
* @param metadata the metadata to save
@ -116,4 +127,38 @@ export const buildAuthEndpoints = API => ({
url: "/api/system/environment",
})
},
/**
* Updates the company logo for the environment.
* @param data the logo form data
*/
uploadLogo: async data => {
return await API.post({
url: "/api/global/configs/upload/settings/logoUrl",
body: data,
json: false,
})
},
/**
* Uploads a logo for an OIDC provider.
* @param name the name of the OIDC provider
* @param data the logo form data to upload
*/
uploadOIDCLogo: async ({ name, data }) => {
return await API.post({
url: `/api/global/configs/upload/logos_oidc/${name}`,
body: data,
json: false,
})
},
/**
* Gets the list of OIDC logos.
*/
getOIDCLogos: async () => {
return await API.get({
url: "/api/global/configs/logos_oidc",
})
},
})

View File

@ -8,4 +8,67 @@ export const buildAutomationEndpoints = API => ({
body: { fields },
})
},
/**
* Tests an automation with data.
* @param automationId the ID of the automation to test
* @param testData the test data to run against the automation
*/
testAutomation: async ({ automationId, testData }) => {
return await API.post({
url: `/api/automations/${automationId}/test`,
body: testData,
})
},
/**
* Gets a list of all automations.
*/
getAutomations: async () => {
return await API.get({
url: "/api/automations",
})
},
/**
* Gets a list of all the definitions for blocks in automations.
*/
getAutomationDefinitions: async () => {
return await API.get({
url: "/api/automations/definitions/list",
})
},
/**
* Creates an automation.
* @param automation the automation to create
*/
createAutomation: async automation => {
return await API.post({
url: "/api/automations",
body: automation,
})
},
/**
* Updates an automation.
* @param automation the automation to update
*/
updateAutomation: async automation => {
return await API.put({
url: "/api/automations",
body: automation,
})
},
/**
* Deletes an automation
* @param automationId the ID of the automation to delete
* @param automationRev the rev of the automation to delete
*/
deleteAutomation: async ({ automationId, automationRev }) => {
return await API.delete({
url: `/api/automations/${automationId}/${automationRev}`,
})
},
})

View File

@ -5,13 +5,26 @@ export const buildBuilderEndpoints = API => ({
* @param {string} appId - ID of the currently running app
*/
fetchComponentLibDefinitions: async appId => {
return await API.get({ url: `/api/${appId}/components/definitions` })
return await API.get({
url: `/api/${appId}/components/definitions`,
})
},
/**
* Gets the list of available integrations.
*/
getIntegrations: async () => {
return await API.get({ url: "/api/integrations" })
return await API.get({
url: "/api/integrations",
})
},
/**
* Gets the version of the installed Budibase environment.
*/
getBudibaseVersion: async () => {
return await API.get({
url: "/api/dev/version",
})
},
})

View File

@ -0,0 +1,19 @@
export const buildHostingEndpoints = API => ({
/**
* Gets the hosting URLs of the environment.
*/
getHostingURLs: async () => {
return await API.get({
url: "/api/hosting/urls",
})
},
/**
* Gets the list of deployed apps.
*/
getDeployedApps: async () => {
return await API.get({
url: "/api/hosting/apps",
})
},
})

View File

@ -4,6 +4,7 @@ import { buildAppEndpoints } from "./app"
import { buildAttachmentEndpoints } from "./attachments"
import { buildAuthEndpoints } from "./auth"
import { buildAutomationEndpoints } from "./automations"
import { buildHostingEndpoints } from "./hosting"
import { buildQueryEndpoints } from "./queries"
import { buildRelationshipEndpoints } from "./relationships"
import { buildRouteEndpoints } from "./routes"
@ -35,6 +36,7 @@ export const createAPIClient = config => {
// Try to read a message from the error
let message = response.statusText
let json = null
try {
const json = await response.json()
if (json?.message) {
@ -47,6 +49,7 @@ export const createAPIClient = config => {
}
return {
message,
json,
status: response.status,
url: response.url,
method,
@ -58,6 +61,7 @@ export const createAPIClient = config => {
const makeError = message => {
return {
message,
json: null,
status: 400,
url: "",
method: "",
@ -173,6 +177,7 @@ export const createAPIClient = config => {
...buildAttachmentEndpoints(API),
...buildAuthEndpoints(API),
...buildAutomationEndpoints(API),
...buildHostingEndpoints(API),
...buildQueryEndpoints(API),
...buildRelationshipEndpoints(API),
...buildRouteEndpoints(API),