commit
982ae39907
|
@ -8,7 +8,7 @@ const { newid } = require("../../hashing")
|
|||
const { createASession } = require("../../security/sessions")
|
||||
const { getTenantId } = require("../../tenancy")
|
||||
|
||||
const INVALID_ERR = "Invalid Credentials"
|
||||
const INVALID_ERR = "Invalid credentials"
|
||||
const SSO_NO_PASSWORD = "SSO user does not have a password set"
|
||||
const EXPIRED = "This account has expired. Please reset your password"
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { fly } from "svelte/transition"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
import Input from "../Form/Input.svelte"
|
||||
import { capitalise } from "../utils/helpers"
|
||||
import { capitalise } from "../helpers"
|
||||
|
||||
export let value
|
||||
export let size = "M"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||
import "@spectrum-css/picker/dist/index-vars.css"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { generateID } from "../../utils/helpers"
|
||||
import { uuid } from "../../helpers"
|
||||
|
||||
export let id = null
|
||||
export let disabled = false
|
||||
|
@ -17,7 +17,7 @@
|
|||
export let timeOnly = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const flatpickrId = `${generateID()}-wrapper`
|
||||
const flatpickrId = `${uuid()}-wrapper`
|
||||
let open = false
|
||||
let flatpickr, flatpickrOptions, isTimeOnly
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import "@spectrum-css/typography/dist/index-vars.css"
|
||||
import "@spectrum-css/illustratedmessage/dist/index-vars.css"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { generateID } from "../../utils/helpers"
|
||||
import { uuid } from "../../helpers"
|
||||
import Icon from "../../Icon/Icon.svelte"
|
||||
import Link from "../../Link/Link.svelte"
|
||||
import Tag from "../../Tags/Tag.svelte"
|
||||
|
@ -37,7 +37,7 @@
|
|||
"jfif",
|
||||
]
|
||||
|
||||
const fieldId = id || generateID()
|
||||
const fieldId = id || uuid()
|
||||
let selectedImageIdx = 0
|
||||
let fileDragged = false
|
||||
let selectedUrl
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import CellRenderer from "./CellRenderer.svelte"
|
||||
import SelectEditRenderer from "./SelectEditRenderer.svelte"
|
||||
import { cloneDeep } from "lodash"
|
||||
import { deepGet } from "../utils/helpers"
|
||||
import { deepGet } from "../helpers"
|
||||
|
||||
/**
|
||||
* The expected schema is our normal couch schemas for our tables.
|
||||
|
|
|
@ -1,11 +1,45 @@
|
|||
export const generateID = () => {
|
||||
const rand = Math.random().toString(32).substring(2)
|
||||
|
||||
// Starts with a letter so that its a valid DOM ID
|
||||
return `A${rand}`
|
||||
/**
|
||||
* Generates a DOM safe UUID.
|
||||
* Starting with a letter is important to make it DOM safe.
|
||||
* @return {string} a random DOM safe UUID
|
||||
*/
|
||||
export function uuid() {
|
||||
return "cxxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, c => {
|
||||
const r = (Math.random() * 16) | 0
|
||||
const v = c === "x" ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
export const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||
/**
|
||||
* Capitalises a string
|
||||
* @param string the string to capitalise
|
||||
* @return {string} the capitalised string
|
||||
*/
|
||||
export const capitalise = string => {
|
||||
if (!string) {
|
||||
return string
|
||||
}
|
||||
return string.substring(0, 1).toUpperCase() + string.substring(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a short hash of a string
|
||||
* @param string the string to compute a hash of
|
||||
* @return {string} the hash string
|
||||
*/
|
||||
export const hashString = string => {
|
||||
if (!string) {
|
||||
return "0"
|
||||
}
|
||||
let hash = 0
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
let char = string.charCodeAt(i)
|
||||
hash = (hash << 5) - hash + char
|
||||
hash = hash & hash // Convert to 32bit integer
|
||||
}
|
||||
return hash.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key within an object. The key supports dot syntax for retrieving deep
|
|
@ -85,5 +85,5 @@ export { default as clickOutside } from "./Actions/click_outside"
|
|||
// Stores
|
||||
export { notifications, createNotificationStore } from "./Stores/notifications"
|
||||
|
||||
// Utils
|
||||
export * from "./utils/helpers"
|
||||
// Helpers
|
||||
export * as Helpers from "./helpers"
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
"scripts": {
|
||||
"build": "routify -b && vite build --emptyOutDir",
|
||||
"start": "routify -c rollup",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watchAll",
|
||||
"dev:builder": "routify -c dev:vite",
|
||||
"dev:vite": "vite --host 0.0.0.0",
|
||||
"rollup": "rollup -c -w",
|
||||
|
@ -68,7 +66,7 @@
|
|||
"dependencies": {
|
||||
"@budibase/bbui": "^1.0.50-alpha.5",
|
||||
"@budibase/client": "^1.0.50-alpha.5",
|
||||
"@budibase/colorpicker": "1.1.2",
|
||||
"@budibase/frontend-core": "^1.0.50-alpha.5",
|
||||
"@budibase/string-templates": "^1.0.50-alpha.5",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import PosthogClient from "./PosthogClient"
|
||||
import IntercomClient from "./IntercomClient"
|
||||
import SentryClient from "./SentryClient"
|
||||
|
@ -17,13 +17,11 @@ class AnalyticsHub {
|
|||
}
|
||||
|
||||
async activate() {
|
||||
const analyticsStatus = await api.get("/api/analytics")
|
||||
const json = await analyticsStatus.json()
|
||||
|
||||
// Analytics disabled
|
||||
if (!json.enabled) return
|
||||
|
||||
this.clients.forEach(client => client.init())
|
||||
// Check analytics are enabled
|
||||
const analyticsStatus = await API.getAnalyticsStatus()
|
||||
if (analyticsStatus.enabled) {
|
||||
this.clients.forEach(client => client.init())
|
||||
}
|
||||
}
|
||||
|
||||
identify(id, metadata) {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import {
|
||||
createAPIClient,
|
||||
CookieUtils,
|
||||
Constants,
|
||||
} from "@budibase/frontend-core"
|
||||
import { store } from "./builderStore"
|
||||
import { get } from "svelte/store"
|
||||
import { auth } from "./stores/portal"
|
||||
|
||||
export const API = createAPIClient({
|
||||
attachHeaders: headers => {
|
||||
// Attach app ID header from store
|
||||
headers["x-budibase-app-id"] = get(store).appId
|
||||
|
||||
// Add csrf token if authenticated
|
||||
const user = get(auth).user
|
||||
if (user?.csrfToken) {
|
||||
headers["x-csrf-token"] = user.csrfToken
|
||||
}
|
||||
},
|
||||
|
||||
onError: error => {
|
||||
const { url, message, status, method, handled } = error || {}
|
||||
|
||||
// Log all API errors to Sentry
|
||||
// analytics.captureException(error)
|
||||
|
||||
// Log any errors that we haven't manually handled
|
||||
if (!handled) {
|
||||
console.error("Unhandled error from API client", error)
|
||||
return
|
||||
}
|
||||
|
||||
// Log all errors to console
|
||||
console.warn(`[Builder] HTTP ${status} on ${method}:${url}\n\t${message}`)
|
||||
|
||||
// Logout on 403's
|
||||
if (status === 403) {
|
||||
// Remove cookies
|
||||
CookieUtils.removeCookie(Constants.Cookies.Auth)
|
||||
|
||||
// Reload after removing cookie, go to login
|
||||
if (!url.includes("self") && !url.includes("login")) {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
|
@ -1,49 +0,0 @@
|
|||
import { store } from "./index"
|
||||
import { get as svelteGet } from "svelte/store"
|
||||
import { removeCookie, Cookies } from "./cookies"
|
||||
import { auth } from "stores/portal"
|
||||
|
||||
const apiCall =
|
||||
method =>
|
||||
async (url, body, headers = { "Content-Type": "application/json" }) => {
|
||||
headers["x-budibase-app-id"] = svelteGet(store).appId
|
||||
headers["x-budibase-api-version"] = "1"
|
||||
|
||||
// add csrf token if authenticated
|
||||
const user = svelteGet(auth).user
|
||||
if (user && user.csrfToken) {
|
||||
headers["x-csrf-token"] = user.csrfToken
|
||||
}
|
||||
|
||||
const json = headers["Content-Type"] === "application/json"
|
||||
const resp = await fetch(url, {
|
||||
method: method,
|
||||
body: json ? JSON.stringify(body) : body,
|
||||
headers,
|
||||
})
|
||||
if (resp.status === 403) {
|
||||
if (url.includes("/api/templates")) {
|
||||
return { json: () => [] }
|
||||
}
|
||||
removeCookie(Cookies.Auth)
|
||||
// reload after removing cookie, go to login
|
||||
if (!url.includes("self") && !url.includes("login")) {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
export const post = apiCall("POST")
|
||||
export const get = apiCall("GET")
|
||||
export const patch = apiCall("PATCH")
|
||||
export const del = apiCall("DELETE")
|
||||
export const put = apiCall("PUT")
|
||||
|
||||
export default {
|
||||
post: apiCall("POST"),
|
||||
get: apiCall("GET"),
|
||||
patch: apiCall("PATCH"),
|
||||
delete: apiCall("DELETE"),
|
||||
put: apiCall("PUT"),
|
||||
}
|
|
@ -15,10 +15,7 @@ import {
|
|||
encodeJSBinding,
|
||||
} from "@budibase/string-templates"
|
||||
import { TableNames } from "../constants"
|
||||
import {
|
||||
convertJSONSchemaToTableSchema,
|
||||
getJSONArrayDatasourceSchema,
|
||||
} from "./jsonUtils"
|
||||
import { JSONUtils } from "@budibase/frontend-core"
|
||||
import ActionDefinitions from "components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json"
|
||||
|
||||
// Regex to match all instances of template strings
|
||||
|
@ -439,7 +436,7 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
|||
else if (type === "jsonarray") {
|
||||
table = tables.find(table => table._id === datasource.tableId)
|
||||
let tableSchema = table?.schema
|
||||
schema = getJSONArrayDatasourceSchema(tableSchema, datasource)
|
||||
schema = JSONUtils.getJSONArrayDatasourceSchema(tableSchema, datasource)
|
||||
}
|
||||
|
||||
// Otherwise we assume we're targeting an internal table or a plus
|
||||
|
@ -471,9 +468,12 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
|||
Object.keys(schema).forEach(fieldKey => {
|
||||
const fieldSchema = schema[fieldKey]
|
||||
if (fieldSchema?.type === "json") {
|
||||
const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, {
|
||||
squashObjects: true,
|
||||
})
|
||||
const jsonSchema = JSONUtils.convertJSONSchemaToTableSchema(
|
||||
fieldSchema,
|
||||
{
|
||||
squashObjects: true,
|
||||
}
|
||||
)
|
||||
Object.keys(jsonSchema).forEach(jsonKey => {
|
||||
jsonAdditions[`${fieldKey}.${jsonKey}`] = {
|
||||
type: jsonSchema[jsonKey].type,
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { get } from "builderStore/api"
|
||||
|
||||
/**
|
||||
* Fetches the definitions for component library components. This includes
|
||||
* their props and other metadata from components.json.
|
||||
* @param {string} appId - ID of the currently running app
|
||||
*/
|
||||
export const fetchComponentLibDefinitions = async appId => {
|
||||
const LIB_DEFINITION_URL = `/api/${appId}/components/definitions`
|
||||
try {
|
||||
const libDefinitionResponse = await get(LIB_DEFINITION_URL)
|
||||
return await libDefinitionResponse.json()
|
||||
} catch (err) {
|
||||
console.error(`Error fetching component definitions for ${appId}`, err)
|
||||
}
|
||||
}
|
|
@ -1,26 +1,40 @@
|
|||
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"
|
||||
|
||||
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`),
|
||||
API.getAutomations(),
|
||||
API.getAutomationDefinitions(),
|
||||
])
|
||||
const jsonResponses = await Promise.all(responses.map(x => x.json()))
|
||||
store.update(state => {
|
||||
let selected = state.selectedAutomation?.automation
|
||||
state.automations = jsonResponses[0]
|
||||
state.automations = responses[0]
|
||||
state.blockDefinitions = {
|
||||
TRIGGER: jsonResponses[1].trigger,
|
||||
ACTION: jsonResponses[1].action,
|
||||
TRIGGER: responses[1].trigger,
|
||||
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) {
|
||||
selected = jsonResponses[0].filter(
|
||||
selected = responses[0].filter(
|
||||
automation => automation._id === selected._id
|
||||
)
|
||||
state.selectedAutomation = new Automation(selected[0])
|
||||
|
@ -36,40 +50,36 @@ const automationActions = store => ({
|
|||
steps: [],
|
||||
},
|
||||
}
|
||||
const CREATE_AUTOMATION_URL = `/api/automations`
|
||||
const response = await api.post(CREATE_AUTOMATION_URL, automation)
|
||||
const json = await response.json()
|
||||
const response = await API.createAutomation(automation)
|
||||
store.update(state => {
|
||||
state.automations = [...state.automations, json.automation]
|
||||
store.actions.select(json.automation)
|
||||
state.automations = [...state.automations, response.automation]
|
||||
store.actions.select(response.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()
|
||||
const response = await API.updateAutomation(automation)
|
||||
store.update(state => {
|
||||
const newAutomation = json.automation
|
||||
const updatedAutomation = response.automation
|
||||
const existingIdx = state.automations.findIndex(
|
||||
existing => existing._id === automation._id
|
||||
)
|
||||
if (existingIdx !== -1) {
|
||||
state.automations.splice(existingIdx, 1, newAutomation)
|
||||
state.automations.splice(existingIdx, 1, updatedAutomation)
|
||||
state.automations = [...state.automations]
|
||||
store.actions.select(newAutomation)
|
||||
store.actions.select(updatedAutomation)
|
||||
return state
|
||||
}
|
||||
})
|
||||
},
|
||||
delete: async automation => {
|
||||
const { _id, _rev } = automation
|
||||
const DELETE_AUTOMATION_URL = `/api/automations/${_id}/${_rev}`
|
||||
await api.delete(DELETE_AUTOMATION_URL)
|
||||
|
||||
await API.deleteAutomation({
|
||||
automationId: automation?._id,
|
||||
automationRev: automation?._rev,
|
||||
})
|
||||
store.update(state => {
|
||||
const existingIdx = state.automations.findIndex(
|
||||
existing => existing._id === _id
|
||||
existing => existing._id === automation?._id
|
||||
)
|
||||
state.automations.splice(existingIdx, 1)
|
||||
state.automations = [...state.automations]
|
||||
|
@ -78,16 +88,17 @@ const automationActions = store => ({
|
|||
return state
|
||||
})
|
||||
},
|
||||
trigger: async automation => {
|
||||
const { _id } = automation
|
||||
return await api.post(`/api/automations/${_id}/trigger`)
|
||||
},
|
||||
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
|
||||
state.selectedAutomation.testResults = null
|
||||
return state
|
||||
})
|
||||
const result = await API.testAutomation({
|
||||
automationId: automation?._id,
|
||||
testData,
|
||||
})
|
||||
store.update(state => {
|
||||
state.selectedAutomation.testResults = result
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
@ -143,17 +154,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
|
||||
}
|
||||
|
|
|
@ -14,8 +14,7 @@ import {
|
|||
database,
|
||||
tables,
|
||||
} from "stores/backend"
|
||||
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
||||
import api from "../api"
|
||||
import { API } from "api"
|
||||
import { FrontendTypes } from "constants"
|
||||
import analytics, { Events } from "analytics"
|
||||
import {
|
||||
|
@ -26,7 +25,7 @@ import {
|
|||
findComponent,
|
||||
getComponentSettings,
|
||||
} from "../componentUtils"
|
||||
import { uuid } from "../uuid"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { removeBindings } from "../dataBinding"
|
||||
|
||||
const INITIAL_FRONTEND_STATE = {
|
||||
|
@ -70,15 +69,12 @@ export const getFrontendStore = () => {
|
|||
},
|
||||
initialise: async pkg => {
|
||||
const { layouts, screens, application, clientLibPath } = pkg
|
||||
const components = await fetchComponentLibDefinitions(application.appId)
|
||||
// make sure app isn't locked
|
||||
if (
|
||||
components &&
|
||||
components.status === 400 &&
|
||||
components.message?.includes("lock")
|
||||
) {
|
||||
throw { ok: false, reason: "locked" }
|
||||
}
|
||||
|
||||
// Fetch component definitions.
|
||||
// Allow errors to propagate.
|
||||
let components = await API.fetchComponentLibDefinitions(application.appId)
|
||||
|
||||
// Reset store state
|
||||
store.update(state => ({
|
||||
...state,
|
||||
libraries: application.componentLibraries,
|
||||
|
@ -91,8 +87,8 @@ export const getFrontendStore = () => {
|
|||
description: application.description,
|
||||
appId: application.appId,
|
||||
url: application.url,
|
||||
layouts,
|
||||
screens,
|
||||
layouts: layouts || [],
|
||||
screens: screens || [],
|
||||
theme: application.theme || "spectrum--light",
|
||||
customTheme: application.customTheme,
|
||||
hasAppPackage: true,
|
||||
|
@ -104,51 +100,43 @@ export const getFrontendStore = () => {
|
|||
}))
|
||||
|
||||
// Initialise backend stores
|
||||
const [_integrations] = await Promise.all([
|
||||
api.get("/api/integrations").then(r => r.json()),
|
||||
])
|
||||
datasources.init()
|
||||
integrations.set(_integrations)
|
||||
queries.init()
|
||||
database.set(application.instance)
|
||||
tables.init()
|
||||
await datasources.init()
|
||||
await integrations.init()
|
||||
await queries.init()
|
||||
await tables.init()
|
||||
},
|
||||
theme: {
|
||||
save: async theme => {
|
||||
const appId = get(store).appId
|
||||
const response = await api.put(`/api/applications/${appId}`, { theme })
|
||||
if (response.status === 200) {
|
||||
store.update(state => {
|
||||
state.theme = theme
|
||||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Error updating theme")
|
||||
}
|
||||
await API.saveAppMetadata({
|
||||
appId,
|
||||
metadata: { theme },
|
||||
})
|
||||
store.update(state => {
|
||||
state.theme = theme
|
||||
return state
|
||||
})
|
||||
},
|
||||
},
|
||||
customTheme: {
|
||||
save: async customTheme => {
|
||||
const appId = get(store).appId
|
||||
const response = await api.put(`/api/applications/${appId}`, {
|
||||
customTheme,
|
||||
await API.saveAppMetadata({
|
||||
appId,
|
||||
metadata: { customTheme },
|
||||
})
|
||||
store.update(state => {
|
||||
state.customTheme = customTheme
|
||||
return state
|
||||
})
|
||||
if (response.status === 200) {
|
||||
store.update(state => {
|
||||
state.customTheme = customTheme
|
||||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Error updating theme")
|
||||
}
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
fetch: async () => {
|
||||
const response = await api.get("/api/routing")
|
||||
const json = await response.json()
|
||||
const response = await API.fetchAppRoutes()
|
||||
store.update(state => {
|
||||
state.routes = json.routes
|
||||
state.routes = response.routes
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
@ -172,82 +160,76 @@ export const getFrontendStore = () => {
|
|||
return state
|
||||
})
|
||||
},
|
||||
create: async screen => {
|
||||
screen = await store.actions.screens.save(screen)
|
||||
store.update(state => {
|
||||
state.selectedScreenId = screen._id
|
||||
state.selectedComponentId = screen.props._id
|
||||
state.currentFrontEndType = FrontendTypes.SCREEN
|
||||
selectedAccessRole.set(screen.routing.roleId)
|
||||
return state
|
||||
})
|
||||
return screen
|
||||
},
|
||||
save: async screen => {
|
||||
const creatingNewScreen = screen._id === undefined
|
||||
const response = await api.post(`/api/screens`, screen)
|
||||
if (response.status !== 200) {
|
||||
return
|
||||
}
|
||||
screen = await response.json()
|
||||
await store.actions.routing.fetch()
|
||||
|
||||
const savedScreen = await API.saveScreen(screen)
|
||||
store.update(state => {
|
||||
const foundScreen = state.screens.findIndex(
|
||||
el => el._id === screen._id
|
||||
)
|
||||
if (foundScreen !== -1) {
|
||||
state.screens.splice(foundScreen, 1)
|
||||
const idx = state.screens.findIndex(x => x._id === savedScreen._id)
|
||||
if (idx !== -1) {
|
||||
state.screens.splice(idx, 1, savedScreen)
|
||||
} else {
|
||||
state.screens.push(savedScreen)
|
||||
}
|
||||
state.screens.push(screen)
|
||||
return state
|
||||
})
|
||||
|
||||
if (creatingNewScreen) {
|
||||
store.actions.screens.select(screen._id)
|
||||
}
|
||||
// Refresh routes
|
||||
await store.actions.routing.fetch()
|
||||
|
||||
return screen
|
||||
// Select the new screen if creating a new one
|
||||
if (creatingNewScreen) {
|
||||
store.actions.screens.select(savedScreen._id)
|
||||
}
|
||||
return savedScreen
|
||||
},
|
||||
delete: async screens => {
|
||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||
|
||||
const screenDeletePromises = []
|
||||
// Build array of promises to speed up bulk deletions
|
||||
const promises = []
|
||||
screensToDelete.forEach(screen => {
|
||||
// Delete the screen
|
||||
promises.push(
|
||||
API.deleteScreen({
|
||||
screenId: screen._id,
|
||||
screenRev: screen._rev,
|
||||
})
|
||||
)
|
||||
// Remove links to this screen
|
||||
promises.push(
|
||||
store.actions.components.links.delete(
|
||||
screen.routing.route,
|
||||
screen.props._instanceName
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
const deletedIds = screensToDelete.map(screen => screen._id)
|
||||
store.update(state => {
|
||||
for (let screenToDelete of screensToDelete) {
|
||||
state.screens = state.screens.filter(
|
||||
screen => screen._id !== screenToDelete._id
|
||||
)
|
||||
screenDeletePromises.push(
|
||||
api.delete(
|
||||
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
|
||||
)
|
||||
)
|
||||
if (screenToDelete._id === state.selectedScreenId) {
|
||||
state.selectedScreenId = null
|
||||
}
|
||||
//remove the link for this screen
|
||||
screenDeletePromises.push(
|
||||
store.actions.components.links.delete(
|
||||
screenToDelete.routing.route,
|
||||
screenToDelete.props._instanceName
|
||||
)
|
||||
)
|
||||
// Remove deleted screens from state
|
||||
state.screens = state.screens.filter(screen => {
|
||||
return !deletedIds.includes(screen._id)
|
||||
})
|
||||
// Deselect the current screen if it was deleted
|
||||
if (deletedIds.includes(state.selectedScreenId)) {
|
||||
state.selectedScreenId = null
|
||||
}
|
||||
return state
|
||||
})
|
||||
await Promise.all(screenDeletePromises)
|
||||
|
||||
// Refresh routes
|
||||
await store.actions.routing.fetch()
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
saveSelected: async () => {
|
||||
const state = get(store)
|
||||
const selectedAsset = get(currentAsset)
|
||||
|
||||
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) {
|
||||
await store.actions.screens.save(selectedAsset)
|
||||
return await store.actions.screens.save(selectedAsset)
|
||||
} else {
|
||||
await store.actions.layouts.save(selectedAsset)
|
||||
return await store.actions.layouts.save(selectedAsset)
|
||||
}
|
||||
},
|
||||
setDevice: device => {
|
||||
|
@ -271,25 +253,13 @@ export const getFrontendStore = () => {
|
|||
})
|
||||
},
|
||||
save: async layout => {
|
||||
const layoutToSave = cloneDeep(layout)
|
||||
const creatingNewLayout = layoutToSave._id === undefined
|
||||
const response = await api.post(`/api/layouts`, layoutToSave)
|
||||
const savedLayout = await response.json()
|
||||
|
||||
// Abort if saving failed
|
||||
if (response.status !== 200) {
|
||||
return
|
||||
}
|
||||
|
||||
const creatingNewLayout = layout._id === undefined
|
||||
const savedLayout = await API.saveLayout(layout)
|
||||
store.update(state => {
|
||||
const layoutIdx = state.layouts.findIndex(
|
||||
stateLayout => stateLayout._id === savedLayout._id
|
||||
)
|
||||
if (layoutIdx >= 0) {
|
||||
// update existing layout
|
||||
state.layouts.splice(layoutIdx, 1, savedLayout)
|
||||
const idx = state.layouts.findIndex(x => x._id === savedLayout._id)
|
||||
if (idx !== -1) {
|
||||
state.layouts.splice(idx, 1, savedLayout)
|
||||
} else {
|
||||
// save new layout
|
||||
state.layouts.push(savedLayout)
|
||||
}
|
||||
return state
|
||||
|
@ -299,7 +269,6 @@ export const getFrontendStore = () => {
|
|||
if (creatingNewLayout) {
|
||||
store.actions.layouts.select(savedLayout._id)
|
||||
}
|
||||
|
||||
return savedLayout
|
||||
},
|
||||
find: layoutId => {
|
||||
|
@ -309,21 +278,20 @@ export const getFrontendStore = () => {
|
|||
const storeContents = get(store)
|
||||
return storeContents.layouts.find(layout => layout._id === layoutId)
|
||||
},
|
||||
delete: async layoutToDelete => {
|
||||
const response = await api.delete(
|
||||
`/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}`
|
||||
)
|
||||
if (response.status !== 200) {
|
||||
const json = await response.json()
|
||||
throw new Error(json.message)
|
||||
delete: async layout => {
|
||||
if (!layout?._id) {
|
||||
return
|
||||
}
|
||||
await API.deleteLayout({
|
||||
layoutId: layout._id,
|
||||
layoutRev: layout._rev,
|
||||
})
|
||||
store.update(state => {
|
||||
state.layouts = state.layouts.filter(
|
||||
layout => layout._id !== layoutToDelete._id
|
||||
)
|
||||
if (layoutToDelete._id === state.selectedLayoutId) {
|
||||
// Select main layout if we deleted the selected layout
|
||||
if (layout._id === state.selectedLayoutId) {
|
||||
state.selectedLayoutId = get(mainLayout)._id
|
||||
}
|
||||
state.layouts = state.layouts.filter(x => x._id !== layout._id)
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
@ -398,7 +366,7 @@ export const getFrontendStore = () => {
|
|||
}
|
||||
|
||||
return {
|
||||
_id: uuid(),
|
||||
_id: Helpers.uuid(),
|
||||
_component: definition.component,
|
||||
_styles: { normal: {}, hover: {}, active: {} },
|
||||
_instanceName: `New ${definition.name}`,
|
||||
|
@ -415,16 +383,12 @@ export const getFrontendStore = () => {
|
|||
componentName,
|
||||
presetProps
|
||||
)
|
||||
if (!componentInstance) {
|
||||
if (!componentInstance || !asset) {
|
||||
return
|
||||
}
|
||||
|
||||
// Find parent node to attach this component to
|
||||
let parentComponent
|
||||
|
||||
if (!asset) {
|
||||
return
|
||||
}
|
||||
if (selected) {
|
||||
// Use current screen or layout as parent if no component is selected
|
||||
const definition = store.actions.components.getDefinition(
|
||||
|
@ -552,7 +516,7 @@ export const getFrontendStore = () => {
|
|||
if (!component) {
|
||||
return
|
||||
}
|
||||
component._id = uuid()
|
||||
component._id = Helpers.uuid()
|
||||
component._children?.forEach(randomizeIds)
|
||||
}
|
||||
randomizeIds(componentToPaste)
|
||||
|
@ -606,11 +570,6 @@ export const getFrontendStore = () => {
|
|||
selected._styles.custom = style
|
||||
await store.actions.preview.saveSelected()
|
||||
},
|
||||
resetStyles: async () => {
|
||||
const selected = get(selectedComponent)
|
||||
selected._styles = { normal: {}, hover: {}, active: {} }
|
||||
await store.actions.preview.saveSelected()
|
||||
},
|
||||
updateConditions: async conditions => {
|
||||
const selected = get(selectedComponent)
|
||||
selected._conditions = conditions
|
||||
|
@ -665,7 +624,7 @@ export const getFrontendStore = () => {
|
|||
newLink = cloneDeep(nav._children[0])
|
||||
|
||||
// Set our new props
|
||||
newLink._id = uuid()
|
||||
newLink._id = Helpers.uuid()
|
||||
newLink._instanceName = `${title} Link`
|
||||
newLink.url = url
|
||||
newLink.text = title
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { generate } from "shortid"
|
||||
|
||||
export const notificationStore = writable({
|
||||
notifications: [],
|
||||
})
|
||||
|
||||
export function send(message, type = "default") {
|
||||
notificationStore.update(state => {
|
||||
state.notifications = [
|
||||
...state.notifications,
|
||||
{ id: generate(), type, message },
|
||||
]
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
export const notifier = {
|
||||
danger: msg => send(msg, "danger"),
|
||||
warning: msg => send(msg, "warning"),
|
||||
info: msg => send(msg, "info"),
|
||||
success: msg => send(msg, "success"),
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { uuid } from "builderStore/uuid"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { BaseStructure } from "./BaseStructure"
|
||||
|
||||
export class Component extends BaseStructure {
|
||||
|
@ -6,7 +6,7 @@ export class Component extends BaseStructure {
|
|||
super(false)
|
||||
this._children = []
|
||||
this._json = {
|
||||
_id: uuid(),
|
||||
_id: Helpers.uuid(),
|
||||
_component: name,
|
||||
_styles: {
|
||||
normal: {},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseStructure } from "./BaseStructure"
|
||||
import { uuid } from "builderStore/uuid"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
||||
export class Screen extends BaseStructure {
|
||||
constructor() {
|
||||
|
@ -7,7 +7,7 @@ export class Screen extends BaseStructure {
|
|||
this._json = {
|
||||
layoutId: "layout_private_master",
|
||||
props: {
|
||||
_id: uuid(),
|
||||
_id: Helpers.uuid(),
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: {
|
||||
normal: {},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { localStorageStore } from "./localStorage"
|
||||
import { createLocalStorageStore } from "@budibase/frontend-core"
|
||||
|
||||
export const getThemeStore = () => {
|
||||
const themeElement = document.documentElement
|
||||
|
@ -6,7 +6,7 @@ export const getThemeStore = () => {
|
|||
theme: "darkest",
|
||||
options: ["lightest", "light", "dark", "darkest"],
|
||||
}
|
||||
const store = localStorageStore("bb-theme", initialValue)
|
||||
const store = createLocalStorageStore("bb-theme", initialValue)
|
||||
|
||||
// Update theme class when store changes
|
||||
store.subscribe(state => {
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
export function uuid() {
|
||||
// always want to make this start with a letter, as this makes it
|
||||
// easier to use with template string bindings in the client
|
||||
return "cxxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, c => {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
Body,
|
||||
Icon,
|
||||
Tooltip,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { automationStore } from "builderStore"
|
||||
import { admin } from "stores/portal"
|
||||
|
@ -47,15 +48,19 @@
|
|||
}
|
||||
|
||||
async function addBlockToAutomation() {
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock(
|
||||
"ACTION",
|
||||
actionVal.stepId,
|
||||
actionVal
|
||||
)
|
||||
automationStore.actions.addBlockToAutomation(newBlock, blockIdx + 1)
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
try {
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock(
|
||||
"ACTION",
|
||||
actionVal.stepId,
|
||||
actionVal
|
||||
)
|
||||
automationStore.actions.addBlockToAutomation(newBlock, blockIdx + 1)
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving automation")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -30,26 +30,13 @@
|
|||
}
|
||||
|
||||
async function deleteAutomation() {
|
||||
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}.`
|
||||
try {
|
||||
await automationStore.actions.delete(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting automation")
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -85,7 +72,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 +88,7 @@
|
|||
</ConfirmDialog>
|
||||
|
||||
<Modal bind:this={testDataModal} width="30%">
|
||||
<TestDataModal {testAutomation} />
|
||||
<TestDataModal />
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
Button,
|
||||
StatusLight,
|
||||
ActionButton,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||
|
@ -54,10 +55,14 @@
|
|||
).every(x => block?.inputs[x])
|
||||
|
||||
async function deleteStep() {
|
||||
automationStore.actions.deleteAutomationBlock(block)
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
try {
|
||||
automationStore.actions.deleteAutomationBlock(block)
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving notification")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<script>
|
||||
import { ModalContent, Tabs, Tab, TextArea, Label } from "@budibase/bbui"
|
||||
import {
|
||||
ModalContent,
|
||||
Tabs,
|
||||
Tab,
|
||||
TextArea,
|
||||
Label,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { automationStore } from "builderStore"
|
||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
|
@ -37,6 +44,17 @@
|
|||
failedParse = "Invalid JSON"
|
||||
}
|
||||
}
|
||||
|
||||
const testAutomation = async () => {
|
||||
try {
|
||||
await automationStore.actions.test(
|
||||
$automationStore.selectedAutomation?.automation,
|
||||
testData
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error("Error testing notification")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
|
@ -44,12 +62,7 @@
|
|||
confirmText="Test"
|
||||
showConfirmButton={true}
|
||||
disabled={isError}
|
||||
onConfirm={() => {
|
||||
automationStore.actions.test(
|
||||
$automationStore.selectedAutomation?.automation,
|
||||
testData
|
||||
)
|
||||
}}
|
||||
onConfirm={testAutomation}
|
||||
cancelText="Cancel"
|
||||
>
|
||||
<Tabs selected="Form" quiet
|
||||
|
|
|
@ -4,10 +4,16 @@
|
|||
import { automationStore } from "builderStore"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import EditAutomationPopover from "./EditAutomationPopover.svelte"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
|
||||
onMount(() => {
|
||||
automationStore.actions.fetch()
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await automationStore.actions.fetch()
|
||||
} catch (error) {
|
||||
notifications.error("Error getting automations list")
|
||||
}
|
||||
})
|
||||
|
||||
function selectAutomation(automation) {
|
||||
|
|
|
@ -24,29 +24,33 @@
|
|||
nameTouched && !name ? "Please specify a name for the automation." : null
|
||||
|
||||
async function createAutomation() {
|
||||
await automationStore.actions.create({
|
||||
name,
|
||||
instanceId,
|
||||
})
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock(
|
||||
"TRIGGER",
|
||||
triggerVal.stepId,
|
||||
triggerVal
|
||||
)
|
||||
try {
|
||||
await automationStore.actions.create({
|
||||
name,
|
||||
instanceId,
|
||||
})
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock(
|
||||
"TRIGGER",
|
||||
triggerVal.stepId,
|
||||
triggerVal
|
||||
)
|
||||
|
||||
automationStore.actions.addBlockToAutomation(newBlock)
|
||||
if (triggerVal.stepId === "WEBHOOK") {
|
||||
webhookModal.show
|
||||
automationStore.actions.addBlockToAutomation(newBlock)
|
||||
if (triggerVal.stepId === "WEBHOOK") {
|
||||
webhookModal.show
|
||||
}
|
||||
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
|
||||
notifications.success(`Automation ${name} created`)
|
||||
|
||||
$goto(`./${$automationStore.selectedAutomation.automation._id}`)
|
||||
analytics.captureEvent(Events.AUTOMATION.CREATED, { name })
|
||||
} catch (error) {
|
||||
notifications.error("Error creating automation")
|
||||
}
|
||||
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
|
||||
notifications.success(`Automation ${name} created.`)
|
||||
|
||||
$goto(`./${$automationStore.selectedAutomation.automation._id}`)
|
||||
analytics.captureEvent(Events.AUTOMATION.CREATED, { name })
|
||||
}
|
||||
$: triggers = Object.entries($automationStore.blockDefinitions.TRIGGER)
|
||||
|
||||
|
|
|
@ -11,9 +11,13 @@
|
|||
let updateAutomationDialog
|
||||
|
||||
async function deleteAutomation() {
|
||||
await automationStore.actions.delete(automation)
|
||||
notifications.success("Automation deleted.")
|
||||
$goto("../automate")
|
||||
try {
|
||||
await automationStore.actions.delete(automation)
|
||||
notifications.success("Automation deleted successfully")
|
||||
$goto("../automate")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting automation")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -20,14 +20,18 @@
|
|||
}
|
||||
|
||||
async function saveAutomation() {
|
||||
const updatedAutomation = {
|
||||
...automation,
|
||||
name,
|
||||
try {
|
||||
const updatedAutomation = {
|
||||
...automation,
|
||||
name,
|
||||
}
|
||||
await automationStore.actions.save(updatedAutomation)
|
||||
notifications.success(`Automation ${name} updated successfully`)
|
||||
analytics.captureEvent(Events.AUTOMATION.SAVED, { name })
|
||||
hide()
|
||||
} catch (error) {
|
||||
notifications.error("Error saving automation")
|
||||
}
|
||||
await automationStore.actions.save(updatedAutomation)
|
||||
notifications.success(`Automation ${name} updated successfully.`)
|
||||
analytics.captureEvent(Events.AUTOMATION.SAVED, { name })
|
||||
hide()
|
||||
}
|
||||
|
||||
function checkValid(evt) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
Drawer,
|
||||
Modal,
|
||||
Detail,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||
|
||||
|
@ -28,7 +29,7 @@
|
|||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||
import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte"
|
||||
// need the client lucene builder to convert to the structure API expects
|
||||
import { buildLuceneQuery } from "helpers/lucene"
|
||||
import { LuceneUtils } from "@budibase/frontend-core"
|
||||
|
||||
export let block
|
||||
export let testData
|
||||
|
@ -54,28 +55,32 @@
|
|||
$: schemaFields = table ? Object.values(table.schema) : []
|
||||
|
||||
const onChange = debounce(async function (e, key) {
|
||||
if (isTestModal) {
|
||||
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
|
||||
if (stepId === "WEBHOOK") {
|
||||
try {
|
||||
if (isTestModal) {
|
||||
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
|
||||
if (stepId === "WEBHOOK") {
|
||||
automationStore.actions.addTestDataToAutomation({
|
||||
body: {
|
||||
[key]: e.detail,
|
||||
...$automationStore.selectedAutomation.automation.testData.body,
|
||||
},
|
||||
})
|
||||
}
|
||||
automationStore.actions.addTestDataToAutomation({
|
||||
body: {
|
||||
[key]: e.detail,
|
||||
...$automationStore.selectedAutomation.automation.testData.body,
|
||||
},
|
||||
[key]: e.detail,
|
||||
})
|
||||
testData[key] = e.detail
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
} else {
|
||||
block.inputs[key] = e.detail
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
}
|
||||
automationStore.actions.addTestDataToAutomation({
|
||||
[key]: e.detail,
|
||||
})
|
||||
testData[key] = e.detail
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
} else {
|
||||
block.inputs[key] = e.detail
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving automation")
|
||||
}
|
||||
}, 800)
|
||||
|
||||
|
@ -131,7 +136,7 @@
|
|||
}
|
||||
|
||||
function saveFilters(key) {
|
||||
const filters = buildLuceneQuery(tempFilters)
|
||||
const filters = LuceneUtils.buildLuceneQuery(tempFilters)
|
||||
const defKey = `${key}-def`
|
||||
inputData[key] = filters
|
||||
inputData[defKey] = tempFilters
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { Icon, notifications } from "@budibase/bbui"
|
||||
import { automationStore } from "builderStore"
|
||||
import WebhookDisplay from "./WebhookDisplay.svelte"
|
||||
import { ModalContent } from "@budibase/bbui"
|
||||
|
@ -16,15 +16,24 @@
|
|||
onMount(async () => {
|
||||
if (!automation?.definition?.trigger?.inputs.schemaUrl) {
|
||||
// save the automation initially
|
||||
await automationStore.actions.save(automation)
|
||||
try {
|
||||
await automationStore.actions.save(automation)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving automation")
|
||||
}
|
||||
}
|
||||
interval = setInterval(async () => {
|
||||
await automationStore.actions.fetch()
|
||||
const outputs = automation?.definition?.trigger.schema.outputs?.properties
|
||||
// always one prop for the "body"
|
||||
if (Object.keys(outputs).length > 1) {
|
||||
propCount = Object.keys(outputs).length - 1
|
||||
finished = true
|
||||
try {
|
||||
await automationStore.actions.fetch()
|
||||
const outputs =
|
||||
automation?.definition?.trigger.schema.outputs?.properties
|
||||
// always one prop for the "body"
|
||||
if (Object.keys(outputs).length > 1) {
|
||||
propCount = Object.keys(outputs).length - 1
|
||||
finished = true
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error getting automations list")
|
||||
}
|
||||
}, POLL_RATE_MS)
|
||||
schemaURL = automation?.definition?.trigger?.inputs.schemaUrl
|
||||
|
|
|
@ -14,18 +14,19 @@
|
|||
import Table from "./Table.svelte"
|
||||
import { TableNames } from "constants"
|
||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||
import { fetchTableData } from "helpers/fetchTableData"
|
||||
import { Pagination } from "@budibase/bbui"
|
||||
import { fetchData } from "@budibase/frontend-core"
|
||||
import { API } from "api"
|
||||
|
||||
let hideAutocolumns = true
|
||||
|
||||
$: isUsersTable = $tables.selected?._id === TableNames.USERS
|
||||
$: type = $tables.selected?.type
|
||||
$: isInternal = type !== "external"
|
||||
$: schema = $tables.selected?.schema
|
||||
$: enrichedSchema = enrichSchema($tables.selected?.schema)
|
||||
$: id = $tables.selected?._id
|
||||
$: search = searchTable(id)
|
||||
$: columnOptions = Object.keys($search.schema || {})
|
||||
$: fetch = createFetch(id)
|
||||
|
||||
const enrichSchema = schema => {
|
||||
let tempSchema = { ...schema }
|
||||
|
@ -47,18 +48,24 @@
|
|||
return tempSchema
|
||||
}
|
||||
// Fetches new data whenever the table changes
|
||||
const searchTable = tableId => {
|
||||
return fetchTableData({
|
||||
tableId,
|
||||
schema,
|
||||
limit: 10,
|
||||
paginate: true,
|
||||
const createFetch = tableId => {
|
||||
return fetchData({
|
||||
API,
|
||||
datasource: {
|
||||
tableId,
|
||||
type: "table",
|
||||
},
|
||||
options: {
|
||||
schema,
|
||||
limit: 10,
|
||||
paginate: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch data whenever sorting option changes
|
||||
const onSort = e => {
|
||||
search.update({
|
||||
fetch.update({
|
||||
sortColumn: e.detail.column,
|
||||
sortOrder: e.detail.order,
|
||||
})
|
||||
|
@ -66,22 +73,20 @@
|
|||
|
||||
// Fetch data whenever filters change
|
||||
const onFilter = e => {
|
||||
search.update({
|
||||
filters: e.detail,
|
||||
fetch.update({
|
||||
filter: e.detail,
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch data whenever schema changes
|
||||
const onUpdateColumns = () => {
|
||||
search.update({
|
||||
schema,
|
||||
})
|
||||
fetch.refresh()
|
||||
}
|
||||
|
||||
// Fetch data whenever rows are modified. Unfortunately we have to lose
|
||||
// our pagination place, as our bookmarks will have shifted.
|
||||
const onUpdateRows = () => {
|
||||
search.update()
|
||||
fetch.refresh()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -91,9 +96,9 @@
|
|||
schema={enrichedSchema}
|
||||
{type}
|
||||
tableId={id}
|
||||
data={$search.rows}
|
||||
data={$fetch.rows}
|
||||
bind:hideAutocolumns
|
||||
loading={$search.loading}
|
||||
loading={$fetch.loading}
|
||||
on:sort={onSort}
|
||||
allowEditing
|
||||
disableSorting
|
||||
|
@ -138,11 +143,11 @@
|
|||
<div in:fade={{ delay: 200, duration: 100 }}>
|
||||
<div class="pagination">
|
||||
<Pagination
|
||||
page={$search.pageNumber + 1}
|
||||
hasPrevPage={$search.hasPrevPage}
|
||||
hasNextPage={$search.hasNextPage}
|
||||
goToPrevPage={$search.loading ? null : search.prevPage}
|
||||
goToNextPage={$search.loading ? null : search.nextPage}
|
||||
page={$fetch.pageNumber + 1}
|
||||
hasPrevPage={$fetch.hasPrevPage}
|
||||
hasNextPage={$fetch.hasNextPage}
|
||||
goToPrevPage={$fetch.loading ? null : fetch.prevPage}
|
||||
goToNextPage={$fetch.loading ? null : fetch.nextPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import Table from "./Table.svelte"
|
||||
import { tables } from "stores/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export let tableId
|
||||
export let rowId
|
||||
|
@ -27,9 +28,15 @@
|
|||
}
|
||||
|
||||
async function fetchData(tableId, rowId) {
|
||||
const QUERY_VIEW_URL = `/api/${tableId}/${rowId}/enrich`
|
||||
const response = await api.get(QUERY_VIEW_URL)
|
||||
row = await response.json()
|
||||
try {
|
||||
row = await API.fetchRelationshipData({
|
||||
tableId,
|
||||
rowId,
|
||||
})
|
||||
} catch (error) {
|
||||
row = null
|
||||
notifications.error("Error fetching relationship data")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { fade } from "svelte/transition"
|
||||
import { goto, params } from "@roxi/routify"
|
||||
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
|
||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||
|
@ -88,12 +88,17 @@
|
|||
}
|
||||
|
||||
const deleteRows = async () => {
|
||||
await api.delete(`/api/${tableId}/rows`, {
|
||||
rows: selectedRows,
|
||||
})
|
||||
data = data.filter(row => !selectedRows.includes(row))
|
||||
notifications.success(`Successfully deleted ${selectedRows.length} rows`)
|
||||
selectedRows = []
|
||||
try {
|
||||
await API.deleteRows({
|
||||
tableId,
|
||||
rows: selectedRows,
|
||||
})
|
||||
data = data.filter(row => !selectedRows.includes(row))
|
||||
notifications.success(`Successfully deleted ${selectedRows.length} rows`)
|
||||
selectedRows = []
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting rows")
|
||||
}
|
||||
}
|
||||
|
||||
const editRow = row => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { tables } from "stores/backend"
|
||||
|
||||
import Table from "./Table.svelte"
|
||||
|
@ -9,6 +9,7 @@
|
|||
import ExportButton from "./buttons/ExportButton.svelte"
|
||||
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
|
||||
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export let view = {}
|
||||
|
||||
|
@ -20,33 +21,31 @@
|
|||
$: name = view.name
|
||||
|
||||
// Fetch rows for specified view
|
||||
$: {
|
||||
loading = true
|
||||
fetchViewData(name, view.field, view.groupBy, view.calculation)
|
||||
}
|
||||
$: fetchViewData(name, view.field, view.groupBy, view.calculation)
|
||||
|
||||
async function fetchViewData(name, field, groupBy, calculation) {
|
||||
loading = true
|
||||
const _tables = $tables.list
|
||||
const allTableViews = _tables.map(table => table.views)
|
||||
const thisView = allTableViews.filter(
|
||||
views => views != null && views[name] != null
|
||||
)[0]
|
||||
|
||||
// don't fetch view data if the view no longer exists
|
||||
// Don't fetch view data if the view no longer exists
|
||||
if (!thisView) {
|
||||
loading = false
|
||||
return
|
||||
}
|
||||
const params = new URLSearchParams()
|
||||
if (calculation) {
|
||||
params.set("field", field)
|
||||
params.set("calculation", calculation)
|
||||
try {
|
||||
data = await API.fetchViewData({
|
||||
name,
|
||||
calculation,
|
||||
field,
|
||||
groupBy,
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error fetching view data")
|
||||
}
|
||||
if (groupBy) {
|
||||
params.set("group", groupBy)
|
||||
}
|
||||
const QUERY_VIEW_URL = `/api/views/${name}?${params}`
|
||||
const response = await api.get(QUERY_VIEW_URL)
|
||||
data = await response.json()
|
||||
loading = false
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -38,9 +38,13 @@
|
|||
})
|
||||
|
||||
function saveView() {
|
||||
views.save(view)
|
||||
notifications.success(`View ${view.name} saved.`)
|
||||
analytics.captureEvent(Events.VIEW.ADDED_CALCULATE, { field: view.field })
|
||||
try {
|
||||
views.save(view)
|
||||
notifications.success(`View ${view.name} saved`)
|
||||
analytics.captureEvent(Events.VIEW.ADDED_CALCULATE, { field: view.field })
|
||||
} catch (error) {
|
||||
notifications.error("Error saving view")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@
|
|||
})
|
||||
dispatch("updatecolumns")
|
||||
} catch (err) {
|
||||
notifications.error(err)
|
||||
notifications.error("Error saving column")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,17 +133,21 @@
|
|||
}
|
||||
|
||||
function deleteColumn() {
|
||||
field.name = deleteColName
|
||||
if (field.name === $tables.selected.primaryDisplay) {
|
||||
notifications.error("You cannot delete the display column")
|
||||
} else {
|
||||
tables.deleteField(field)
|
||||
notifications.success(`Column ${field.name} deleted.`)
|
||||
confirmDeleteDialog.hide()
|
||||
hide()
|
||||
deletion = false
|
||||
try {
|
||||
field.name = deleteColName
|
||||
if (field.name === $tables.selected.primaryDisplay) {
|
||||
notifications.error("You cannot delete the display column")
|
||||
} else {
|
||||
tables.deleteField(field)
|
||||
notifications.success(`Column ${field.name} deleted.`)
|
||||
confirmDeleteDialog.hide()
|
||||
hide()
|
||||
deletion = false
|
||||
dispatch("updatecolumns")
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting column")
|
||||
}
|
||||
dispatch("updatecolumns")
|
||||
}
|
||||
|
||||
function handleTypeChange(event) {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -12,17 +12,21 @@
|
|||
|
||||
function saveView() {
|
||||
if (views.includes(name)) {
|
||||
notifications.error(`View exists with name ${name}.`)
|
||||
notifications.error(`View exists with name ${name}`)
|
||||
return
|
||||
}
|
||||
viewsStore.save({
|
||||
name,
|
||||
tableId: $tables.selected._id,
|
||||
field,
|
||||
})
|
||||
notifications.success(`View ${name} created`)
|
||||
analytics.captureEvent(Events.VIEW.CREATED, { name })
|
||||
$goto(`../../view/${name}`)
|
||||
try {
|
||||
viewsStore.save({
|
||||
name,
|
||||
tableId: $tables.selected._id,
|
||||
field,
|
||||
})
|
||||
notifications.success(`View ${name} created`)
|
||||
analytics.captureEvent(Events.VIEW.CREATED, { name })
|
||||
$goto(`../../view/${name}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error creating view")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { ModalContent, Select, Input, Button } from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
import { roles } from "stores/backend"
|
||||
|
@ -24,8 +24,12 @@
|
|||
!builtInRoles.includes(selectedRole.name)
|
||||
|
||||
const fetchBasePermissions = async () => {
|
||||
const permissionsResponse = await api.get("/api/permission/builtin")
|
||||
basePermissions = await permissionsResponse.json()
|
||||
try {
|
||||
basePermissions = await API.getBasePermissions()
|
||||
} catch (error) {
|
||||
notifications.error("Error fetching base permission options")
|
||||
basePermissions = []
|
||||
}
|
||||
}
|
||||
|
||||
// Changes the selected role
|
||||
|
@ -68,23 +72,23 @@
|
|||
}
|
||||
|
||||
// Save/create the role
|
||||
const response = await roles.save(selectedRole)
|
||||
if (response.status === 200) {
|
||||
notifications.success("Role saved successfully.")
|
||||
} else {
|
||||
notifications.error("Error saving role.")
|
||||
try {
|
||||
await roles.save(selectedRole)
|
||||
notifications.success("Role saved successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error saving role")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes the selected role
|
||||
const deleteRole = async () => {
|
||||
const response = await roles.delete(selectedRole)
|
||||
if (response.status === 200) {
|
||||
try {
|
||||
await roles.delete(selectedRole)
|
||||
changeRole()
|
||||
notifications.success("Role deleted successfully.")
|
||||
} else {
|
||||
notifications.error("Error deleting role.")
|
||||
notifications.success("Role deleted successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting role")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { Select, ModalContent, notifications } from "@budibase/bbui"
|
||||
import download from "downloadjs"
|
||||
import { get } from "builderStore/api"
|
||||
import { API } from "api"
|
||||
|
||||
const FORMATS = [
|
||||
{
|
||||
|
@ -19,17 +19,14 @@
|
|||
let exportFormat = FORMATS[0].key
|
||||
|
||||
async function exportView() {
|
||||
const uri = encodeURIComponent(view)
|
||||
const response = await get(
|
||||
`/api/views/export?view=${uri}&format=${exportFormat}`
|
||||
)
|
||||
if (response.status === 200) {
|
||||
const data = await response.text()
|
||||
try {
|
||||
const data = await API.exportView({
|
||||
viewName: view,
|
||||
format: exportFormat,
|
||||
})
|
||||
download(data, `export.${exportFormat}`)
|
||||
} else {
|
||||
notifications.error(
|
||||
`Unable to export ${exportFormat.toUpperCase()} data.`
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error(`Unable to export ${exportFormat.toUpperCase()} data`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -72,11 +72,15 @@
|
|||
$: schema = viewTable && viewTable.schema ? viewTable.schema : {}
|
||||
|
||||
function saveView() {
|
||||
views.save(view)
|
||||
notifications.success(`View ${view.name} saved.`)
|
||||
analytics.captureEvent(Events.VIEW.ADDED_FILTER, {
|
||||
filters: JSON.stringify(view.filters),
|
||||
})
|
||||
try {
|
||||
views.save(view)
|
||||
notifications.success(`View ${view.name} saved`)
|
||||
analytics.captureEvent(Events.VIEW.ADDED_FILTER, {
|
||||
filters: JSON.stringify(view.filters),
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error saving view")
|
||||
}
|
||||
}
|
||||
|
||||
function removeFilter(idx) {
|
||||
|
@ -158,7 +162,7 @@
|
|||
<Select
|
||||
bind:value={filter.value}
|
||||
options={fieldOptions(filter.key)}
|
||||
getOptionLabel={x => x.toString()}
|
||||
getOptionLabel={x => x?.toString() || ""}
|
||||
/>
|
||||
{:else if filter.key && isDate(filter.key)}
|
||||
<DatePicker
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
.map(([key]) => key)
|
||||
|
||||
function saveView() {
|
||||
views.save(view)
|
||||
notifications.success(`View ${view.name} saved.`)
|
||||
try {
|
||||
views.save(view)
|
||||
notifications.success(`View ${view.name} saved`)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving view")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<script>
|
||||
import { ModalContent, Label, notifications, Body } from "@budibase/bbui"
|
||||
import {
|
||||
ModalContent,
|
||||
Label,
|
||||
notifications,
|
||||
Body,
|
||||
Layout,
|
||||
} from "@budibase/bbui"
|
||||
import TableDataImport from "../../TableNavigator/TableDataImport.svelte"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -12,15 +18,17 @@
|
|||
$: valid = dataImport?.csvString != null && dataImport?.valid
|
||||
|
||||
async function importData() {
|
||||
const response = await api.post(`/api/tables/${tableId}/import`, {
|
||||
dataImport,
|
||||
})
|
||||
if (response.status !== 200) {
|
||||
const error = await response.text()
|
||||
notifications.error(`Unable to import data - ${error}`)
|
||||
} else {
|
||||
notifications.success("Rows successfully imported.")
|
||||
try {
|
||||
await API.importTableData({
|
||||
tableId,
|
||||
data: dataImport,
|
||||
})
|
||||
notifications.success("Rows successfully imported")
|
||||
} catch (error) {
|
||||
notifications.error("Unable to import data")
|
||||
}
|
||||
|
||||
// Always refresh rows just to be sure
|
||||
dispatch("updaterows")
|
||||
}
|
||||
</script>
|
||||
|
@ -31,12 +39,14 @@
|
|||
onConfirm={importData}
|
||||
disabled={!valid}
|
||||
>
|
||||
<Body
|
||||
>Import rows to an existing table from a CSV. Only columns from the CSV
|
||||
which exist in the table will be imported.</Body
|
||||
>
|
||||
<Label grey extraSmall>CSV to import</Label>
|
||||
<TableDataImport bind:dataImport bind:existingTableId={tableId} />
|
||||
<Body size="S">
|
||||
Import rows to an existing table from a CSV. Only columns from the CSV which
|
||||
exist in the table will be imported.
|
||||
</Body>
|
||||
<Layout gap="XS" noPadding>
|
||||
<Label grey extraSmall>CSV to import</Label>
|
||||
<TableDataImport bind:dataImport bind:existingTableId={tableId} />
|
||||
</Layout>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -14,15 +14,19 @@
|
|||
export let permissions
|
||||
|
||||
async function changePermission(level, role) {
|
||||
await permissionsStore.save({
|
||||
level,
|
||||
role,
|
||||
resource: resourceId,
|
||||
})
|
||||
try {
|
||||
await permissionsStore.save({
|
||||
level,
|
||||
role,
|
||||
resource: resourceId,
|
||||
})
|
||||
|
||||
// Show updated permissions in UI: REMOVE
|
||||
permissions = await permissionsStore.forResource(resourceId)
|
||||
notifications.success("Updated permissions.")
|
||||
// Show updated permissions in UI: REMOVE
|
||||
permissions = await permissionsStore.forResource(resourceId)
|
||||
notifications.success("Updated permissions")
|
||||
} catch (error) {
|
||||
notifications.error("Error updating permissions")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
||||
import { customQueryIconText, customQueryIconColor } from "helpers/data/utils"
|
||||
import ICONS from "./icons"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
let openDataSources = []
|
||||
$: enrichedDataSources = Array.isArray($datasources.list)
|
||||
|
@ -63,9 +64,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
datasources.fetch()
|
||||
queries.fetch()
|
||||
onMount(async () => {
|
||||
try {
|
||||
await datasources.fetch()
|
||||
await queries.fetch()
|
||||
} catch (error) {
|
||||
notifications.error("Error fetching datasources and queries")
|
||||
}
|
||||
})
|
||||
|
||||
const containsActiveEntity = datasource => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { ModalContent, Body, Input } from "@budibase/bbui"
|
||||
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
||||
import { tables, datasources } from "stores/backend"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
|
@ -29,10 +29,14 @@
|
|||
}
|
||||
|
||||
async function saveTable() {
|
||||
submitted = true
|
||||
const table = await tables.save(buildDefaultTable(name, datasource._id))
|
||||
await datasources.fetch()
|
||||
$goto(`../../table/${table._id}`)
|
||||
try {
|
||||
submitted = true
|
||||
const table = await tables.save(buildDefaultTable(name, datasource._id))
|
||||
await datasources.fetch()
|
||||
$goto(`../../table/${table._id}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving table")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -90,8 +90,8 @@
|
|||
await datasources.updateSchema(datasource)
|
||||
notifications.success(`Datasource ${name} tables updated successfully.`)
|
||||
await tables.fetch()
|
||||
} catch (err) {
|
||||
notifications.error(`Error updating datasource schema: ${err}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error updating datasource schema")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { Body } from "@budibase/bbui"
|
||||
import { Body, notifications } from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import ICONS from "../icons"
|
||||
|
||||
export let integration = {}
|
||||
|
@ -9,14 +9,17 @@
|
|||
const INTERNAL = "BUDIBASE"
|
||||
|
||||
async function fetchIntegrations() {
|
||||
const response = await api.get("/api/integrations")
|
||||
const json = await response.json()
|
||||
|
||||
let otherIntegrations
|
||||
try {
|
||||
otherIntegrations = await API.getIntegrations()
|
||||
} catch (error) {
|
||||
otherIntegrations = {}
|
||||
notifications.error("Error getting integrations")
|
||||
}
|
||||
integrations = {
|
||||
[INTERNAL]: { datasource: {}, name: "INTERNAL/CSV" },
|
||||
...json,
|
||||
...otherIntegrations,
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
function selectIntegration(integrationType) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Table, Modal, Layout, ActionButton } from "@budibase/bbui"
|
||||
import AuthTypeRenderer from "./AuthTypeRenderer.svelte"
|
||||
import RestAuthenticationModal from "./RestAuthenticationModal.svelte"
|
||||
import { uuid } from "builderStore/uuid"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
||||
export let configs = []
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
return c
|
||||
})
|
||||
} else {
|
||||
config._id = uuid()
|
||||
config._id = Helpers.uuid()
|
||||
configs = [...configs, config]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<script>
|
||||
import { ModalContent, Modal, Body, Layout, Detail } from "@budibase/bbui"
|
||||
import {
|
||||
ModalContent,
|
||||
Modal,
|
||||
Body,
|
||||
Layout,
|
||||
Detail,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import ICONS from "../icons"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { IntegrationNames, IntegrationTypes } from "constants/backend"
|
||||
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
|
||||
import DatasourceConfigModal from "components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte"
|
||||
|
@ -12,7 +19,7 @@
|
|||
import ImportRestQueriesModal from "./ImportRestQueriesModal.svelte"
|
||||
|
||||
export let modal
|
||||
let integrations = []
|
||||
let integrations = {}
|
||||
let integration = {}
|
||||
let internalTableModal
|
||||
let externalDatasourceModal
|
||||
|
@ -57,22 +64,32 @@
|
|||
externalDatasourceModal.hide()
|
||||
internalTableModal.show()
|
||||
} else if (integration.type === IntegrationTypes.REST) {
|
||||
// skip modal for rest, create straight away
|
||||
const resp = await createRestDatasource(integration)
|
||||
$goto(`./datasource/${resp._id}`)
|
||||
try {
|
||||
// Skip modal for rest, create straight away
|
||||
const resp = await createRestDatasource(integration)
|
||||
$goto(`./datasource/${resp._id}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error creating datasource")
|
||||
}
|
||||
} else {
|
||||
externalDatasourceModal.show()
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchIntegrations() {
|
||||
const response = await api.get("/api/integrations")
|
||||
const json = await response.json()
|
||||
integrations = {
|
||||
let newIntegrations = {
|
||||
[IntegrationTypes.INTERNAL]: { datasource: {}, name: "INTERNAL/CSV" },
|
||||
...json,
|
||||
}
|
||||
return json
|
||||
try {
|
||||
const integrationList = await API.getIntegrations()
|
||||
newIntegrations = {
|
||||
...newIntegrations,
|
||||
...integrationList,
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error fetching integrations")
|
||||
}
|
||||
integrations = newIntegrations
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
$goto(`./datasource/${resp._id}`)
|
||||
notifications.success(`Datasource updated successfully.`)
|
||||
} catch (err) {
|
||||
notifications.error(`Error saving datasource: ${err}`)
|
||||
notifications.error("Error saving datasource")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,8 +79,8 @@
|
|||
})
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
notifications.error(`Error importing: ${err}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error importing queries")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,28 @@
|
|||
let updateDatasourceDialog
|
||||
|
||||
async function deleteDatasource() {
|
||||
let wasSelectedSource = $datasources.selected
|
||||
if (!wasSelectedSource && $queries.selected) {
|
||||
const queryId = $queries.selected
|
||||
wasSelectedSource = $datasources.list.find(ds =>
|
||||
queryId.includes(ds._id)
|
||||
)?._id
|
||||
}
|
||||
const wasSelectedTable = $tables.selected
|
||||
await datasources.delete(datasource)
|
||||
notifications.success("Datasource deleted")
|
||||
// navigate to first index page if the source you are deleting is selected
|
||||
const entities = Object.values(datasource?.entities || {})
|
||||
if (
|
||||
wasSelectedSource === datasource._id ||
|
||||
(entities &&
|
||||
entities.find(entity => entity._id === wasSelectedTable?._id))
|
||||
) {
|
||||
$goto("./datasource")
|
||||
try {
|
||||
let wasSelectedSource = $datasources.selected
|
||||
if (!wasSelectedSource && $queries.selected) {
|
||||
const queryId = $queries.selected
|
||||
wasSelectedSource = $datasources.list.find(ds =>
|
||||
queryId.includes(ds._id)
|
||||
)?._id
|
||||
}
|
||||
const wasSelectedTable = $tables.selected
|
||||
await datasources.delete(datasource)
|
||||
notifications.success("Datasource deleted")
|
||||
// Navigate to first index page if the source you are deleting is selected
|
||||
const entities = Object.values(datasource?.entities || {})
|
||||
if (
|
||||
wasSelectedSource === datasource._id ||
|
||||
(entities &&
|
||||
entities.find(entity => entity._id === wasSelectedTable?._id))
|
||||
) {
|
||||
$goto("./datasource")
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting datasource")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -10,26 +10,30 @@
|
|||
let confirmDeleteDialog
|
||||
|
||||
async function deleteQuery() {
|
||||
const wasSelectedQuery = $queries.selected
|
||||
// need to calculate this before the query is deleted
|
||||
const navigateToDatasource = wasSelectedQuery === query._id
|
||||
try {
|
||||
const wasSelectedQuery = $queries.selected
|
||||
// need to calculate this before the query is deleted
|
||||
const navigateToDatasource = wasSelectedQuery === query._id
|
||||
|
||||
await queries.delete(query)
|
||||
await datasources.fetch()
|
||||
await queries.delete(query)
|
||||
await datasources.fetch()
|
||||
|
||||
if (navigateToDatasource) {
|
||||
await datasources.select(query.datasourceId)
|
||||
$goto(`./datasource/${query.datasourceId}`)
|
||||
if (navigateToDatasource) {
|
||||
await datasources.select(query.datasourceId)
|
||||
$goto(`./datasource/${query.datasourceId}`)
|
||||
}
|
||||
notifications.success("Query deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting query")
|
||||
}
|
||||
notifications.success("Query deleted")
|
||||
}
|
||||
|
||||
async function duplicateQuery() {
|
||||
try {
|
||||
const newQuery = await queries.duplicate(query)
|
||||
onClickQuery(newQuery)
|
||||
} catch (e) {
|
||||
notifications.error(e.message)
|
||||
} catch (error) {
|
||||
notifications.error("Error duplicating query")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
Body,
|
||||
} from "@budibase/bbui"
|
||||
import { tables } from "stores/backend"
|
||||
import { uuid } from "builderStore/uuid"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
export let save
|
||||
|
@ -140,7 +140,7 @@
|
|||
const manyToMany =
|
||||
fromRelationship.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||
// main is simply used to know this is the side the user configured it from
|
||||
const id = uuid()
|
||||
const id = Helpers.uuid()
|
||||
if (!manyToMany) {
|
||||
delete fromRelationship.through
|
||||
delete toRelationship.through
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { Select, InlineAlert, notifications } from "@budibase/bbui"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
|
||||
const BYTES_IN_MB = 1000000
|
||||
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
|
||||
|
@ -50,28 +50,26 @@
|
|||
}
|
||||
|
||||
async function validateCSV() {
|
||||
const response = await api.post("/api/tables/csv/validate", {
|
||||
csvString,
|
||||
schema: schema || {},
|
||||
tableId: existingTableId,
|
||||
})
|
||||
try {
|
||||
const parseResult = await API.validateTableCSV({
|
||||
csvString,
|
||||
schema: schema || {},
|
||||
tableId: existingTableId,
|
||||
})
|
||||
schema = parseResult?.schema
|
||||
fields = Object.keys(schema || {}).filter(
|
||||
key => schema[key].type !== "omit"
|
||||
)
|
||||
|
||||
const parseResult = await response.json()
|
||||
schema = parseResult && parseResult.schema
|
||||
fields = Object.keys(schema || {}).filter(
|
||||
key => schema[key].type !== "omit"
|
||||
)
|
||||
// Check primary display is valid
|
||||
if (!primaryDisplay || fields.indexOf(primaryDisplay) === -1) {
|
||||
primaryDisplay = fields[0]
|
||||
}
|
||||
|
||||
// Check primary display is valid
|
||||
if (!primaryDisplay || fields.indexOf(primaryDisplay) === -1) {
|
||||
primaryDisplay = fields[0]
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
hasValidated = true
|
||||
} catch (error) {
|
||||
notifications.error("CSV Invalid, please try another CSV file")
|
||||
return []
|
||||
}
|
||||
hasValidated = true
|
||||
}
|
||||
|
||||
async function handleFile(evt) {
|
||||
|
|
|
@ -49,8 +49,8 @@
|
|||
if (wasSelectedTable && wasSelectedTable._id === table._id) {
|
||||
$goto("./table")
|
||||
}
|
||||
} catch (err) {
|
||||
notifications.error(err)
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting table")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,11 +27,15 @@
|
|||
}
|
||||
|
||||
async function deleteView() {
|
||||
const name = view.name
|
||||
const id = view.tableId
|
||||
await views.delete(name)
|
||||
notifications.success("View deleted")
|
||||
$goto(`./table/${id}`)
|
||||
try {
|
||||
const name = view.name
|
||||
const id = view.tableId
|
||||
await views.delete(name)
|
||||
notifications.success("View deleted")
|
||||
$goto(`./table/${id}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting view")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Dropzone, notifications } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
|
||||
export let value = []
|
||||
export let label
|
||||
|
@ -20,8 +20,12 @@
|
|||
for (let i = 0; i < fileList.length; i++) {
|
||||
data.append("file", fileList[i])
|
||||
}
|
||||
const response = await api.post(`/api/attachments/process`, data, {})
|
||||
return await response.json()
|
||||
try {
|
||||
return await API.uploadBuilderAttachment(data)
|
||||
} catch (error) {
|
||||
notifications.error("Failed to upload attachment")
|
||||
return []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { tables } from "stores/backend"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { Select, Label, Multiselect } from "@budibase/bbui"
|
||||
import { capitalise } from "../../helpers"
|
||||
|
||||
|
@ -17,12 +17,9 @@
|
|||
$: fetchRows(linkedTableId)
|
||||
|
||||
async function fetchRows(linkedTableId) {
|
||||
const FETCH_ROWS_URL = `/api/${linkedTableId}/rows`
|
||||
try {
|
||||
const response = await api.get(FETCH_ROWS_URL)
|
||||
rows = await response.json()
|
||||
rows = await API.fetchTableData(linkedTableId)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
rows = []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import analytics, { Events } from "analytics"
|
||||
import { store } from "builderStore"
|
||||
|
||||
|
@ -9,18 +9,14 @@
|
|||
|
||||
async function deployApp() {
|
||||
try {
|
||||
const response = await api.post("/api/deploy")
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`status ${response.status}`)
|
||||
} else {
|
||||
analytics.captureEvent(Events.APP.PUBLISHED, {
|
||||
appId: $store.appId,
|
||||
})
|
||||
notifications.success(`Application published successfully`)
|
||||
}
|
||||
} catch (err) {
|
||||
analytics.captureException(err)
|
||||
notifications.error(`Error publishing app: ${err}`)
|
||||
await API.deployAppChanges()
|
||||
analytics.captureEvent(Events.APP.PUBLISHED, {
|
||||
appId: $store.appId,
|
||||
})
|
||||
notifications.success("Application published successfully")
|
||||
} catch (error) {
|
||||
analytics.captureException(error)
|
||||
notifications.error("Error publishing app")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import Spinner from "components/common/Spinner.svelte"
|
||||
import { slide } from "svelte/transition"
|
||||
import { Heading, Button, Modal, ModalContent } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte"
|
||||
import { store } from "builderStore"
|
||||
|
@ -63,20 +63,14 @@
|
|||
|
||||
async function fetchDeployments() {
|
||||
try {
|
||||
const response = await api.get(`/api/deployments`)
|
||||
const json = await response.json()
|
||||
|
||||
const newDeployments = await API.getAppDeployments()
|
||||
if (deployments.length > 0) {
|
||||
checkIncomingDeploymentStatus(deployments, json)
|
||||
checkIncomingDeploymentStatus(deployments, newDeployments)
|
||||
}
|
||||
|
||||
deployments = json
|
||||
deployments = newDeployments
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
clearInterval(poll)
|
||||
notifications.error(
|
||||
"Error fetching deployment history. Please try again."
|
||||
)
|
||||
notifications.error("Error fetching deployment history")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
ModalContent,
|
||||
} from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
|
||||
let revertModal
|
||||
let appName
|
||||
|
@ -16,24 +16,14 @@
|
|||
|
||||
const revert = async () => {
|
||||
try {
|
||||
const response = await api.post(`/api/dev/${appId}/revert`)
|
||||
const json = await response.json()
|
||||
if (response.status !== 200) throw json.message
|
||||
await API.revertAppChanges(appId)
|
||||
|
||||
// Reset frontend state after revert
|
||||
const applicationPkg = await api.get(
|
||||
`/api/applications/${appId}/appPackage`
|
||||
)
|
||||
const pkg = await applicationPkg.json()
|
||||
if (applicationPkg.ok) {
|
||||
await store.actions.initialise(pkg)
|
||||
} else {
|
||||
throw new Error(pkg)
|
||||
}
|
||||
|
||||
notifications.info("Changes reverted.")
|
||||
} catch (err) {
|
||||
notifications.error(`Error reverting changes: ${err}`)
|
||||
const applicationPkg = await API.fetchAppPackage(appId)
|
||||
await store.actions.initialise(applicationPkg)
|
||||
notifications.info("Changes reverted successfully")
|
||||
} catch (error) {
|
||||
notifications.error(`Error reverting changes: ${error}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
Button,
|
||||
} from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import clientPackage from "@budibase/client/package.json"
|
||||
|
||||
let updateModal
|
||||
|
@ -18,26 +18,17 @@
|
|||
$: revertAvailable = $store.revertableVersion != null
|
||||
|
||||
const refreshAppPackage = async () => {
|
||||
const applicationPkg = await api.get(
|
||||
`/api/applications/${appId}/appPackage`
|
||||
)
|
||||
const pkg = await applicationPkg.json()
|
||||
if (applicationPkg.ok) {
|
||||
try {
|
||||
const pkg = await API.fetchAppPackage(appId)
|
||||
await store.actions.initialise(pkg)
|
||||
} else {
|
||||
throw new Error(pkg)
|
||||
} catch (error) {
|
||||
notifications.error("Error fetching app package")
|
||||
}
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
try {
|
||||
const response = await api.post(
|
||||
`/api/applications/${appId}/client/update`
|
||||
)
|
||||
const json = await response.json()
|
||||
if (response.status !== 200) {
|
||||
throw json.message
|
||||
}
|
||||
await API.updateAppClientVersion(appId)
|
||||
|
||||
// Don't wait for the async refresh, since this causes modal flashing
|
||||
refreshAppPackage()
|
||||
|
@ -47,23 +38,17 @@
|
|||
} catch (err) {
|
||||
notifications.error(`Error updating app: ${err}`)
|
||||
}
|
||||
updateModal.hide()
|
||||
}
|
||||
|
||||
const revert = async () => {
|
||||
try {
|
||||
const revertableVersion = $store.revertableVersion
|
||||
const response = await api.post(
|
||||
`/api/applications/${appId}/client/revert`
|
||||
)
|
||||
const json = await response.json()
|
||||
if (response.status !== 200) {
|
||||
throw json.message
|
||||
}
|
||||
await API.revertAppClientVersion(appId)
|
||||
|
||||
// Don't wait for the async refresh, since this causes modal flashing
|
||||
refreshAppPackage()
|
||||
notifications.success(
|
||||
`App reverted successfully to version ${revertableVersion}`
|
||||
`App reverted successfully to version ${$store.revertableVersion}`
|
||||
)
|
||||
} catch (err) {
|
||||
notifications.error(`Error reverting app: ${err}`)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Select } from "@budibase/bbui"
|
||||
import { notifications, Select } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
|
@ -23,14 +23,18 @@
|
|||
]
|
||||
|
||||
const onChangeTheme = async theme => {
|
||||
await store.actions.theme.save(theme)
|
||||
await store.actions.customTheme.save({
|
||||
...get(store).customTheme,
|
||||
navBackground:
|
||||
theme === "spectrum--light"
|
||||
? "var(--spectrum-global-color-gray-50)"
|
||||
: "var(--spectrum-global-color-gray-100)",
|
||||
})
|
||||
try {
|
||||
await store.actions.theme.save(theme)
|
||||
await store.actions.customTheme.save({
|
||||
...get(store).customTheme,
|
||||
navBackground:
|
||||
theme === "spectrum--light"
|
||||
? "var(--spectrum-global-color-gray-50)"
|
||||
: "var(--spectrum-global-color-gray-100)",
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error updating theme")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<script>
|
||||
import { ActionMenu, ActionButton, MenuItem, Icon } from "@budibase/bbui"
|
||||
import {
|
||||
ActionMenu,
|
||||
ActionButton,
|
||||
MenuItem,
|
||||
Icon,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { store, currentAssetName, selectedComponent } from "builderStore"
|
||||
import structure from "./componentStructure.json"
|
||||
|
||||
|
@ -36,7 +42,11 @@
|
|||
|
||||
const onItemChosen = async item => {
|
||||
if (!item.isCategory) {
|
||||
await store.actions.components.create(item.component)
|
||||
try {
|
||||
await store.actions.components.create(item.component)
|
||||
} catch (error) {
|
||||
notifications.error("Error creating component")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
Body,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import ErrorSVG from "assets/error.svg?raw"
|
||||
import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw"
|
||||
import { findComponent, findComponentPath } from "builderStore/componentUtils"
|
||||
|
||||
let iframe
|
||||
|
@ -146,44 +146,49 @@
|
|||
}
|
||||
})
|
||||
|
||||
const handleBudibaseEvent = event => {
|
||||
const handleBudibaseEvent = async event => {
|
||||
const { type, data } = event.data || event.detail
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
|
||||
if (type === "select-component" && data.id) {
|
||||
store.actions.components.select({ _id: data.id })
|
||||
} else if (type === "update-prop") {
|
||||
store.actions.components.updateProp(data.prop, data.value)
|
||||
} else if (type === "delete-component" && data.id) {
|
||||
confirmDeleteComponent(data.id)
|
||||
} else if (type === "preview-loaded") {
|
||||
// Wait for this event to show the client library if intelligent
|
||||
// loading is supported
|
||||
loading = false
|
||||
} else if (type === "move-component") {
|
||||
const { componentId, destinationComponentId } = data
|
||||
const rootComponent = get(currentAsset).props
|
||||
try {
|
||||
if (type === "select-component" && data.id) {
|
||||
store.actions.components.select({ _id: data.id })
|
||||
} else if (type === "update-prop") {
|
||||
await store.actions.components.updateProp(data.prop, data.value)
|
||||
} else if (type === "delete-component" && data.id) {
|
||||
confirmDeleteComponent(data.id)
|
||||
} else if (type === "preview-loaded") {
|
||||
// Wait for this event to show the client library if intelligent
|
||||
// loading is supported
|
||||
loading = false
|
||||
} else if (type === "move-component") {
|
||||
const { componentId, destinationComponentId } = data
|
||||
const rootComponent = get(currentAsset).props
|
||||
|
||||
// Get source and destination components
|
||||
const source = findComponent(rootComponent, componentId)
|
||||
const destination = findComponent(rootComponent, destinationComponentId)
|
||||
// Get source and destination components
|
||||
const source = findComponent(rootComponent, componentId)
|
||||
const destination = findComponent(rootComponent, destinationComponentId)
|
||||
|
||||
// Stop if the target is a child of source
|
||||
const path = findComponentPath(source, destinationComponentId)
|
||||
const ids = path.map(component => component._id)
|
||||
if (ids.includes(data.destinationComponentId)) {
|
||||
return
|
||||
// Stop if the target is a child of source
|
||||
const path = findComponentPath(source, destinationComponentId)
|
||||
const ids = path.map(component => component._id)
|
||||
if (ids.includes(data.destinationComponentId)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Cut and paste the component to the new destination
|
||||
if (source && destination) {
|
||||
store.actions.components.copy(source, true)
|
||||
await store.actions.components.paste(destination, data.mode)
|
||||
}
|
||||
} else {
|
||||
console.warn(`Client sent unknown event type: ${type}`)
|
||||
}
|
||||
|
||||
// Cut and paste the component to the new destination
|
||||
if (source && destination) {
|
||||
store.actions.components.copy(source, true)
|
||||
store.actions.components.paste(destination, data.mode)
|
||||
}
|
||||
} else {
|
||||
console.warn(`Client sent unknown event type: ${type}`)
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
notifications.error("Error handling event from app preview")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +201,7 @@
|
|||
try {
|
||||
await store.actions.components.delete({ _id: idToDelete })
|
||||
} catch (error) {
|
||||
notifications.error(error)
|
||||
notifications.error("Error deleting component")
|
||||
}
|
||||
idToDelete = null
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
Label,
|
||||
Select,
|
||||
Button,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import AppThemeSelect from "./AppThemeSelect.svelte"
|
||||
|
@ -43,23 +44,31 @@
|
|||
]
|
||||
|
||||
const updateProperty = property => {
|
||||
return e => {
|
||||
store.actions.customTheme.save({
|
||||
...get(store).customTheme,
|
||||
[property]: e.detail,
|
||||
})
|
||||
return async e => {
|
||||
try {
|
||||
store.actions.customTheme.save({
|
||||
...get(store).customTheme,
|
||||
[property]: e.detail,
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error updating custom theme")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resetTheme = () => {
|
||||
const theme = get(store).theme
|
||||
store.actions.customTheme.save({
|
||||
...defaultTheme,
|
||||
navBackground:
|
||||
theme === "spectrum--light"
|
||||
? "var(--spectrum-global-color-gray-50)"
|
||||
: "var(--spectrum-global-color-gray-100)",
|
||||
})
|
||||
try {
|
||||
const theme = get(store).theme
|
||||
store.actions.customTheme.save({
|
||||
...defaultTheme,
|
||||
navBackground:
|
||||
theme === "spectrum--light"
|
||||
? "var(--spectrum-global-color-gray-50)"
|
||||
: "var(--spectrum-global-color-gray-100)",
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error saving custom theme")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -29,10 +29,14 @@
|
|||
if (currentIndex === 0) {
|
||||
return
|
||||
}
|
||||
const newChildren = parent._children.filter(c => c !== component)
|
||||
newChildren.splice(currentIndex - 1, 0, component)
|
||||
parent._children = newChildren
|
||||
store.actions.preview.saveSelected()
|
||||
try {
|
||||
const newChildren = parent._children.filter(c => c !== component)
|
||||
newChildren.splice(currentIndex - 1, 0, component)
|
||||
parent._children = newChildren
|
||||
store.actions.preview.saveSelected()
|
||||
} catch (error) {
|
||||
notifications.error("Error saving screen")
|
||||
}
|
||||
}
|
||||
|
||||
const moveDownComponent = () => {
|
||||
|
@ -45,10 +49,14 @@
|
|||
if (currentIndex === parent._children.length - 1) {
|
||||
return
|
||||
}
|
||||
const newChildren = parent._children.filter(c => c !== component)
|
||||
newChildren.splice(currentIndex + 1, 0, component)
|
||||
parent._children = newChildren
|
||||
store.actions.preview.saveSelected()
|
||||
try {
|
||||
const newChildren = parent._children.filter(c => c !== component)
|
||||
newChildren.splice(currentIndex + 1, 0, component)
|
||||
parent._children = newChildren
|
||||
store.actions.preview.saveSelected()
|
||||
} catch (error) {
|
||||
notifications.error("Error saving screen")
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateComponent = () => {
|
||||
|
@ -60,7 +68,7 @@
|
|||
try {
|
||||
await store.actions.components.delete(component)
|
||||
} catch (error) {
|
||||
notifications.error(error)
|
||||
notifications.error("Error deleting component")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,8 +78,12 @@
|
|||
}
|
||||
|
||||
const pasteComponent = (mode, preserveBindings = false) => {
|
||||
// lives in store - also used by drag drop
|
||||
store.actions.components.paste(component, mode, preserveBindings)
|
||||
try {
|
||||
// lives in store - also used by drag drop
|
||||
store.actions.components.paste(component, mode, preserveBindings)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving component")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { capitalise } from "helpers"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export let components = []
|
||||
export let currentComponent
|
||||
|
@ -62,6 +63,14 @@
|
|||
}
|
||||
closedNodes = closedNodes
|
||||
}
|
||||
|
||||
const onDrop = async () => {
|
||||
try {
|
||||
await dragDropStore.actions.drop()
|
||||
} catch (error) {
|
||||
notifications.error("Error saving component")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
|
@ -69,7 +78,7 @@
|
|||
<li on:click|stopPropagation={() => selectComponent(component)}>
|
||||
{#if $dragDropStore?.targetComponent === component && $dragDropStore.dropPosition === DropPosition.ABOVE}
|
||||
<div
|
||||
on:drop={dragDropStore.actions.drop}
|
||||
on:drop={onDrop}
|
||||
ondragover="return false"
|
||||
ondragenter="return false"
|
||||
class="drop-item"
|
||||
|
@ -83,7 +92,7 @@
|
|||
on:dragstart={dragstart(component)}
|
||||
on:dragover={dragover(component, index)}
|
||||
on:iconClick={() => toggleNodeOpen(component._id)}
|
||||
on:drop={dragDropStore.actions.drop}
|
||||
on:drop={onDrop}
|
||||
text={getComponentText(component)}
|
||||
withArrow
|
||||
indentLevel={level + 1}
|
||||
|
@ -105,7 +114,7 @@
|
|||
|
||||
{#if $dragDropStore?.targetComponent === component && ($dragDropStore.dropPosition === DropPosition.INSIDE || $dragDropStore.dropPosition === DropPosition.BELOW)}
|
||||
<div
|
||||
on:drop={dragDropStore.actions.drop}
|
||||
on:drop={onDrop}
|
||||
ondragover="return false"
|
||||
ondragenter="return false"
|
||||
class="drop-item"
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
const deleteLayout = async () => {
|
||||
try {
|
||||
await store.actions.layouts.delete(layout)
|
||||
notifications.success(`Layout ${layout.name} deleted successfully.`)
|
||||
notifications.success("Layout deleted successfully")
|
||||
} catch (err) {
|
||||
notifications.error(`Error deleting layout: ${err.message}`)
|
||||
notifications.error("Error deleting layout")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,9 +32,9 @@
|
|||
const layoutToSave = cloneDeep(layout)
|
||||
layoutToSave.name = name
|
||||
await store.actions.layouts.save(layoutToSave)
|
||||
notifications.success(`Layout saved successfully.`)
|
||||
notifications.success("Layout saved successfully")
|
||||
} catch (err) {
|
||||
notifications.error(`Error saving layout: ${err.message}`)
|
||||
notifications.error("Error saving layout")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
const deleteScreen = async () => {
|
||||
try {
|
||||
await store.actions.screens.delete(screen)
|
||||
await store.actions.routing.fetch()
|
||||
$goto("../")
|
||||
notifications.success("Deleted screen successfully.")
|
||||
} catch (err) {
|
||||
|
|
|
@ -72,7 +72,7 @@ export default function () {
|
|||
return state
|
||||
})
|
||||
},
|
||||
drop: () => {
|
||||
drop: async () => {
|
||||
const state = get(store)
|
||||
|
||||
// Stop if the target and source are the same
|
||||
|
@ -92,7 +92,7 @@ export default function () {
|
|||
|
||||
// Cut and paste the component
|
||||
frontendStore.actions.components.copy(state.dragged, true)
|
||||
frontendStore.actions.components.paste(
|
||||
await frontendStore.actions.components.paste(
|
||||
state.targetComponent,
|
||||
state.dropPosition
|
||||
)
|
||||
|
|
|
@ -11,7 +11,15 @@
|
|||
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
|
||||
import Layout from "components/design/NavigationPanel/Layout.svelte"
|
||||
import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte"
|
||||
import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui"
|
||||
import {
|
||||
Icon,
|
||||
Modal,
|
||||
Select,
|
||||
Search,
|
||||
Tabs,
|
||||
Tab,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
|
||||
export let showModal
|
||||
|
||||
|
@ -58,8 +66,12 @@
|
|||
selectedAccessRole.set(role)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
store.actions.routing.fetch()
|
||||
onMount(async () => {
|
||||
try {
|
||||
await store.actions.routing.fetch()
|
||||
} catch (error) {
|
||||
notifications.error("Error fetching routes")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
try {
|
||||
await store.actions.layouts.save({ name })
|
||||
notifications.success(`Layout ${name} created successfully`)
|
||||
} catch (err) {
|
||||
notifications.error(`Error creating layout ${name}.`)
|
||||
} catch (error) {
|
||||
notifications.error("Error creating layout")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import ScreenDetailsModal from "components/design/NavigationPanel/ScreenDetailsModal.svelte"
|
||||
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
|
||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import { Modal, notifications } from "@budibase/bbui"
|
||||
import { store, selectedAccessRole, allScreens } from "builderStore"
|
||||
import analytics, { Events } from "analytics"
|
||||
|
||||
|
@ -29,15 +29,19 @@
|
|||
|
||||
const save = async () => {
|
||||
showProgressCircle = true
|
||||
await createScreens()
|
||||
for (let screen of createdScreens) {
|
||||
await saveScreens(screen)
|
||||
try {
|
||||
await createScreens()
|
||||
for (let screen of createdScreens) {
|
||||
await saveScreens(screen)
|
||||
}
|
||||
await store.actions.routing.fetch()
|
||||
selectedScreens = []
|
||||
createdScreens = []
|
||||
screenName = ""
|
||||
url = ""
|
||||
} catch (error) {
|
||||
notifications.error("Error creating screens")
|
||||
}
|
||||
await store.actions.routing.fetch()
|
||||
selectedScreens = []
|
||||
createdScreens = []
|
||||
screenName = ""
|
||||
url = ""
|
||||
showProgressCircle = false
|
||||
}
|
||||
|
||||
|
@ -71,12 +75,16 @@
|
|||
|
||||
draftScreen.routing.route = route
|
||||
|
||||
await store.actions.screens.create(draftScreen)
|
||||
await store.actions.screens.save(draftScreen)
|
||||
if (draftScreen.props._instanceName.endsWith("List")) {
|
||||
await store.actions.components.links.save(
|
||||
draftScreen.routing.route,
|
||||
draftScreen.routing.route.split("/")[1]
|
||||
)
|
||||
try {
|
||||
await store.actions.components.links.save(
|
||||
draftScreen.routing.route,
|
||||
draftScreen.routing.route.split("/")[1]
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error("Error creating link to screen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { isEmpty } from "lodash/fp"
|
||||
import { Input, DetailSummary } from "@budibase/bbui"
|
||||
import { Input, DetailSummary, notifications } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import PropertyControl from "./PropertyControls/PropertyControl.svelte"
|
||||
import LayoutSelect from "./PropertyControls/LayoutSelect.svelte"
|
||||
|
@ -40,7 +40,13 @@
|
|||
]
|
||||
}
|
||||
|
||||
const updateProp = store.actions.components.updateProp
|
||||
const updateProp = async (key, value) => {
|
||||
try {
|
||||
await store.actions.components.updateProp(key, value)
|
||||
} catch (error) {
|
||||
notifications.error("Error updating component prop")
|
||||
}
|
||||
}
|
||||
|
||||
const canRenderControl = setting => {
|
||||
const control = getComponentForSettingType(setting?.type)
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<script>
|
||||
import { DetailSummary, ActionButton, Drawer, Button } from "@budibase/bbui"
|
||||
import {
|
||||
DetailSummary,
|
||||
ActionButton,
|
||||
Drawer,
|
||||
Button,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import ConditionalUIDrawer from "./PropertyControls/ConditionalUIDrawer.svelte"
|
||||
|
||||
|
@ -14,8 +20,12 @@
|
|||
drawer.show()
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
store.actions.components.updateConditions(tempValue)
|
||||
const save = async () => {
|
||||
try {
|
||||
await store.actions.components.updateConditions(tempValue)
|
||||
} catch (error) {
|
||||
notifications.error("Error updating conditions")
|
||||
}
|
||||
drawer.hide()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
Layout,
|
||||
Body,
|
||||
Button,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
|
||||
|
@ -21,8 +22,12 @@
|
|||
drawer.show()
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
store.actions.components.updateCustomStyle(tempValue)
|
||||
const save = async () => {
|
||||
try {
|
||||
await store.actions.components.updateCustomStyle(tempValue)
|
||||
} catch (error) {
|
||||
notifications.error("Error updating custom style")
|
||||
}
|
||||
drawer.hide()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -36,28 +36,37 @@
|
|||
|
||||
// called by the parent modal when actions are saved
|
||||
const createAutomation = async parameters => {
|
||||
if (parameters.automationId || !parameters.newAutomationName) return
|
||||
await automationStore.actions.create({ name: parameters.newAutomationName })
|
||||
const appActionDefinition = $automationStore.blockDefinitions.TRIGGER.APP
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock(
|
||||
"TRIGGER",
|
||||
"APP",
|
||||
appActionDefinition
|
||||
)
|
||||
|
||||
newBlock.inputs = {
|
||||
fields: Object.keys(parameters.fields).reduce((fields, key) => {
|
||||
fields[key] = "string"
|
||||
return fields
|
||||
}, {}),
|
||||
if (parameters.automationId || !parameters.newAutomationName) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await automationStore.actions.create({
|
||||
name: parameters.newAutomationName,
|
||||
})
|
||||
const appActionDefinition = $automationStore.blockDefinitions.TRIGGER.APP
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock(
|
||||
"TRIGGER",
|
||||
"APP",
|
||||
appActionDefinition
|
||||
)
|
||||
|
||||
automationStore.actions.addBlockToAutomation(newBlock)
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
parameters.automationId = $automationStore.selectedAutomation.automation._id
|
||||
delete parameters.newAutomationName
|
||||
newBlock.inputs = {
|
||||
fields: Object.keys(parameters.fields).reduce((fields, key) => {
|
||||
fields[key] = "string"
|
||||
return fields
|
||||
}, {}),
|
||||
}
|
||||
|
||||
automationStore.actions.addBlockToAutomation(newBlock)
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
parameters.automationId =
|
||||
$automationStore.selectedAutomation.automation._id
|
||||
delete parameters.newAutomationName
|
||||
} catch (error) {
|
||||
notifications.error("Error creating automation")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
import { dndzone } from "svelte-dnd-action"
|
||||
import { generate } from "shortid"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import { OperatorOptions, getValidOperatorsForType } from "constants/lucene"
|
||||
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
||||
import { selectedComponent } from "builderStore"
|
||||
import { getComponentForSettingType } from "./componentSettings"
|
||||
import PropertyControl from "./PropertyControl.svelte"
|
||||
|
@ -83,7 +83,7 @@
|
|||
valueType: "string",
|
||||
id: generate(),
|
||||
action: "hide",
|
||||
operator: OperatorOptions.Equals.value,
|
||||
operator: Constants.OperatorOptions.Equals.value,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -108,13 +108,13 @@
|
|||
}
|
||||
|
||||
const getOperatorOptions = condition => {
|
||||
return getValidOperatorsForType(condition.valueType)
|
||||
return LuceneUtils.getValidOperatorsForType(condition.valueType)
|
||||
}
|
||||
|
||||
const onOperatorChange = (condition, newOperator) => {
|
||||
const noValueOptions = [
|
||||
OperatorOptions.Empty.value,
|
||||
OperatorOptions.NotEmpty.value,
|
||||
Constants.OperatorOptions.Empty.value,
|
||||
Constants.OperatorOptions.NotEmpty.value,
|
||||
]
|
||||
condition.noValue = noValueOptions.includes(newOperator)
|
||||
if (condition.noValue) {
|
||||
|
@ -127,9 +127,12 @@
|
|||
condition.referenceValue = null
|
||||
|
||||
// Ensure a valid operator is set
|
||||
const validOperators = getValidOperatorsForType(newType).map(x => x.value)
|
||||
const validOperators = LuceneUtils.getValidOperatorsForType(newType).map(
|
||||
x => x.value
|
||||
)
|
||||
if (!validOperators.includes(condition.operator)) {
|
||||
condition.operator = validOperators[0] ?? OperatorOptions.Equals.value
|
||||
condition.operator =
|
||||
validOperators[0] ?? Constants.OperatorOptions.Equals.value
|
||||
onOperatorChange(condition, condition.operator)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||
import { generate } from "shortid"
|
||||
import { getValidOperatorsForType, OperatorOptions } from "constants/lucene"
|
||||
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
||||
import { getFields } from "helpers/searchFields"
|
||||
|
||||
export let schemaFields
|
||||
|
@ -32,7 +32,7 @@
|
|||
{
|
||||
id: generate(),
|
||||
field: null,
|
||||
operator: OperatorOptions.Equals.value,
|
||||
operator: Constants.OperatorOptions.Equals.value,
|
||||
value: null,
|
||||
valueType: "Value",
|
||||
},
|
||||
|
@ -54,11 +54,12 @@
|
|||
expression.type = enrichedSchemaFields.find(x => x.name === field)?.type
|
||||
|
||||
// Ensure a valid operator is set
|
||||
const validOperators = getValidOperatorsForType(expression.type).map(
|
||||
x => x.value
|
||||
)
|
||||
const validOperators = LuceneUtils.getValidOperatorsForType(
|
||||
expression.type
|
||||
).map(x => x.value)
|
||||
if (!validOperators.includes(expression.operator)) {
|
||||
expression.operator = validOperators[0] ?? OperatorOptions.Equals.value
|
||||
expression.operator =
|
||||
validOperators[0] ?? Constants.OperatorOptions.Equals.value
|
||||
onOperatorChange(expression, expression.operator)
|
||||
}
|
||||
|
||||
|
@ -73,8 +74,8 @@
|
|||
|
||||
const onOperatorChange = (expression, operator) => {
|
||||
const noValueOptions = [
|
||||
OperatorOptions.Empty.value,
|
||||
OperatorOptions.NotEmpty.value,
|
||||
Constants.OperatorOptions.Empty.value,
|
||||
Constants.OperatorOptions.NotEmpty.value,
|
||||
]
|
||||
expression.noValue = noValueOptions.includes(operator)
|
||||
if (expression.noValue) {
|
||||
|
@ -110,7 +111,7 @@
|
|||
/>
|
||||
<Select
|
||||
disabled={!filter.field}
|
||||
options={getValidOperatorsForType(filter.type)}
|
||||
options={LuceneUtils.getValidOperatorsForType(filter.type)}
|
||||
bind:value={filter.operator}
|
||||
on:change={e => onOperatorChange(filter, e.detail)}
|
||||
placeholder={null}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import { ActionButton, notifications } from "@budibase/bbui"
|
||||
import { currentAsset, store } from "builderStore"
|
||||
import { findClosestMatchingComponent } from "builderStore/componentUtils"
|
||||
import { makeDatasourceFormComponents } from "builderStore/store/screenTemplates/utils/commonComponents"
|
||||
|
@ -9,7 +9,7 @@
|
|||
|
||||
let confirmResetFieldsDialog
|
||||
|
||||
const resetFormFields = () => {
|
||||
const resetFormFields = async () => {
|
||||
const form = findClosestMatchingComponent(
|
||||
$currentAsset.props,
|
||||
componentInstance._id,
|
||||
|
@ -17,10 +17,14 @@
|
|||
)
|
||||
const dataSource = form?.dataSource
|
||||
const fields = makeDatasourceFormComponents(dataSource)
|
||||
store.actions.components.updateProp(
|
||||
"_children",
|
||||
fields.map(field => field.json())
|
||||
)
|
||||
try {
|
||||
await store.actions.components.updateProp(
|
||||
"_children",
|
||||
fields.map(field => field.json())
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error("Error resetting form fields")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { get } from "svelte/store"
|
||||
import { get as deepGet, setWith } from "lodash"
|
||||
import { Input, DetailSummary } from "@budibase/bbui"
|
||||
import { Input, DetailSummary, notifications } from "@budibase/bbui"
|
||||
import PropertyControl from "./PropertyControls/PropertyControl.svelte"
|
||||
import LayoutSelect from "./PropertyControls/LayoutSelect.svelte"
|
||||
import RoleSelect from "./PropertyControls/RoleSelect.svelte"
|
||||
|
@ -29,7 +29,12 @@
|
|||
}
|
||||
return state
|
||||
})
|
||||
store.actions.preview.saveSelected()
|
||||
|
||||
try {
|
||||
store.actions.preview.saveSelected()
|
||||
} catch (error) {
|
||||
notifications.error("Error saving settings")
|
||||
}
|
||||
}
|
||||
|
||||
const screenSettings = [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import PropertyControl from "./PropertyControls/PropertyControl.svelte"
|
||||
import { DetailSummary } from "@budibase/bbui"
|
||||
import { DetailSummary, notifications } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
|
||||
export let name
|
||||
|
@ -23,6 +23,14 @@
|
|||
delete controlProps.control
|
||||
return controlProps
|
||||
}
|
||||
|
||||
const updateStyle = async (key, val) => {
|
||||
try {
|
||||
await store.actions.components.updateStyle(key, val)
|
||||
} catch (error) {
|
||||
notifications.error("Error updating style")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DetailSummary collapsible={false} name={`${name}${changed ? " *" : ""}`}>
|
||||
|
@ -34,7 +42,7 @@
|
|||
control={prop.control}
|
||||
key={prop.key}
|
||||
value={style[prop.key]}
|
||||
onChange={val => store.actions.components.updateStyle(prop.key, val)}
|
||||
onChange={val => updateStyle(prop.key, val)}
|
||||
props={getControlProps(prop)}
|
||||
{bindings}
|
||||
/>
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
Detail,
|
||||
Divider,
|
||||
Layout,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { auth } from "stores/portal"
|
||||
|
||||
|
@ -45,20 +46,28 @@
|
|||
improvements,
|
||||
comment,
|
||||
})
|
||||
auth.updateSelf({
|
||||
flags: {
|
||||
feedbackSubmitted: true,
|
||||
},
|
||||
})
|
||||
try {
|
||||
auth.updateSelf({
|
||||
flags: {
|
||||
feedbackSubmitted: true,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error updating user")
|
||||
}
|
||||
dispatch("complete")
|
||||
}
|
||||
|
||||
function cancelFeedback() {
|
||||
auth.updateSelf({
|
||||
flags: {
|
||||
feedbackSubmitted: true,
|
||||
},
|
||||
})
|
||||
try {
|
||||
auth.updateSelf({
|
||||
flags: {
|
||||
feedbackSubmitted: true,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error updating user")
|
||||
}
|
||||
dispatch("complete")
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Label, Select } from "@budibase/bbui"
|
||||
import { Label, notifications, Select } from "@budibase/bbui"
|
||||
import { permissions, roles } from "stores/backend"
|
||||
import { Roles } from "constants/backend"
|
||||
|
||||
|
@ -11,16 +11,20 @@
|
|||
let roleId, loaded, fetched
|
||||
|
||||
async function updateRole(role, id) {
|
||||
roleId = role
|
||||
const queryId = query?._id || id
|
||||
if (roleId && queryId) {
|
||||
for (let level of ["read", "write"]) {
|
||||
await permissions.save({
|
||||
level,
|
||||
role,
|
||||
resource: queryId,
|
||||
})
|
||||
try {
|
||||
roleId = role
|
||||
const queryId = query?._id || id
|
||||
if (roleId && queryId) {
|
||||
for (let level of ["read", "write"]) {
|
||||
await permissions.save({
|
||||
level,
|
||||
role,
|
||||
resource: queryId,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error updating role")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,9 +71,9 @@
|
|||
}
|
||||
data = response.rows
|
||||
fields = response.schema
|
||||
notifications.success("Query executed successfully.")
|
||||
} catch (err) {
|
||||
notifications.error(err)
|
||||
notifications.success("Query executed successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error previewing query")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,9 +83,8 @@
|
|||
saveId = _id
|
||||
notifications.success(`Query saved successfully.`)
|
||||
$goto(`../${_id}`)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
notifications.error(`Error creating query. ${err.message}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error creating query")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<script>
|
||||
import { ModalContent, Modal, Icon, ColorPicker, Label } from "@budibase/bbui"
|
||||
import {
|
||||
ModalContent,
|
||||
Modal,
|
||||
Icon,
|
||||
ColorPicker,
|
||||
Label,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { apps } from "stores/portal"
|
||||
|
||||
export let app
|
||||
|
@ -51,12 +58,16 @@
|
|||
}
|
||||
|
||||
const save = async () => {
|
||||
await apps.update(app.instance._id, {
|
||||
icon: {
|
||||
name: selectedIcon,
|
||||
color: selectedColor,
|
||||
},
|
||||
})
|
||||
try {
|
||||
await apps.update(app.instance._id, {
|
||||
icon: {
|
||||
name: selectedIcon,
|
||||
color: selectedColor,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
notifications.error("Error updating app")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { writable, get as svelteGet } from "svelte/store"
|
||||
import { notifications, Input, ModalContent, Dropzone } from "@budibase/bbui"
|
||||
import { store, automationStore } from "builderStore"
|
||||
import { API } from "api"
|
||||
import { apps, admin, auth } from "stores/portal"
|
||||
import api, { get, post } from "builderStore/api"
|
||||
import analytics, { Events } from "analytics"
|
||||
import { onMount } from "svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
@ -45,43 +45,27 @@
|
|||
}
|
||||
|
||||
// Create App
|
||||
const appResp = await post("/api/applications", data, {})
|
||||
const appJson = await appResp.json()
|
||||
if (!appResp.ok) {
|
||||
throw new Error(appJson.message)
|
||||
}
|
||||
|
||||
const createdApp = await API.createApp(data)
|
||||
analytics.captureEvent(Events.APP.CREATED, {
|
||||
name: $values.name,
|
||||
appId: appJson.instance._id,
|
||||
appId: createdApp.instance._id,
|
||||
templateToUse: template,
|
||||
})
|
||||
|
||||
// Select Correct Application/DB in prep for creating user
|
||||
const applicationPkg = await get(
|
||||
`/api/applications/${appJson.instance._id}/appPackage`
|
||||
)
|
||||
const pkg = await applicationPkg.json()
|
||||
if (applicationPkg.ok) {
|
||||
await store.actions.initialise(pkg)
|
||||
await automationStore.actions.fetch()
|
||||
// update checklist - incase first app
|
||||
await admin.init()
|
||||
} else {
|
||||
throw new Error(pkg)
|
||||
}
|
||||
const pkg = await API.fetchAppPackage(createdApp.instance._id)
|
||||
await store.actions.initialise(pkg)
|
||||
await automationStore.actions.fetch()
|
||||
// Update checklist - in case first app
|
||||
await admin.init()
|
||||
|
||||
// Create user
|
||||
const user = {
|
||||
roleId: $values.roleId,
|
||||
}
|
||||
const userResp = await api.post(`/api/users/metadata/self`, user)
|
||||
await userResp.json()
|
||||
await API.updateOwnMetadata({ roleId: $values.roleId })
|
||||
await auth.setInitInfo({})
|
||||
$goto(`/builder/app/${appJson.instance._id}`)
|
||||
$goto(`/builder/app/${createdApp.instance._id}`)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
notifications.error(error)
|
||||
notifications.error("Error creating app")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
await apps.update(app.instance._id, body)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
notifications.error(error)
|
||||
notifications.error("Error updating app")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* Operator options for lucene queries
|
||||
*/
|
||||
export const OperatorOptions = {
|
||||
Equals: {
|
||||
value: "equal",
|
||||
label: "Equals",
|
||||
},
|
||||
NotEquals: {
|
||||
value: "notEqual",
|
||||
label: "Not equals",
|
||||
},
|
||||
Empty: {
|
||||
value: "empty",
|
||||
label: "Is empty",
|
||||
},
|
||||
NotEmpty: {
|
||||
value: "notEmpty",
|
||||
label: "Is not empty",
|
||||
},
|
||||
StartsWith: {
|
||||
value: "string",
|
||||
label: "Starts with",
|
||||
},
|
||||
Like: {
|
||||
value: "fuzzy",
|
||||
label: "Like",
|
||||
},
|
||||
MoreThan: {
|
||||
value: "rangeLow",
|
||||
label: "More than",
|
||||
},
|
||||
LessThan: {
|
||||
value: "rangeHigh",
|
||||
label: "Less than",
|
||||
},
|
||||
Contains: {
|
||||
value: "equal",
|
||||
label: "Contains",
|
||||
},
|
||||
NotContains: {
|
||||
value: "notEqual",
|
||||
label: "Does Not Contain",
|
||||
},
|
||||
}
|
||||
|
||||
export const NoEmptyFilterStrings = [
|
||||
OperatorOptions.StartsWith.value,
|
||||
OperatorOptions.Like.value,
|
||||
OperatorOptions.Equals.value,
|
||||
OperatorOptions.NotEquals.value,
|
||||
OperatorOptions.Contains.value,
|
||||
OperatorOptions.NotContains.value,
|
||||
]
|
||||
|
||||
/**
|
||||
* Returns the valid operator options for a certain data type
|
||||
* @param type the data type
|
||||
*/
|
||||
export const getValidOperatorsForType = type => {
|
||||
const Op = OperatorOptions
|
||||
const stringOps = [
|
||||
Op.Equals,
|
||||
Op.NotEquals,
|
||||
Op.StartsWith,
|
||||
Op.Like,
|
||||
Op.Empty,
|
||||
Op.NotEmpty,
|
||||
]
|
||||
const numOps = [
|
||||
Op.Equals,
|
||||
Op.NotEquals,
|
||||
Op.MoreThan,
|
||||
Op.LessThan,
|
||||
Op.Empty,
|
||||
Op.NotEmpty,
|
||||
]
|
||||
if (type === "string") {
|
||||
return stringOps
|
||||
} else if (type === "number") {
|
||||
return numOps
|
||||
} else if (type === "options") {
|
||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||
} else if (type === "array") {
|
||||
return [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty]
|
||||
} else if (type === "boolean") {
|
||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||
} else if (type === "longform") {
|
||||
return stringOps
|
||||
} else if (type === "datetime") {
|
||||
return numOps
|
||||
} else if (type === "formula") {
|
||||
return stringOps.concat([Op.MoreThan, Op.LessThan])
|
||||
}
|
||||
return []
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { writable } from "svelte/store"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
|
||||
export default function (url) {
|
||||
const store = writable({ status: "LOADING", data: {}, error: {} })
|
||||
|
@ -7,8 +7,8 @@ export default function (url) {
|
|||
async function get() {
|
||||
store.update(u => ({ ...u, status: "LOADING" }))
|
||||
try {
|
||||
const response = await api.get(url)
|
||||
store.set({ data: await response.json(), status: "SUCCESS" })
|
||||
const data = await API.get({ url })
|
||||
store.set({ data, status: "SUCCESS" })
|
||||
} catch (e) {
|
||||
store.set({ data: {}, error: e, status: "ERROR" })
|
||||
}
|
||||
|
|
|
@ -1,210 +0,0 @@
|
|||
// Do not use any aliased imports in common files, as these will be bundled
|
||||
// by multiple bundlers which may not be able to resolve them.
|
||||
// This will eventually be replaced by the new client implementation when we
|
||||
// add a core package.
|
||||
import { writable, derived, get } from "svelte/store"
|
||||
import * as API from "../builderStore/api"
|
||||
import { buildLuceneQuery } from "./lucene"
|
||||
|
||||
const defaultOptions = {
|
||||
tableId: null,
|
||||
filters: null,
|
||||
limit: 10,
|
||||
sortColumn: null,
|
||||
sortOrder: "ascending",
|
||||
paginate: true,
|
||||
schema: null,
|
||||
}
|
||||
|
||||
export const fetchTableData = opts => {
|
||||
// Save option set so we can override it later rather than relying on params
|
||||
let options = {
|
||||
...defaultOptions,
|
||||
...opts,
|
||||
}
|
||||
|
||||
// Local non-observable state
|
||||
let query
|
||||
let sortType
|
||||
let lastBookmark
|
||||
|
||||
// Local observable state
|
||||
const store = writable({
|
||||
rows: [],
|
||||
schema: null,
|
||||
loading: false,
|
||||
loaded: false,
|
||||
bookmarks: [],
|
||||
pageNumber: 0,
|
||||
})
|
||||
|
||||
// Derive certain properties to return
|
||||
const derivedStore = derived(store, $store => {
|
||||
return {
|
||||
...$store,
|
||||
hasNextPage: $store.bookmarks[$store.pageNumber + 1] != null,
|
||||
hasPrevPage: $store.pageNumber > 0,
|
||||
}
|
||||
})
|
||||
|
||||
const fetchPage = async bookmark => {
|
||||
lastBookmark = bookmark
|
||||
const { tableId, limit, sortColumn, sortOrder, paginate } = options
|
||||
const res = await API.post(`/api/${options.tableId}/search`, {
|
||||
tableId,
|
||||
query,
|
||||
limit,
|
||||
sort: sortColumn,
|
||||
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
||||
sortType,
|
||||
paginate,
|
||||
bookmark,
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
// Fetches a fresh set of results from the server
|
||||
const fetchData = async () => {
|
||||
const { tableId, schema, sortColumn, filters } = options
|
||||
|
||||
// Ensure table ID exists
|
||||
if (!tableId) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get and enrich schema.
|
||||
// Ensure there are "name" properties for all fields and that field schema
|
||||
// are objects
|
||||
let enrichedSchema = schema
|
||||
if (!enrichedSchema) {
|
||||
const definition = await API.get(`/api/tables/${tableId}`)
|
||||
enrichedSchema = definition?.schema ?? null
|
||||
}
|
||||
if (enrichedSchema) {
|
||||
Object.entries(schema).forEach(([fieldName, fieldSchema]) => {
|
||||
if (typeof fieldSchema === "string") {
|
||||
enrichedSchema[fieldName] = {
|
||||
type: fieldSchema,
|
||||
name: fieldName,
|
||||
}
|
||||
} else {
|
||||
enrichedSchema[fieldName] = {
|
||||
...fieldSchema,
|
||||
name: fieldName,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Save fixed schema so we can provide it later
|
||||
options.schema = enrichedSchema
|
||||
}
|
||||
|
||||
// Ensure schema exists
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
store.update($store => ({ ...$store, schema, loading: true }))
|
||||
|
||||
// Work out what sort type to use
|
||||
if (!sortColumn || !schema[sortColumn]) {
|
||||
sortType = "string"
|
||||
}
|
||||
const type = schema?.[sortColumn]?.type
|
||||
sortType = type === "number" ? "number" : "string"
|
||||
|
||||
// Build the lucene query
|
||||
query = buildLuceneQuery(filters)
|
||||
|
||||
// Actually fetch data
|
||||
const page = await fetchPage()
|
||||
store.update($store => ({
|
||||
...$store,
|
||||
loading: false,
|
||||
loaded: true,
|
||||
pageNumber: 0,
|
||||
rows: page.rows,
|
||||
bookmarks: page.hasNextPage ? [null, page.bookmark] : [null],
|
||||
}))
|
||||
}
|
||||
|
||||
// Fetches the next page of data
|
||||
const nextPage = async () => {
|
||||
const state = get(derivedStore)
|
||||
if (state.loading || !options.paginate || !state.hasNextPage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch next page
|
||||
store.update($store => ({ ...$store, loading: true }))
|
||||
const page = await fetchPage(state.bookmarks[state.pageNumber + 1])
|
||||
|
||||
// Update state
|
||||
store.update($store => {
|
||||
let { bookmarks, pageNumber } = $store
|
||||
if (page.hasNextPage) {
|
||||
bookmarks[pageNumber + 2] = page.bookmark
|
||||
}
|
||||
return {
|
||||
...$store,
|
||||
pageNumber: pageNumber + 1,
|
||||
rows: page.rows,
|
||||
bookmarks,
|
||||
loading: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Fetches the previous page of data
|
||||
const prevPage = async () => {
|
||||
const state = get(derivedStore)
|
||||
if (state.loading || !options.paginate || !state.hasPrevPage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch previous page
|
||||
store.update($store => ({ ...$store, loading: true }))
|
||||
const page = await fetchPage(state.bookmarks[state.pageNumber - 1])
|
||||
|
||||
// Update state
|
||||
store.update($store => {
|
||||
return {
|
||||
...$store,
|
||||
pageNumber: $store.pageNumber - 1,
|
||||
rows: page.rows,
|
||||
loading: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Resets the data set and updates options
|
||||
const update = async newOptions => {
|
||||
if (newOptions) {
|
||||
options = {
|
||||
...options,
|
||||
...newOptions,
|
||||
}
|
||||
}
|
||||
await fetchData()
|
||||
}
|
||||
|
||||
// Loads the same page again
|
||||
const refresh = async () => {
|
||||
if (get(store).loading) {
|
||||
return
|
||||
}
|
||||
const page = await fetchPage(lastBookmark)
|
||||
store.update($store => ({ ...$store, rows: page.rows }))
|
||||
}
|
||||
|
||||
// Initially fetch data but don't bother waiting for the result
|
||||
fetchData()
|
||||
|
||||
// Return our derived store which will be updated over time
|
||||
return {
|
||||
subscribe: derivedStore.subscribe,
|
||||
nextPage,
|
||||
prevPage,
|
||||
update,
|
||||
refresh,
|
||||
}
|
||||
}
|
|
@ -2,12 +2,7 @@
|
|||
import { isActive, redirect, params } from "@roxi/routify"
|
||||
import { admin, auth } from "stores/portal"
|
||||
import { onMount } from "svelte"
|
||||
import {
|
||||
Cookies,
|
||||
getCookie,
|
||||
removeCookie,
|
||||
setCookie,
|
||||
} from "builderStore/cookies"
|
||||
import { CookieUtils, Constants } from "@budibase/frontend-core"
|
||||
|
||||
let loaded = false
|
||||
|
||||
|
@ -46,9 +41,12 @@
|
|||
|
||||
if (user.tenantId !== urlTenantId) {
|
||||
// user should not be here - play it safe and log them out
|
||||
await auth.logout()
|
||||
await auth.setOrganisation(null)
|
||||
return
|
||||
try {
|
||||
await auth.logout()
|
||||
await auth.setOrganisation(null)
|
||||
} catch (error) {
|
||||
// Swallow error and do nothing
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no user - set the org according to the url
|
||||
|
@ -57,17 +55,23 @@
|
|||
}
|
||||
|
||||
onMount(async () => {
|
||||
if ($params["?template"]) {
|
||||
await auth.setInitInfo({ init_template: $params["?template"] })
|
||||
try {
|
||||
await auth.getSelf()
|
||||
await admin.init()
|
||||
|
||||
// Set init info if present
|
||||
if ($params["?template"]) {
|
||||
await auth.setInitInfo({ init_template: $params["?template"] })
|
||||
}
|
||||
|
||||
// Validate tenant if in a multi-tenant env
|
||||
if (useAccountPortal && multiTenancyEnabled) {
|
||||
await validateTenantId()
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't show a notification here, as we might 403 initially due to not
|
||||
// being logged in
|
||||
}
|
||||
|
||||
await auth.getSelf()
|
||||
await admin.init()
|
||||
|
||||
if (useAccountPortal && multiTenancyEnabled) {
|
||||
await validateTenantId()
|
||||
}
|
||||
|
||||
loaded = true
|
||||
})
|
||||
|
||||
|
@ -79,7 +83,7 @@
|
|||
loaded &&
|
||||
apiReady &&
|
||||
!$auth.user &&
|
||||
!getCookie(Cookies.ReturnUrl) &&
|
||||
!CookieUtils.getCookie(Constants.Cookies.ReturnUrl) &&
|
||||
// logout triggers a page refresh, so we don't want to set the return url
|
||||
!$auth.postLogout &&
|
||||
// don't set the return url on pre-login pages
|
||||
|
@ -88,7 +92,7 @@
|
|||
!$isActive("./admin")
|
||||
) {
|
||||
const url = window.location.pathname
|
||||
setCookie(Cookies.ReturnUrl, url)
|
||||
CookieUtils.setCookie(Constants.Cookies.ReturnUrl, url)
|
||||
}
|
||||
|
||||
// if tenant is not set go to it
|
||||
|
@ -122,9 +126,9 @@
|
|||
}
|
||||
// lastly, redirect to the return url if it has been set
|
||||
else if (loaded && apiReady && $auth.user) {
|
||||
const returnUrl = getCookie(Cookies.ReturnUrl)
|
||||
const returnUrl = CookieUtils.getCookie(Constants.Cookies.ReturnUrl)
|
||||
if (returnUrl) {
|
||||
removeCookie(Cookies.ReturnUrl)
|
||||
CookieUtils.removeCookie(Constants.Cookies.ReturnUrl)
|
||||
window.location.href = returnUrl
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { notifications, ModalContent, Dropzone, Body } from "@budibase/bbui"
|
||||
import { post } from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { admin } from "stores/portal"
|
||||
|
||||
let submitting = false
|
||||
|
@ -9,24 +9,19 @@
|
|||
|
||||
async function importApps() {
|
||||
submitting = true
|
||||
|
||||
try {
|
||||
// Create form data to create app
|
||||
let data = new FormData()
|
||||
data.append("importFile", value.file)
|
||||
|
||||
// Create App
|
||||
const importResp = await post("/api/cloud/import", data, {})
|
||||
const importJson = await importResp.json()
|
||||
if (!importResp.ok) {
|
||||
throw new Error(importJson.message)
|
||||
}
|
||||
await API.importApps(data)
|
||||
await admin.checkImportComplete()
|
||||
notifications.success("Import complete, please finish registration!")
|
||||
} catch (error) {
|
||||
notifications.error(error)
|
||||
submitting = false
|
||||
notifications.error("Failed to import apps")
|
||||
}
|
||||
submitting = false
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -36,10 +31,10 @@
|
|||
onConfirm={importApps}
|
||||
disabled={!value.file}
|
||||
>
|
||||
<Body
|
||||
>Please upload the file that was exported from your Cloud environment to get
|
||||
started</Body
|
||||
>
|
||||
<Body>
|
||||
Please upload the file that was exported from your Cloud environment to get
|
||||
started
|
||||
</Body>
|
||||
<Dropzone
|
||||
gallery={false}
|
||||
label="File to import"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
Modal,
|
||||
} from "@budibase/bbui"
|
||||
import { goto } from "@roxi/routify"
|
||||
import api from "builderStore/api"
|
||||
import { API } from "api"
|
||||
import { admin, auth } from "stores/portal"
|
||||
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||
import ImportAppsModal from "./_components/ImportAppsModal.svelte"
|
||||
|
@ -30,22 +30,22 @@
|
|||
try {
|
||||
adminUser.tenantId = tenantId
|
||||
// Save the admin user
|
||||
const response = await api.post(`/api/global/users/init`, adminUser)
|
||||
const json = await response.json()
|
||||
if (response.status !== 200) {
|
||||
throw new Error(json.message)
|
||||
}
|
||||
notifications.success(`Admin user created`)
|
||||
await API.createAdminUser(adminUser)
|
||||
notifications.success("Admin user created")
|
||||
await admin.init()
|
||||
$goto("../portal")
|
||||
} catch (err) {
|
||||
notifications.error(`Failed to create admin user: ${err}`)
|
||||
} catch (error) {
|
||||
notifications.error("Failed to create admin user")
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (!cloud) {
|
||||
await admin.checkImportComplete()
|
||||
try {
|
||||
await admin.checkImportComplete()
|
||||
} catch (error) {
|
||||
notifications.error("Error checking import status")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -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, onDestroy } 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,24 @@
|
|||
}
|
||||
|
||||
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) {
|
||||
notifications.error(`Error initialising app: ${error?.message}`)
|
||||
$redirect("../../")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 +65,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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue