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