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 { writable } from "svelte/store"
import api from "../../api" import { API } from "api"
import Automation from "./Automation" import Automation from "./Automation"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import analytics, { Events } from "analytics" 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 => ({ const automationActions = store => ({
fetch: async () => { fetch: async () => {
const responses = await Promise.all([ try {
api.get(`/api/automations`), const responses = await Promise.all([
api.get(`/api/automations/definitions/list`), API.getAutomations(),
]) API.getAutomationDefinitions(),
const jsonResponses = await Promise.all(responses.map(x => x.json())) ])
store.update(state => { store.update(state => {
let selected = state.selectedAutomation?.automation let selected = state.selectedAutomation?.automation
state.automations = jsonResponses[0] state.automations = responses[0]
state.blockDefinitions = { state.blockDefinitions = {
TRIGGER: jsonResponses[1].trigger, TRIGGER: responses[1].trigger,
ACTION: jsonResponses[1].action, ACTION: responses[1].action,
} }
// if previously selected find the new obj and select it // If previously selected find the new obj and select it
if (selected) { if (selected) {
selected = jsonResponses[0].filter( selected = responses[0].filter(
automation => automation._id === selected._id automation => automation._id === selected._id
) )
state.selectedAutomation = new Automation(selected[0]) state.selectedAutomation = new Automation(selected[0])
} }
return state return state
}) })
} catch (error) {
notifications.error("Error fetching automations")
store.set(initialAutomationState)
}
}, },
create: async ({ name }) => { create: async ({ name }) => {
const automation = { try {
name, const automation = {
type: "automation", name,
definition: { type: "automation",
steps: [], 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 => { save: async automation => {
const UPDATE_AUTOMATION_URL = `/api/automations` try {
const response = await api.put(UPDATE_AUTOMATION_URL, automation) const response = await API.updateAutomation(automation)
const json = await response.json() store.update(state => {
store.update(state => { const updatedAutomation = response.automation
const newAutomation = json.automation const existingIdx = state.automations.findIndex(
const existingIdx = state.automations.findIndex( existing => existing._id === automation._id
existing => existing._id === automation._id )
) if (existingIdx !== -1) {
if (existingIdx !== -1) { state.automations.splice(existingIdx, 1, updatedAutomation)
state.automations.splice(existingIdx, 1, newAutomation) state.automations = [...state.automations]
state.automations = [...state.automations] store.actions.select(updatedAutomation)
store.actions.select(newAutomation) return state
return state }
} })
}) notifications.success("Automation saved successfully")
} catch (error) {
notifications.error("Error saving automation")
}
}, },
delete: async automation => { delete: async automation => {
const { _id, _rev } = automation try {
const DELETE_AUTOMATION_URL = `/api/automations/${_id}/${_rev}` await API.deleteAutomation({
await api.delete(DELETE_AUTOMATION_URL) automationId: automation?._id,
automationRev: automation?._rev,
store.update(state => { })
const existingIdx = state.automations.findIndex( store.update(state => {
existing => existing._id === _id const existingIdx = state.automations.findIndex(
) existing => existing._id === automation?._id
state.automations.splice(existingIdx, 1) )
state.automations = [...state.automations] state.automations.splice(existingIdx, 1)
state.selectedAutomation = null state.automations = [...state.automations]
state.selectedBlock = null state.selectedAutomation = null
return state state.selectedBlock = null
}) return state
}, })
trigger: async automation => { notifications.success("Automation deleted successfully")
const { _id } = automation } catch (error) {
return await api.post(`/api/automations/${_id}/trigger`) notifications.error("Error deleting automation")
}
}, },
test: async (automation, testData) => { test: async (automation, testData) => {
const { _id } = automation try {
const response = await api.post(`/api/automations/${_id}/test`, testData) const result = await API.testAutomation({
const json = await response.json() automationId: automation?._id,
store.update(state => { testData,
state.selectedAutomation.testResults = json })
return state 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 => { select: automation => {
store.update(state => { 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 { writable } from "svelte/store"
import api, { get } from "../api" import { API } from "api"
import { notifications } from "@budibase/bbui"
const INITIAL_HOSTING_UI_STATE = { const INITIAL_HOSTING_UI_STATE = {
appUrl: "", appUrl: "",
@ -12,22 +13,40 @@ export const getHostingStore = () => {
const store = writable({ ...INITIAL_HOSTING_UI_STATE }) const store = writable({ ...INITIAL_HOSTING_UI_STATE })
store.actions = { store.actions = {
fetch: async () => { fetch: async () => {
const response = await api.get("/api/hosting/urls") try {
const urls = await response.json() const urls = await API.getHostingURLs()
store.update(state => { store.update(state => {
state.appUrl = urls.app state.appUrl = urls.app
return state return state
}) })
} catch (error) {
store.update(state => {
state.appUrl = ""
return state
})
notifications.error("Error fetching hosting URLs")
}
}, },
fetchDeployedApps: async () => { fetchDeployedApps: async () => {
let deployments = await (await get("/api/hosting/apps")).json() try {
store.update(state => { const deployments = await API.getDeployedApps()
state.deployedApps = deployments store.update(state => {
state.deployedAppNames = Object.values(deployments).map(app => app.name) state.deployedApps = deployments
state.deployedAppUrls = Object.values(deployments).map(app => app.url) state.deployedAppNames = Object.values(deployments).map(
return state app => app.name
}) )
return deployments 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 return store

View File

@ -33,23 +33,6 @@
await automationStore.actions.delete( await automationStore.actions.delete(
$automationStore.selectedAutomation?.automation $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> </script>
@ -85,7 +68,7 @@
animate:flip={{ duration: 500 }} animate:flip={{ duration: 500 }}
in:fly|local={{ x: 500, duration: 1500 }} in:fly|local={{ x: 500, duration: 1500 }}
> >
<FlowItem {testDataModal} {testAutomation} {onSelect} {block} /> <FlowItem {testDataModal} {onSelect} {block} />
</div> </div>
{/each} {/each}
</div> </div>
@ -101,7 +84,7 @@
</ConfirmDialog> </ConfirmDialog>
<Modal bind:this={testDataModal} width="30%"> <Modal bind:this={testDataModal} width="30%">
<TestDataModal {testAutomation} /> <TestDataModal />
</Modal> </Modal>
</div> </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 { tables, rows } from "stores/backend"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
import RowFieldControl from "../RowFieldControl.svelte" import RowFieldControl from "../RowFieldControl.svelte"
import * as api from "../api" import { API } from "api"
import { ModalContent } from "@budibase/bbui" import { ModalContent } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte" import ErrorsBox from "components/common/ErrorsBox.svelte"
import { FIELDS } from "constants/backend" import { FIELDS } from "constants/backend"
@ -22,30 +22,30 @@
$: tableSchema = Object.entries(table?.schema ?? {}) $: tableSchema = Object.entries(table?.schema ?? {})
async function saveRow() { async function saveRow() {
const rowResponse = await api.saveRow( errors = []
{ ...row, tableId: table._id }, try {
table._id await API.saveRow({ ...row, tableId: table._id })
) notifications.success("Row saved successfully")
rows.save()
if (rowResponse.errors) { dispatch("updaterows")
errors = Object.entries(rowResponse.errors) } catch (error) {
.map(([key, error]) => ({ dataPath: key, message: error })) if (error.handled) {
.flat() 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 // Prevent modal closing if there were errors
return false 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> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -122,4 +122,14 @@ export const buildAppEndpoints = API => ({
url: `/api/dev/${appId}/lock`, 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. * Updates the current user metadata.
* @param metadata the metadata to save * @param metadata the metadata to save
@ -116,4 +127,38 @@ export const buildAuthEndpoints = API => ({
url: "/api/system/environment", 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 }, 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 * @param {string} appId - ID of the currently running app
*/ */
fetchComponentLibDefinitions: async appId => { 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. * Gets the list of available integrations.
*/ */
getIntegrations: async () => { 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 { buildAttachmentEndpoints } from "./attachments"
import { buildAuthEndpoints } from "./auth" import { buildAuthEndpoints } from "./auth"
import { buildAutomationEndpoints } from "./automations" import { buildAutomationEndpoints } from "./automations"
import { buildHostingEndpoints } from "./hosting"
import { buildQueryEndpoints } from "./queries" import { buildQueryEndpoints } from "./queries"
import { buildRelationshipEndpoints } from "./relationships" import { buildRelationshipEndpoints } from "./relationships"
import { buildRouteEndpoints } from "./routes" import { buildRouteEndpoints } from "./routes"
@ -35,6 +36,7 @@ export const createAPIClient = config => {
// Try to read a message from the error // Try to read a message from the error
let message = response.statusText let message = response.statusText
let json = null
try { try {
const json = await response.json() const json = await response.json()
if (json?.message) { if (json?.message) {
@ -47,6 +49,7 @@ export const createAPIClient = config => {
} }
return { return {
message, message,
json,
status: response.status, status: response.status,
url: response.url, url: response.url,
method, method,
@ -58,6 +61,7 @@ export const createAPIClient = config => {
const makeError = message => { const makeError = message => {
return { return {
message, message,
json: null,
status: 400, status: 400,
url: "", url: "",
method: "", method: "",
@ -173,6 +177,7 @@ export const createAPIClient = config => {
...buildAttachmentEndpoints(API), ...buildAttachmentEndpoints(API),
...buildAuthEndpoints(API), ...buildAuthEndpoints(API),
...buildAutomationEndpoints(API), ...buildAutomationEndpoints(API),
...buildHostingEndpoints(API),
...buildQueryEndpoints(API), ...buildQueryEndpoints(API),
...buildRelationshipEndpoints(API), ...buildRelationshipEndpoints(API),
...buildRouteEndpoints(API), ...buildRouteEndpoints(API),