commit
41d0991b14
|
@ -43,13 +43,13 @@ jobs:
|
||||||
verbose: true
|
verbose: true
|
||||||
- run: yarn test:e2e:ci
|
- run: yarn test:e2e:ci
|
||||||
|
|
||||||
- name: Build and Push Staging Docker Image
|
- name: Build and Push Development Docker Image
|
||||||
# Only run on push
|
# Only run on push
|
||||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||||
run: |
|
run: |
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
||||||
yarn build
|
yarn build
|
||||||
yarn build:docker:staging
|
yarn build:docker:develop
|
||||||
env:
|
env:
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||||
|
|
|
@ -29,6 +29,7 @@ services:
|
||||||
- ./logs:/logs
|
- ./logs:/logs
|
||||||
depends_on:
|
depends_on:
|
||||||
- worker-service
|
- worker-service
|
||||||
|
- redis-service
|
||||||
|
|
||||||
worker-service:
|
worker-service:
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
@ -38,6 +38,11 @@ static_resources:
|
||||||
route:
|
route:
|
||||||
cluster: server-dev
|
cluster: server-dev
|
||||||
|
|
||||||
|
- match: { prefix: "/app/" }
|
||||||
|
route:
|
||||||
|
cluster: server-dev
|
||||||
|
prefix_rewrite: "/"
|
||||||
|
|
||||||
# the below three cases are needed to make sure
|
# the below three cases are needed to make sure
|
||||||
# all traffic prefixed for the builder is passed through
|
# all traffic prefixed for the builder is passed through
|
||||||
# correctly.
|
# correctly.
|
||||||
|
|
|
@ -8,9 +8,5 @@ echo "Tagging images with SHA: $GITHUB_SHA and tag: $tag"
|
||||||
docker tag app-service budibase/apps:$tag
|
docker tag app-service budibase/apps:$tag
|
||||||
docker tag worker-service budibase/worker:$tag
|
docker tag worker-service budibase/worker:$tag
|
||||||
|
|
||||||
# Tag with git sha
|
docker push budibase/apps:$tag
|
||||||
docker tag app-service budibase/apps:$GITHUB_SHA
|
docker push budibase/worker:$tag
|
||||||
docker tag worker-service budibase/worker:$GITHUB_SHA
|
|
||||||
|
|
||||||
docker push budibase/apps
|
|
||||||
docker push budibase/worker
|
|
||||||
|
|
|
@ -41,6 +41,6 @@
|
||||||
"test:e2e": "lerna run cy:test",
|
"test:e2e": "lerna run cy:test",
|
||||||
"test:e2e:ci": "lerna run cy:ci",
|
"test:e2e:ci": "lerna run cy:ci",
|
||||||
"build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
|
"build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
|
||||||
"build:docker:staging": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh staging && cd -"
|
"build:docker:develop": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,12 @@ function buildNoAuthRegex(patterns) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function finalise(ctx, { authenticated, user, internal } = {}) {
|
||||||
|
ctx.isAuthenticated = authenticated || false
|
||||||
|
ctx.user = user
|
||||||
|
ctx.internal = internal || false
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = (noAuthPatterns = [], opts) => {
|
module.exports = (noAuthPatterns = [], opts) => {
|
||||||
const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : []
|
const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : []
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
|
@ -36,35 +42,39 @@ module.exports = (noAuthPatterns = [], opts) => {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const apiKey = ctx.request.headers["x-budibase-api-key"]
|
|
||||||
// check the actual user is authenticated first
|
// check the actual user is authenticated first
|
||||||
const authCookie = getCookie(ctx, Cookies.Auth)
|
const authCookie = getCookie(ctx, Cookies.Auth)
|
||||||
|
let authenticated = false,
|
||||||
// this is an internal request, no user made it
|
user = null,
|
||||||
if (apiKey && apiKey === env.INTERNAL_API_KEY) {
|
internal = false
|
||||||
ctx.isAuthenticated = true
|
if (authCookie) {
|
||||||
ctx.internal = true
|
|
||||||
} else if (authCookie) {
|
|
||||||
try {
|
try {
|
||||||
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||||
const user = await db.get(authCookie.userId)
|
user = await db.get(authCookie.userId)
|
||||||
delete user.password
|
delete user.password
|
||||||
ctx.isAuthenticated = true
|
authenticated = true
|
||||||
ctx.user = user
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// remove the cookie as the use does not exist anymore
|
// remove the cookie as the use does not exist anymore
|
||||||
clearCookie(ctx, Cookies.Auth)
|
clearCookie(ctx, Cookies.Auth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// be explicit
|
const apiKey = ctx.request.headers["x-budibase-api-key"]
|
||||||
if (ctx.isAuthenticated !== true) {
|
// this is an internal request, no user made it
|
||||||
ctx.isAuthenticated = false
|
if (!authenticated && apiKey && apiKey === env.INTERNAL_API_KEY) {
|
||||||
|
authenticated = true
|
||||||
|
internal = true
|
||||||
}
|
}
|
||||||
|
// be explicit
|
||||||
|
if (authenticated !== true) {
|
||||||
|
authenticated = false
|
||||||
|
}
|
||||||
|
// isAuthenticated is a function, so use a variable to be able to check authed state
|
||||||
|
finalise(ctx, { authenticated, user, internal })
|
||||||
return next()
|
return next()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// allow configuring for public access
|
// allow configuring for public access
|
||||||
if (opts && opts.publicAllowed) {
|
if (opts && opts.publicAllowed) {
|
||||||
ctx.isAuthenticated = false
|
finalise(ctx, { authenticated: false })
|
||||||
} else {
|
} else {
|
||||||
ctx.throw(err.status || 403, err)
|
ctx.throw(err.status || 403, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,9 +63,9 @@
|
||||||
on:focus={() => (focus = true)}
|
on:focus={() => (focus = true)}
|
||||||
on:blur={() => (focus = false)}
|
on:blur={() => (focus = false)}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
{value}
|
value={value || ""}
|
||||||
|
placeholder={placeholder || ""}
|
||||||
{disabled}
|
{disabled}
|
||||||
{placeholder}
|
|
||||||
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,9 +27,17 @@
|
||||||
visible = false
|
visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cancel() {
|
||||||
|
if (!visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dispatch("cancel")
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
|
||||||
function handleKey(e) {
|
function handleKey(e) {
|
||||||
if (visible && e.key === "Escape") {
|
if (visible && e.key === "Escape") {
|
||||||
hide()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +49,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext(Context.Modal, { show, hide })
|
setContext(Context.Modal, { show, hide, cancel })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKey} />
|
<svelte:window on:keydown={handleKey} />
|
||||||
|
@ -56,15 +64,17 @@
|
||||||
<Portal target=".modal-container">
|
<Portal target=".modal-container">
|
||||||
<div
|
<div
|
||||||
class="spectrum-Underlay is-open"
|
class="spectrum-Underlay is-open"
|
||||||
transition:fade|local={{ duration: 200 }}
|
in:fade={{ duration: 200 }}
|
||||||
on:mousedown|self={hide}
|
out:fade|local={{ duration: 200 }}
|
||||||
|
on:mousedown|self={cancel}
|
||||||
>
|
>
|
||||||
<div class="modal-wrapper" on:mousedown|self={hide}>
|
<div class="modal-wrapper" on:mousedown|self={cancel}>
|
||||||
<div class="modal-inner-wrapper" on:mousedown|self={hide}>
|
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
|
||||||
<div
|
<div
|
||||||
use:focusFirstInput
|
use:focusFirstInput
|
||||||
class="spectrum-Modal is-open"
|
class="spectrum-Modal is-open"
|
||||||
transition:fly|local={{ y: 30, duration: 200 }}
|
in:fly={{ y: 30, duration: 200 }}
|
||||||
|
out:fly|local={{ y: 30, duration: 200 }}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
export let onConfirm = undefined
|
export let onConfirm = undefined
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
|
||||||
const { hide } = getContext(Context.Modal)
|
const { hide, cancel } = getContext(Context.Modal)
|
||||||
let loading = false
|
let loading = false
|
||||||
$: confirmDisabled = disabled || loading
|
$: confirmDisabled = disabled || loading
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
>
|
>
|
||||||
<slot name="footer" />
|
<slot name="footer" />
|
||||||
{#if showCancelButton}
|
{#if showCancelButton}
|
||||||
<Button group secondary on:click={hide}>{cancelText}</Button>
|
<Button group secondary on:click={cancel}>{cancelText}</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if showConfirmButton}
|
{#if showConfirmButton}
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
export let value
|
export let value
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>{value}</div>
|
<div>{typeof value === "object" ? JSON.stringify(value) : value}</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
|
|
|
@ -359,7 +359,7 @@
|
||||||
background-color: var(--spectrum-alias-background-color-secondary);
|
background-color: var(--spectrum-alias-background-color-secondary);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
|
|
@ -20,6 +20,7 @@ process.env.MINIO_ACCESS_KEY = "budibase"
|
||||||
process.env.MINIO_SECRET_KEY = "budibase"
|
process.env.MINIO_SECRET_KEY = "budibase"
|
||||||
process.env.COUCH_DB_USER = "budibase"
|
process.env.COUCH_DB_USER = "budibase"
|
||||||
process.env.COUCH_DB_PASSWORD = "budibase"
|
process.env.COUCH_DB_PASSWORD = "budibase"
|
||||||
|
process.env.INTERNAL_API_KEY = "budibase"
|
||||||
|
|
||||||
// Stop info logs polluting test outputs
|
// Stop info logs polluting test outputs
|
||||||
process.env.LOG_LEVEL = "error"
|
process.env.LOG_LEVEL = "error"
|
||||||
|
|
|
@ -37,7 +37,7 @@ Cypress.Commands.add("createApp", name => {
|
||||||
cy.contains("Create app").click()
|
cy.contains("Create app").click()
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.get("[data-cy=new-table]", {
|
cy.get(".selected > .content", {
|
||||||
timeout: 20000,
|
timeout: 20000,
|
||||||
}).should("be.visible")
|
}).should("be.visible")
|
||||||
})
|
})
|
||||||
|
@ -51,7 +51,7 @@ Cypress.Commands.add("deleteApp", () => {
|
||||||
.then(val => {
|
.then(val => {
|
||||||
console.log(val)
|
console.log(val)
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
cy.get(".hoverable > use").click()
|
cy.get(".title > :nth-child(3) > .spectrum-Icon").click()
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.get(".spectrum-Button--warning").click()
|
cy.get(".spectrum-Button--warning").click()
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,8 @@ Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
|
|
||||||
Cypress.Commands.add("createTable", tableName => {
|
Cypress.Commands.add("createTable", tableName => {
|
||||||
// Enter table name
|
// Enter table name
|
||||||
cy.get("[data-cy=new-table]").click()
|
cy.contains("Budibase DB").click()
|
||||||
|
cy.contains("Create new table").click()
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get("input").first().type(tableName).blur()
|
cy.get("input").first().type(tableName).blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
"posthog-js": "1.4.5",
|
"posthog-js": "1.4.5",
|
||||||
"remixicon": "2.5.0",
|
"remixicon": "2.5.0",
|
||||||
"shortid": "2.2.15",
|
"shortid": "2.2.15",
|
||||||
"svelte-dnd-action": "^0.8.9",
|
"svelte-dnd-action": "^0.9.8",
|
||||||
"svelte-loading-spinners": "^0.1.1",
|
"svelte-loading-spinners": "^0.1.1",
|
||||||
"svelte-portal": "0.1.0",
|
"svelte-portal": "0.1.0",
|
||||||
"uuid": "8.3.1",
|
"uuid": "8.3.1",
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { initialise } from "builderStore"
|
import { initialise } from "builderStore"
|
||||||
import { NotificationDisplay } from "@budibase/bbui"
|
import { NotificationDisplay } from "@budibase/bbui"
|
||||||
import { parse, stringify } from "qs"
|
import { parse, stringify } from "qs"
|
||||||
|
import HelpIcon from "components/common/HelpIcon.svelte"
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await initialise()
|
await initialise()
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
<Router {routes} config={{ queryHandler }} />
|
<Router {routes} config={{ queryHandler }} />
|
||||||
<div class="modal-container" />
|
<div class="modal-container" />
|
||||||
|
<HelpIcon />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.modal-container {
|
.modal-container {
|
||||||
|
|
|
@ -22,6 +22,7 @@ async function activate() {
|
||||||
if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN })
|
if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN })
|
||||||
if (posthogConfigured) {
|
if (posthogConfigured) {
|
||||||
posthog.init(process.env.POSTHOG_TOKEN, {
|
posthog.init(process.env.POSTHOG_TOKEN, {
|
||||||
|
autocapture: false,
|
||||||
api_host: process.env.POSTHOG_URL,
|
api_host: process.env.POSTHOG_URL,
|
||||||
})
|
})
|
||||||
posthog.set_config({ persistence: "cookie" })
|
posthog.set_config({ persistence: "cookie" })
|
||||||
|
|
|
@ -333,7 +333,7 @@ const buildFormSchema = component => {
|
||||||
*/
|
*/
|
||||||
export function removeBindings(obj) {
|
export function removeBindings(obj) {
|
||||||
for (let [key, value] of Object.entries(obj)) {
|
for (let [key, value] of Object.entries(obj)) {
|
||||||
if (typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
obj[key] = removeBindings(value)
|
obj[key] = removeBindings(value)
|
||||||
} else if (typeof value === "string") {
|
} else if (typeof value === "string") {
|
||||||
obj[key] = value.replace(CAPTURE_HBS_TEMPLATE, "Invalid binding")
|
obj[key] = value.replace(CAPTURE_HBS_TEMPLATE, "Invalid binding")
|
||||||
|
|
|
@ -305,7 +305,6 @@ export const getFrontendStore = () => {
|
||||||
_id: uuid(),
|
_id: uuid(),
|
||||||
_component: definition.component,
|
_component: definition.component,
|
||||||
_styles: { normal: {}, hover: {}, active: {} },
|
_styles: { normal: {}, hover: {}, active: {} },
|
||||||
_transition: "",
|
|
||||||
_instanceName: `New ${definition.name}`,
|
_instanceName: `New ${definition.name}`,
|
||||||
...cloneDeep(props),
|
...cloneDeep(props),
|
||||||
...extras,
|
...extras,
|
||||||
|
@ -508,15 +507,6 @@ export const getFrontendStore = () => {
|
||||||
selected._styles = { normal: {}, hover: {}, active: {} }
|
selected._styles = { normal: {}, hover: {}, active: {} }
|
||||||
await store.actions.preview.saveSelected()
|
await store.actions.preview.saveSelected()
|
||||||
},
|
},
|
||||||
updateTransition: async transition => {
|
|
||||||
const selected = get(selectedComponent)
|
|
||||||
if (transition == null || transition === "") {
|
|
||||||
selected._transition = ""
|
|
||||||
} else {
|
|
||||||
selected._transition = transition
|
|
||||||
}
|
|
||||||
await store.actions.preview.saveSelected()
|
|
||||||
},
|
|
||||||
updateProp: async (name, value) => {
|
updateProp: async (name, value) => {
|
||||||
let component = get(selectedComponent)
|
let component = get(selectedComponent)
|
||||||
if (!name || !component) {
|
if (!name || !component) {
|
||||||
|
@ -536,37 +526,50 @@ export const getFrontendStore = () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a nav bar in the main layout
|
// Add link setting to main layout
|
||||||
const nav = findComponentType(
|
if (layout.props._component.endsWith("layout")) {
|
||||||
layout.props,
|
// If using a new SDK, add to the layout component settings
|
||||||
"@budibase/standard-components/navigation"
|
if (!layout.props.links) {
|
||||||
)
|
layout.props.links = []
|
||||||
if (!nav) {
|
}
|
||||||
return
|
layout.props.links.push({
|
||||||
}
|
|
||||||
|
|
||||||
let newLink
|
|
||||||
if (nav._children && nav._children.length) {
|
|
||||||
// Clone an existing link if one exists
|
|
||||||
newLink = cloneDeep(nav._children[0])
|
|
||||||
|
|
||||||
// Set our new props
|
|
||||||
newLink._id = uuid()
|
|
||||||
newLink._instanceName = `${title} Link`
|
|
||||||
newLink.url = url
|
|
||||||
newLink.text = title
|
|
||||||
} else {
|
|
||||||
// Otherwise create vanilla new link
|
|
||||||
newLink = {
|
|
||||||
...store.actions.components.createInstance("link"),
|
|
||||||
url,
|
|
||||||
text: title,
|
text: title,
|
||||||
_instanceName: `${title} Link`,
|
url,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// If using an old SDK, add to the navigation component
|
||||||
|
// TODO: remove this when we can assume everyone has updated
|
||||||
|
const nav = findComponentType(
|
||||||
|
layout.props,
|
||||||
|
"@budibase/standard-components/navigation"
|
||||||
|
)
|
||||||
|
if (!nav) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let newLink
|
||||||
|
if (nav._children && nav._children.length) {
|
||||||
|
// Clone an existing link if one exists
|
||||||
|
newLink = cloneDeep(nav._children[0])
|
||||||
|
|
||||||
|
// Set our new props
|
||||||
|
newLink._id = uuid()
|
||||||
|
newLink._instanceName = `${title} Link`
|
||||||
|
newLink.url = url
|
||||||
|
newLink.text = title
|
||||||
|
} else {
|
||||||
|
// Otherwise create vanilla new link
|
||||||
|
newLink = {
|
||||||
|
...store.actions.components.createInstance("link"),
|
||||||
|
url,
|
||||||
|
text: title,
|
||||||
|
_instanceName: `${title} Link`,
|
||||||
|
}
|
||||||
|
nav._children = [...nav._children, newLink]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save layout
|
// Save layout
|
||||||
nav._children = [...nav._children, newLink]
|
|
||||||
await store.actions.layouts.save(layout)
|
await store.actions.layouts.save(layout)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -62,6 +62,7 @@ function generateTitleContainer(table, title, formId, repeaterId) {
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
rowId: `{{ ${makePropSafe(repeaterId)}.${makePropSafe("_id")} }}`,
|
rowId: `{{ ${makePropSafe(repeaterId)}.${makePropSafe("_id")} }}`,
|
||||||
revId: `{{ ${makePropSafe(repeaterId)}.${makePropSafe("_rev")} }}`,
|
revId: `{{ ${makePropSafe(repeaterId)}.${makePropSafe("_rev")} }}`,
|
||||||
|
confirm: true,
|
||||||
},
|
},
|
||||||
"##eventHandlerType": "Delete Row",
|
"##eventHandlerType": "Delete Row",
|
||||||
},
|
},
|
||||||
|
@ -84,7 +85,7 @@ const createScreen = table => {
|
||||||
.customProps({
|
.customProps({
|
||||||
dataSource: {
|
dataSource: {
|
||||||
label: table.name,
|
label: table.name,
|
||||||
name: `all_${table._id}`,
|
name: table._id,
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
},
|
},
|
||||||
|
|
|
@ -76,7 +76,7 @@ const createScreen = table => {
|
||||||
.customProps({
|
.customProps({
|
||||||
dataSource: {
|
dataSource: {
|
||||||
label: table.name,
|
label: table.name,
|
||||||
name: `all_${table._id}`,
|
name: table._id,
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
},
|
},
|
||||||
|
@ -128,7 +128,6 @@ const createScreen = table => {
|
||||||
background: "white",
|
background: "white",
|
||||||
"border-radius": "0.5rem",
|
"border-radius": "0.5rem",
|
||||||
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
||||||
"margin-top": "20px",
|
|
||||||
"border-width": "2px",
|
"border-width": "2px",
|
||||||
"border-color": "rgba(0, 0, 0, 0.1)",
|
"border-color": "rgba(0, 0, 0, 0.1)",
|
||||||
"border-style": "None",
|
"border-style": "None",
|
||||||
|
@ -136,7 +135,6 @@ const createScreen = table => {
|
||||||
"padding-bottom": "48px",
|
"padding-bottom": "48px",
|
||||||
"padding-right": "48px",
|
"padding-right": "48px",
|
||||||
"padding-left": "48px",
|
"padding-left": "48px",
|
||||||
"margin-bottom": "20px",
|
|
||||||
})
|
})
|
||||||
.customProps({
|
.customProps({
|
||||||
direction: "column",
|
direction: "column",
|
||||||
|
|
|
@ -14,7 +14,6 @@ export class Component extends BaseStructure {
|
||||||
active: {},
|
active: {},
|
||||||
selected: {},
|
selected: {},
|
||||||
},
|
},
|
||||||
_transition: "",
|
|
||||||
_instanceName: "",
|
_instanceName: "",
|
||||||
_children: [],
|
_children: [],
|
||||||
}
|
}
|
||||||
|
@ -40,11 +39,6 @@ export class Component extends BaseStructure {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
transition(transition) {
|
|
||||||
this._json._transition = transition
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for custom props "type"
|
// Shorthand for custom props "type"
|
||||||
type(type) {
|
type(type) {
|
||||||
this._json.type = type
|
this._json.type = type
|
||||||
|
|
|
@ -40,12 +40,10 @@ export function makeMainForm() {
|
||||||
padding: "0px",
|
padding: "0px",
|
||||||
"border-radius": "0.5rem",
|
"border-radius": "0.5rem",
|
||||||
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
||||||
"margin-top": "20px",
|
|
||||||
"padding-top": "48px",
|
"padding-top": "48px",
|
||||||
"padding-bottom": "48px",
|
"padding-bottom": "48px",
|
||||||
"padding-right": "48px",
|
"padding-right": "48px",
|
||||||
"padding-left": "48px",
|
"padding-left": "48px",
|
||||||
"margin-bottom": "20px",
|
|
||||||
})
|
})
|
||||||
.instanceName("Form")
|
.instanceName("Form")
|
||||||
}
|
}
|
||||||
|
@ -58,6 +56,8 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
|
||||||
.normalStyle({
|
.normalStyle({
|
||||||
"margin-right": "4px",
|
"margin-right": "4px",
|
||||||
"margin-left": "4px",
|
"margin-left": "4px",
|
||||||
|
"margin-top": "0px",
|
||||||
|
"margin-bottom": "0px",
|
||||||
})
|
})
|
||||||
.customStyle(spectrumColor(700))
|
.customStyle(spectrumColor(700))
|
||||||
.text(">")
|
.text(">")
|
||||||
|
@ -65,6 +65,8 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
|
||||||
|
|
||||||
const textStyling = {
|
const textStyling = {
|
||||||
color: "#000000",
|
color: "#000000",
|
||||||
|
"margin-top": "0px",
|
||||||
|
"margin-bottom": "0px",
|
||||||
}
|
}
|
||||||
if (capitalise) {
|
if (capitalise) {
|
||||||
textStyling["text-transform"] = "capitalize"
|
textStyling["text-transform"] = "capitalize"
|
||||||
|
@ -140,10 +142,6 @@ export function makeTitleContainer(title) {
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
const heading = new Component("@budibase/standard-components/heading")
|
||||||
.normalStyle({
|
.normalStyle({
|
||||||
margin: "0px",
|
margin: "0px",
|
||||||
"margin-bottom": "0px",
|
|
||||||
"margin-right": "0px",
|
|
||||||
"margin-top": "0px",
|
|
||||||
"margin-left": "0px",
|
|
||||||
flex: "1 1 auto",
|
flex: "1 1 auto",
|
||||||
})
|
})
|
||||||
.customStyle(spectrumColor(900))
|
.customStyle(spectrumColor(900))
|
||||||
|
|
|
@ -17,31 +17,31 @@
|
||||||
let data = []
|
let data = []
|
||||||
let loading = false
|
let loading = false
|
||||||
$: isUsersTable = $tables.selected?._id === TableNames.USERS
|
$: isUsersTable = $tables.selected?._id === TableNames.USERS
|
||||||
$: title = $tables.selected.name
|
$: title = $tables.selected?.name
|
||||||
$: schema = $tables.selected.schema
|
$: schema = $tables.selected?.schema
|
||||||
$: tableView = {
|
$: tableView = {
|
||||||
schema,
|
schema,
|
||||||
name: $views.selected?.name,
|
name: $views.selected?.name,
|
||||||
}
|
}
|
||||||
|
$: type = $tables.selected?.type
|
||||||
|
$: isInternal = type === "internal"
|
||||||
|
|
||||||
// Fetch rows for specified table
|
// Fetch rows for specified table
|
||||||
$: {
|
$: {
|
||||||
if ($views.selected?.name?.startsWith("all_")) {
|
loading = true
|
||||||
loading = true
|
const loadingTableId = $tables.selected?._id
|
||||||
const loadingTableId = $tables.selected?._id
|
api.fetchDataForTable($tables.selected?._id).then(rows => {
|
||||||
api.fetchDataForView($views.selected).then(rows => {
|
loading = false
|
||||||
loading = false
|
|
||||||
|
|
||||||
// If we started a slow request then quickly change table, sometimes
|
// If we started a slow request then quickly change table, sometimes
|
||||||
// the old data overwrites the new data.
|
// the old data overwrites the new data.
|
||||||
// This check ensures that we don't do that.
|
// This check ensures that we don't do that.
|
||||||
if (loadingTableId !== $tables.selected?._id) {
|
if (loadingTableId !== $tables.selected?._id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data = rows || []
|
data = rows || []
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -50,11 +50,14 @@
|
||||||
{schema}
|
{schema}
|
||||||
tableId={$tables.selected?._id}
|
tableId={$tables.selected?._id}
|
||||||
{data}
|
{data}
|
||||||
|
{type}
|
||||||
allowEditing={true}
|
allowEditing={true}
|
||||||
bind:hideAutocolumns
|
bind:hideAutocolumns
|
||||||
{loading}
|
{loading}
|
||||||
>
|
>
|
||||||
<CreateColumnButton />
|
{#if isInternal}
|
||||||
|
<CreateColumnButton />
|
||||||
|
{/if}
|
||||||
{#if schema && Object.keys(schema).length > 0}
|
{#if schema && Object.keys(schema).length > 0}
|
||||||
{#if !isUsersTable}
|
{#if !isUsersTable}
|
||||||
<CreateRowButton
|
<CreateRowButton
|
||||||
|
@ -62,13 +65,17 @@
|
||||||
modalContentComponent={CreateEditRow}
|
modalContentComponent={CreateEditRow}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<CreateViewButton />
|
{#if isInternal}
|
||||||
|
<CreateViewButton />
|
||||||
|
{/if}
|
||||||
<ManageAccessButton resourceId={$tables.selected?._id} />
|
<ManageAccessButton resourceId={$tables.selected?._id} />
|
||||||
{#if isUsersTable}
|
{#if isUsersTable}
|
||||||
<EditRolesButton />
|
<EditRolesButton />
|
||||||
{/if}
|
{/if}
|
||||||
<HideAutocolumnButton bind:hideAutocolumns />
|
{#if isInternal}
|
||||||
|
<HideAutocolumnButton bind:hideAutocolumns />
|
||||||
|
{/if}
|
||||||
<!-- always have the export last -->
|
<!-- always have the export last -->
|
||||||
<ExportButton view={tableView} />
|
<ExportButton view={$tables.selected?._id} />
|
||||||
{/if}
|
{/if}
|
||||||
</Table>
|
</Table>
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
|
|
||||||
let loading = false
|
let loading = false
|
||||||
let error = false
|
let error = false
|
||||||
|
let type = "external"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
<div class="errors">{error}</div>
|
<div class="errors">{error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Table schema={query.schema} {data} {loading} rowCount={5} />
|
<Table schema={query.schema} {data} {loading} {type} rowCount={5} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.errors {
|
.errors {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
$: linkedTable = $tables.list.find(table => table._id === linkedTableId)
|
$: linkedTable = $tables.list.find(table => table._id === linkedTableId)
|
||||||
$: schema = linkedTable?.schema
|
$: schema = linkedTable?.schema
|
||||||
$: table = $tables.list.find(table => table._id === tableId)
|
$: table = $tables.list.find(table => table._id === tableId)
|
||||||
|
$: type = table?.type
|
||||||
$: fetchData(tableId, rowId)
|
$: fetchData(tableId, rowId)
|
||||||
$: {
|
$: {
|
||||||
let rowLabel = row?.[table?.primaryDisplay]
|
let rowLabel = row?.[table?.primaryDisplay]
|
||||||
|
@ -33,5 +34,5 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if row && row._id === rowId}
|
{#if row && row._id === rowId}
|
||||||
<Table {title} {schema} {data} />
|
<Table {title} {schema} {data} {type} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
export let loading = false
|
export let loading = false
|
||||||
export let hideAutocolumns
|
export let hideAutocolumns
|
||||||
export let rowCount
|
export let rowCount
|
||||||
|
export let type
|
||||||
|
|
||||||
let selectedRows = []
|
let selectedRows = []
|
||||||
let editableColumn
|
let editableColumn
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
let editColumnModal
|
let editColumnModal
|
||||||
let customRenderers = []
|
let customRenderers = []
|
||||||
|
|
||||||
|
$: isInternal = type !== "external"
|
||||||
$: isUsersTable = tableId === TableNames.USERS
|
$: isUsersTable = tableId === TableNames.USERS
|
||||||
$: data && resetSelectedRows()
|
$: data && resetSelectedRows()
|
||||||
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
||||||
|
@ -73,9 +75,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRows = async () => {
|
const deleteRows = async () => {
|
||||||
await api.post(`/api/${tableId}/rows`, {
|
await api.delete(`/api/${tableId}/rows`, {
|
||||||
rows: selectedRows,
|
rows: selectedRows,
|
||||||
type: "delete",
|
|
||||||
})
|
})
|
||||||
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`)
|
||||||
|
@ -125,7 +126,7 @@
|
||||||
bind:selectedRows
|
bind:selectedRows
|
||||||
allowSelectRows={allowEditing && !isUsersTable}
|
allowSelectRows={allowEditing && !isUsersTable}
|
||||||
allowEditRows={allowEditing}
|
allowEditRows={allowEditing}
|
||||||
allowEditColumns={allowEditing}
|
allowEditColumns={allowEditing && isInternal}
|
||||||
showAutoColumns={!hideAutocolumns}
|
showAutoColumns={!hideAutocolumns}
|
||||||
on:editcolumn={e => editColumn(e.detail)}
|
on:editcolumn={e => editColumn(e.detail)}
|
||||||
on:editrow={e => editRow(e.detail)}
|
on:editrow={e => editRow(e.detail)}
|
||||||
|
|
|
@ -11,19 +11,18 @@
|
||||||
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
let hideAutocolumns = true
|
|
||||||
|
|
||||||
|
let hideAutocolumns = true
|
||||||
let data = []
|
let data = []
|
||||||
let loading = false
|
let loading = false
|
||||||
|
let type = "internal"
|
||||||
|
|
||||||
$: name = view.name
|
$: name = view.name
|
||||||
|
|
||||||
// Fetch rows for specified view
|
// Fetch rows for specified view
|
||||||
$: {
|
$: {
|
||||||
if (!name.startsWith("all_")) {
|
loading = true
|
||||||
loading = true
|
fetchViewData(name, view.field, view.groupBy, view.calculation)
|
||||||
fetchViewData(name, view.field, view.groupBy, view.calculation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchViewData(name, field, groupBy, calculation) {
|
async function fetchViewData(name, field, groupBy, calculation) {
|
||||||
|
@ -32,6 +31,7 @@
|
||||||
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) {
|
||||||
return
|
return
|
||||||
|
@ -57,6 +57,7 @@
|
||||||
tableId={view.tableId}
|
tableId={view.tableId}
|
||||||
{data}
|
{data}
|
||||||
{loading}
|
{loading}
|
||||||
|
{type}
|
||||||
allowEditing={!view?.calculation}
|
allowEditing={!view?.calculation}
|
||||||
bind:hideAutocolumns
|
bind:hideAutocolumns
|
||||||
>
|
>
|
||||||
|
|
|
@ -14,12 +14,15 @@ export async function saveRow(row, tableId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRow(row) {
|
export async function deleteRow(row) {
|
||||||
const DELETE_ROWS_URL = `/api/${row.tableId}/rows/${row._id}/${row._rev}`
|
const DELETE_ROWS_URL = `/api/${row.tableId}/rows`
|
||||||
return api.delete(DELETE_ROWS_URL)
|
return api.delete(DELETE_ROWS_URL, {
|
||||||
|
_id: row._id,
|
||||||
|
_rev: row._rev,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchDataForView(view) {
|
export async function fetchDataForTable(tableId) {
|
||||||
const FETCH_ROWS_URL = `/api/views/${view.name}`
|
const FETCH_ROWS_URL = `/api/${tableId}/rows`
|
||||||
|
|
||||||
const response = await api.get(FETCH_ROWS_URL)
|
const response = await api.get(FETCH_ROWS_URL)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
async function confirmDeletion() {
|
async function confirmDeletion() {
|
||||||
await deleteRows()
|
await deleteRows()
|
||||||
modal.hide()
|
modal?.hide()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,9 @@
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
|
||||||
$: viewTable = $tables.list.find(({ _id }) => _id === $views.selected.tableId)
|
$: viewTable = $tables.list.find(
|
||||||
|
({ _id }) => _id === $views.selected?.tableId
|
||||||
|
)
|
||||||
$: fields =
|
$: fields =
|
||||||
viewTable &&
|
viewTable &&
|
||||||
Object.keys(viewTable.schema).filter(
|
Object.keys(viewTable.schema).filter(
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
async function exportView() {
|
async function exportView() {
|
||||||
download(
|
download(
|
||||||
`/api/views/export?view=${encodeURIComponent(
|
`/api/views/export?view=${encodeURIComponent(
|
||||||
view.name
|
view
|
||||||
)}&format=${exportFormat}`
|
)}&format=${exportFormat}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,9 @@
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
|
||||||
$: viewTable = $tables.list.find(({ _id }) => _id === $views.selected.tableId)
|
$: viewTable = $tables.list.find(
|
||||||
|
({ _id }) => _id === $views.selected?.tableId
|
||||||
|
)
|
||||||
$: fields = viewTable && Object.keys(viewTable.schema)
|
$: fields = viewTable && Object.keys(viewTable.schema)
|
||||||
|
|
||||||
function saveView() {
|
function saveView() {
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
|
||||||
$: viewTable = $tables.list.find(({ _id }) => _id === $views.selected.tableId)
|
$: viewTable = $tables.list.find(
|
||||||
|
({ _id }) => _id === $views.selected?.tableId
|
||||||
|
)
|
||||||
$: fields =
|
$: fields =
|
||||||
viewTable &&
|
viewTable &&
|
||||||
Object.entries(viewTable.schema)
|
Object.entries(viewTable.schema)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
import { BUDIBASE_INTERNAL_DB } from "constants"
|
||||||
import { database, datasources, queries } from "stores/backend"
|
import { database, datasources, queries } from "stores/backend"
|
||||||
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
|
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
|
||||||
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
|
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
||||||
import ICONS from "./icons"
|
import ICONS from "./icons"
|
||||||
|
|
||||||
function selectDatasource(datasource) {
|
function selectDatasource(datasource) {
|
||||||
|
@ -13,9 +15,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickQuery(query) {
|
function onClickQuery(query) {
|
||||||
if ($queries.selected === query._id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
queries.select(query)
|
queries.select(query)
|
||||||
$goto(`./datasource/${query.datasourceId}/${query._id}`)
|
$goto(`./datasource/${query.datasourceId}/${query._id}`)
|
||||||
}
|
}
|
||||||
|
@ -42,8 +41,13 @@
|
||||||
width="18"
|
width="18"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<EditDatasourcePopover {datasource} />
|
{#if datasource._id !== BUDIBASE_INTERNAL_DB}
|
||||||
|
<EditDatasourcePopover {datasource} />
|
||||||
|
{/if}
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
||||||
|
<TableNavigator sourceId={datasource._id} />
|
||||||
|
|
||||||
{#each $queries.list.filter(query => query.datasourceId === datasource._id) as query}
|
{#each $queries.list.filter(query => query.datasourceId === datasource._id) as query}
|
||||||
<NavItem
|
<NavItem
|
||||||
indentLevel={1}
|
indentLevel={1}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
export let integration = {}
|
export let integration = {}
|
||||||
|
|
||||||
let schema
|
|
||||||
let integrations = []
|
let integrations = []
|
||||||
|
|
||||||
async function fetchIntegrations() {
|
async function fetchIntegrations() {
|
||||||
|
@ -18,13 +17,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectIntegration(integrationType) {
|
function selectIntegration(integrationType) {
|
||||||
schema = integrations[integrationType].datasource
|
const selected = integrations[integrationType]
|
||||||
|
|
||||||
|
// build the schema
|
||||||
|
const schema = {}
|
||||||
|
for (let key in selected.datasource) {
|
||||||
|
schema[key] = selected.datasource[key].default
|
||||||
|
}
|
||||||
|
|
||||||
integration = {
|
integration = {
|
||||||
type: integrationType,
|
type: integrationType,
|
||||||
...Object.keys(schema).reduce(
|
plus: selected.plus,
|
||||||
(acc, next) => ({ ...acc, [next]: schema[next].default }),
|
...schema,
|
||||||
{}
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
<script>
|
||||||
|
export let width = "100"
|
||||||
|
export let height = "100"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
style="enable-background:new 0 0 48 48;"
|
||||||
|
xml:space="preserve"
|
||||||
|
{height}
|
||||||
|
{width}
|
||||||
|
>
|
||||||
|
<style type="text/css">
|
||||||
|
.st0 {
|
||||||
|
fill: #393c44;
|
||||||
|
}
|
||||||
|
.st1 {
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
.st2 {
|
||||||
|
fill: #4285f4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<rect x="-152.17" y="-24.17" class="st0" width="96.17" height="96.17" />
|
||||||
|
<path
|
||||||
|
class="st1"
|
||||||
|
d="M-83.19,48h-41.79c-1.76,0-3.19-1.43-3.19-3.19V3.02c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||||
|
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st0"
|
||||||
|
d="M-99.62,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
||||||
|
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
|
C-93.55,28.92-93.46,28.52-93.46,28.11z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st0"
|
||||||
|
d="M-114.76,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58
|
||||||
|
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
||||||
|
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
||||||
|
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
|
C-108.68,28.92-108.6,28.52-108.6,28.11z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
class="st2"
|
||||||
|
d="M44.81,159H3.02c-1.76,0-3.19-1.43-3.19-3.19v-41.79c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||||
|
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st1"
|
||||||
|
d="M28.38,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
||||||
|
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
|
C34.45,139.92,34.54,139.52,34.54,139.11z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st1"
|
||||||
|
d="M13.24,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
||||||
|
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
||||||
|
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
||||||
|
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
||||||
|
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st0"
|
||||||
|
d="M44,48H4c-2.21,0-4-1.79-4-4V4c0-2.21,1.79-4,4-4h40c2.21,0,4,1.79,4,4v40C48,46.21,46.21,48,44,48z"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st1"
|
||||||
|
d="M28.48,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||||
|
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||||
|
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
||||||
|
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||||
|
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||||
|
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||||
|
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st1"
|
||||||
|
d="M13,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||||
|
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||||
|
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
||||||
|
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||||
|
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||||
|
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||||
|
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
|
@ -9,8 +9,10 @@ import SqlServer from "./SQLServer.svelte"
|
||||||
import MySQL from "./MySQL.svelte"
|
import MySQL from "./MySQL.svelte"
|
||||||
import ArangoDB from "./ArangoDB.svelte"
|
import ArangoDB from "./ArangoDB.svelte"
|
||||||
import Rest from "./Rest.svelte"
|
import Rest from "./Rest.svelte"
|
||||||
|
import Budibase from "./Budibase.svelte"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
BUDIBASE: Budibase,
|
||||||
POSTGRES: Postgres,
|
POSTGRES: Postgres,
|
||||||
DYNAMODB: DynamoDB,
|
DYNAMODB: DynamoDB,
|
||||||
MONGODB: MongoDB,
|
MONGODB: MongoDB,
|
||||||
|
|
|
@ -23,16 +23,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveDatasource() {
|
async function saveDatasource() {
|
||||||
const { type, ...config } = integration
|
const { type, plus, ...config } = integration
|
||||||
|
|
||||||
// Create datasource
|
// Create datasource
|
||||||
const response = await datasources.save({
|
const response = await datasources.save({
|
||||||
name,
|
name,
|
||||||
source: type,
|
source: type,
|
||||||
config,
|
config,
|
||||||
|
plus,
|
||||||
})
|
})
|
||||||
notifications.success(`Datasource ${name} created successfully.`)
|
notifications.success(`Datasource ${name} created successfully.`)
|
||||||
analytics.captureEvent("Datasource Created", { name })
|
analytics.captureEvent("Datasource Created", { name, type })
|
||||||
|
|
||||||
// Navigate to new datasource
|
// Navigate to new datasource
|
||||||
$goto(`./datasource/${response._id}`)
|
$goto(`./datasource/${response._id}`)
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
|
||||||
|
export let sourceId
|
||||||
|
|
||||||
$: selectedView = $views.selected && $views.selected.name
|
$: selectedView = $views.selected && $views.selected.name
|
||||||
|
|
||||||
function selectTable(table) {
|
function selectTable(table) {
|
||||||
|
@ -31,12 +33,13 @@
|
||||||
|
|
||||||
{#if $database?._id}
|
{#if $database?._id}
|
||||||
<div class="hierarchy-items-container">
|
<div class="hierarchy-items-container">
|
||||||
{#each $tables.list as table, idx}
|
{#each $tables.list.filter(table => table.sourceId === sourceId) as table, idx}
|
||||||
<NavItem
|
<NavItem
|
||||||
|
indentLevel={1}
|
||||||
border={idx > 0}
|
border={idx > 0}
|
||||||
icon={table._id === TableNames.USERS ? "UserGroup" : "Table"}
|
icon={table._id === TableNames.USERS ? "UserGroup" : "Table"}
|
||||||
text={table.name}
|
text={table.name}
|
||||||
selected={selectedView === `all_${table._id}`}
|
selected={$tables.selected?._id === table._id}
|
||||||
on:click={() => selectTable(table)}
|
on:click={() => selectTable(table)}
|
||||||
>
|
>
|
||||||
{#if table._id !== TableNames.USERS}
|
{#if table._id !== TableNames.USERS}
|
||||||
|
@ -45,7 +48,7 @@
|
||||||
</NavItem>
|
</NavItem>
|
||||||
{#each Object.keys(table.views || {}) as viewName, idx (idx)}
|
{#each Object.keys(table.views || {}) as viewName, idx (idx)}
|
||||||
<NavItem
|
<NavItem
|
||||||
indentLevel={1}
|
indentLevel={2}
|
||||||
icon="Remove"
|
icon="Remove"
|
||||||
text={viewName}
|
text={viewName}
|
||||||
selected={selectedView === viewName}
|
selected={selectedView === viewName}
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to new table
|
// Navigate to new table
|
||||||
$goto(`./table/${table._id}`)
|
$goto(`../../table/${table._id}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { store, allScreens } from "builderStore"
|
import { allScreens, store } from "builderStore"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
|
||||||
import {
|
import {
|
||||||
ActionMenu,
|
ActionMenu,
|
||||||
MenuItem,
|
|
||||||
Icon,
|
Icon,
|
||||||
|
Input,
|
||||||
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
Input,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
|
||||||
|
@ -22,9 +22,12 @@
|
||||||
let templateScreens
|
let templateScreens
|
||||||
let willBeDeleted
|
let willBeDeleted
|
||||||
|
|
||||||
|
$: external = table?.type === "external"
|
||||||
|
|
||||||
function showDeleteModal() {
|
function showDeleteModal() {
|
||||||
const screens = $allScreens
|
templateScreens = $allScreens.filter(
|
||||||
templateScreens = screens.filter(screen => screen.autoTableId === table._id)
|
screen => screen.autoTableId === table._id
|
||||||
|
)
|
||||||
willBeDeleted = ["All table data"].concat(
|
willBeDeleted = ["All table data"].concat(
|
||||||
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
||||||
)
|
)
|
||||||
|
@ -61,7 +64,9 @@
|
||||||
<Icon s hoverable name="MoreSmallList" />
|
<Icon s hoverable name="MoreSmallList" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||||
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
{#if !external}
|
||||||
|
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
||||||
|
{/if}
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
|
|
||||||
<Modal bind:this={editorModal}>
|
<Modal bind:this={editorModal}>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import { Icon } from "@budibase/bbui"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a target="_blank" href="https://github.com/Budibase/budibase/discussions">
|
||||||
|
<Icon hoverable name="Help" size="XXL" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
position: absolute;
|
||||||
|
bottom: var(--spacing-m);
|
||||||
|
right: var(--spacing-m);
|
||||||
|
border-radius: 55%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,10 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||||
|
import FeedbackIframe from "../feedback/FeedbackIframe.svelte"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
import FeedbackIframe from "components/feedback/FeedbackIframe.svelte"
|
|
||||||
|
|
||||||
const DeploymentStatus = {
|
const DeploymentStatus = {
|
||||||
SUCCESS: "SUCCESS",
|
SUCCESS: "SUCCESS",
|
||||||
|
@ -29,10 +29,6 @@
|
||||||
} else {
|
} else {
|
||||||
notifications.success(`Application published successfully`)
|
notifications.success(`Application published successfully`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (analytics.requestFeedbackOnDeploy()) {
|
|
||||||
feedbackModal.show()
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
analytics.captureException(err)
|
analytics.captureException(err)
|
||||||
notifications.error(`Error publishing app: ${err}`)
|
notifications.error(`Error publishing app: ${err}`)
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionMenu, ActionButton, MenuItem, Icon } from "@budibase/bbui"
|
import { ActionMenu, ActionButton, MenuItem, Icon } from "@budibase/bbui"
|
||||||
import { store, currentAssetName } from "builderStore"
|
import { store, currentAssetName, selectedComponent } from "builderStore"
|
||||||
import structure from "./componentStructure.json"
|
import structure from "./componentStructure.json"
|
||||||
|
|
||||||
$: enrichedStructure = enrichStructure(structure, $store.components)
|
$: enrichedStructure = enrichStructure(structure, $store.components)
|
||||||
|
|
||||||
|
const isChildAllowed = ({ name }, selectedComponent) => {
|
||||||
|
const currentComponent = store.actions.components.getDefinition(
|
||||||
|
selectedComponent?._component
|
||||||
|
)
|
||||||
|
return currentComponent?.illegalChildren?.includes(name.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
const enrichStructure = (structure, definitions) => {
|
const enrichStructure = (structure, definitions) => {
|
||||||
let enrichedStructure = []
|
let enrichedStructure = []
|
||||||
structure.forEach(item => {
|
structure.forEach(item => {
|
||||||
|
@ -39,6 +46,7 @@
|
||||||
<ActionMenu disabled={!item.isCategory}>
|
<ActionMenu disabled={!item.isCategory}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
|
disabled={isChildAllowed(item, $selectedComponent)}
|
||||||
quiet
|
quiet
|
||||||
size="S"
|
size="S"
|
||||||
slot="control"
|
slot="control"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
[
|
||||||
|
"section",
|
||||||
"container",
|
"container",
|
||||||
"dataprovider",
|
"dataprovider",
|
||||||
"table",
|
"table",
|
||||||
|
@ -61,8 +62,7 @@
|
||||||
"name": "Other",
|
"name": "Other",
|
||||||
"icon": "More",
|
"icon": "More",
|
||||||
"children": [
|
"children": [
|
||||||
"screenslot",
|
"screenslot"
|
||||||
"navigation"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,16 +12,24 @@ export default `
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<style>
|
<style>
|
||||||
html,
|
html, body {
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
margin: 0;
|
||||||
width: 100%;
|
}
|
||||||
|
html {
|
||||||
|
height: calc(100% - 16px);
|
||||||
|
width: calc(100% - 16px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
padding: 2px;
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
|
|
|
@ -10,11 +10,10 @@
|
||||||
|
|
||||||
$: screen = $allScreens.find(screen => screen._id === screenId)
|
$: screen = $allScreens.find(screen => screen._id === screenId)
|
||||||
|
|
||||||
const deleteScreen = () => {
|
const deleteScreen = async () => {
|
||||||
try {
|
try {
|
||||||
store.actions.screens.delete(screen)
|
await store.actions.screens.delete(screen)
|
||||||
store.actions.routing.fetch()
|
await store.actions.routing.fetch()
|
||||||
confirmDeleteDialog.hide()
|
|
||||||
$goto("../")
|
$goto("../")
|
||||||
notifications.success("Deleted screen successfully.")
|
notifications.success("Deleted screen successfully.")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -55,7 +55,6 @@
|
||||||
if (routeError) return false
|
if (routeError) return false
|
||||||
|
|
||||||
draftScreen.props._instanceName = name
|
draftScreen.props._instanceName = name
|
||||||
draftScreen.props._transition = "fade"
|
|
||||||
draftScreen.props._component = baseComponent
|
draftScreen.props._component = baseComponent
|
||||||
draftScreen.routing = { route, roleId }
|
draftScreen.routing = { route, roleId }
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { TextArea, DetailSummary, Button, Select } from "@budibase/bbui"
|
import { TextArea, DetailSummary, Button } from "@budibase/bbui"
|
||||||
import PropertyGroup from "./PropertyControls/PropertyGroup.svelte"
|
import PropertyGroup from "./PropertyControls/PropertyGroup.svelte"
|
||||||
import FlatButtonGroup from "./PropertyControls/FlatButtonGroup"
|
import FlatButtonGroup from "./PropertyControls/FlatButtonGroup"
|
||||||
import { allStyles } from "./componentStyles"
|
import { allStyles } from "./componentStyles"
|
||||||
|
@ -8,7 +8,6 @@
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
export let onStyleChanged = () => {}
|
export let onStyleChanged = () => {}
|
||||||
export let onCustomStyleChanged = () => {}
|
export let onCustomStyleChanged = () => {}
|
||||||
export let onUpdateTransition = () => {}
|
|
||||||
export let onResetStyles = () => {}
|
export let onResetStyles = () => {}
|
||||||
|
|
||||||
let selectedCategory = "normal"
|
let selectedCategory = "normal"
|
||||||
|
@ -24,16 +23,6 @@
|
||||||
{ value: "active", text: "Active" },
|
{ value: "active", text: "Active" },
|
||||||
]
|
]
|
||||||
|
|
||||||
const transitions = [
|
|
||||||
"none",
|
|
||||||
"fade",
|
|
||||||
"blur",
|
|
||||||
"fly",
|
|
||||||
"scale", // slide is hidden because it does not seem to result in any effect
|
|
||||||
]
|
|
||||||
|
|
||||||
const capitalize = ([first, ...rest]) => first.toUpperCase() + rest.join("")
|
|
||||||
|
|
||||||
$: groups = componentDefinition?.styleable ? Object.keys(allStyles) : []
|
$: groups = componentDefinition?.styleable ? Object.keys(allStyles) : []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -78,18 +67,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if componentDefinition?.transitionable}
|
|
||||||
<div class="transitions">
|
|
||||||
<Select
|
|
||||||
value={componentInstance._transition}
|
|
||||||
on:change={event => onUpdateTransition(event.detail)}
|
|
||||||
name="transition"
|
|
||||||
label="Transition"
|
|
||||||
options={transitions}
|
|
||||||
getOptionLabel={capitalize}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { setWith } from "lodash"
|
import { setWith } from "lodash"
|
||||||
|
|
||||||
$: definition = store.actions.components.getDefinition(
|
$: definition = store.actions.components.getDefinition(
|
||||||
$selectedComponent._component
|
$selectedComponent?._component
|
||||||
)
|
)
|
||||||
$: isComponentOrScreen =
|
$: isComponentOrScreen =
|
||||||
$store.currentView === "component" ||
|
$store.currentView === "component" ||
|
||||||
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
const onStyleChanged = store.actions.components.updateStyle
|
const onStyleChanged = store.actions.components.updateStyle
|
||||||
const onCustomStyleChanged = store.actions.components.updateCustomStyle
|
const onCustomStyleChanged = store.actions.components.updateCustomStyle
|
||||||
const onUpdateTransition = store.actions.components.updateTransition
|
|
||||||
const onResetStyles = store.actions.components.resetStyles
|
const onResetStyles = store.actions.components.resetStyles
|
||||||
|
|
||||||
function setAssetProps(name, value) {
|
function setAssetProps(name, value) {
|
||||||
|
@ -64,7 +63,6 @@
|
||||||
componentDefinition={definition}
|
componentDefinition={definition}
|
||||||
{onStyleChanged}
|
{onStyleChanged}
|
||||||
{onCustomStyleChanged}
|
{onCustomStyleChanged}
|
||||||
{onUpdateTransition}
|
|
||||||
{onResetStyles}
|
{onResetStyles}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
type: "table",
|
type: "table",
|
||||||
}))
|
}))
|
||||||
$: views = $tablesStore.list.reduce((acc, cur) => {
|
$: views = $tablesStore.list.reduce((acc, cur) => {
|
||||||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
|
let viewsArr = Object.entries(cur.views || {}).map(([key, value]) => ({
|
||||||
label: key,
|
label: key,
|
||||||
name: key,
|
name: key,
|
||||||
...value,
|
...value,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Drawer } from "@budibase/bbui"
|
import { ActionButton, Button, Drawer } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import EventEditor from "./EventEditor.svelte"
|
import EventEditor from "./EventEditor.svelte"
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary on:click={drawer.show}>Define Actions</Button>
|
<ActionButton on:click={drawer.show}>Define Actions</ActionButton>
|
||||||
<Drawer bind:this={drawer} title={"Actions"}>
|
<Drawer bind:this={drawer} title={"Actions"}>
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
Define what actions to run.
|
Define what actions to run.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label } from "@budibase/bbui"
|
import { Select, Label, Checkbox, Input } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
|
@ -35,6 +35,17 @@
|
||||||
value={parameters.revId}
|
value={parameters.revId}
|
||||||
on:change={value => (parameters.revId = value.detail)}
|
on:change={value => (parameters.revId = value.detail)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Label small />
|
||||||
|
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||||
|
|
||||||
|
{#if parameters.confirm}
|
||||||
|
<Label small>Confirm text</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Are you sure you want to delete this row?"
|
||||||
|
bind:value={parameters.confirmText}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -42,8 +53,8 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: var(--spacing-l);
|
column-gap: var(--spacing-l);
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-s);
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: 60px 1fr;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Layout } from "@budibase/bbui"
|
import { Select, Layout, Input, Checkbox } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
import { datasources, integrations, queries } from "stores/backend"
|
import { datasources, integrations, queries } from "stores/backend"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout>
|
<Layout gap="XS">
|
||||||
<Select
|
<Select
|
||||||
label="Datasource"
|
label="Datasource"
|
||||||
bind:value={parameters.datasourceId}
|
bind:value={parameters.datasourceId}
|
||||||
|
@ -44,22 +44,34 @@
|
||||||
getOptionLabel={query => query.name}
|
getOptionLabel={query => query.name}
|
||||||
getOptionValue={query => query._id}
|
getOptionValue={query => query._id}
|
||||||
/>
|
/>
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if query?.parameters?.length > 0}
|
{#if parameters.queryId}
|
||||||
<div>
|
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||||
<ParameterBuilder
|
|
||||||
bind:customParams={parameters.queryParams}
|
{#if parameters.confirm}
|
||||||
parameters={query.parameters}
|
<Input
|
||||||
bindings={bindableProperties}
|
label="Confirm text"
|
||||||
/>
|
placeholder="Are you sure you want to execute this query?"
|
||||||
<IntegrationQueryEditor
|
bind:value={parameters.confirmText}
|
||||||
height={200}
|
/>
|
||||||
{query}
|
{/if}
|
||||||
schema={fetchQueryDefinition(query)}
|
|
||||||
editable={false}
|
{#if query?.parameters?.length > 0}
|
||||||
{datasource}
|
<div>
|
||||||
/>
|
<ParameterBuilder
|
||||||
</div>
|
bind:customParams={parameters.queryParams}
|
||||||
|
parameters={query.parameters}
|
||||||
|
bindings={bindableProperties}
|
||||||
|
/>
|
||||||
|
<IntegrationQueryEditor
|
||||||
|
height={200}
|
||||||
|
{query}
|
||||||
|
schema={fetchQueryDefinition(query)}
|
||||||
|
editable={false}
|
||||||
|
{datasource}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script>
|
||||||
|
import { Body } from "@budibase/bbui"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Body size="S">This action doesn't require any additional settings.</Body>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -79,7 +79,7 @@
|
||||||
on:click={() => removeField(field[0])}
|
on:click={() => removeField(field[0])}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
<div>
|
<div style="margin-top: 10px">
|
||||||
<Button icon="AddCircle" secondary on:click={addField}>
|
<Button icon="AddCircle" secondary on:click={addField}>
|
||||||
Add
|
Add
|
||||||
{fieldLabel}
|
{fieldLabel}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Body } from "@budibase/bbui"
|
import { Select, Label, Body, Checkbox, Input } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import {
|
import {
|
||||||
|
@ -33,7 +33,8 @@
|
||||||
optional.<br />
|
optional.<br />
|
||||||
You can always add or override fields manually.
|
You can always add or override fields manually.
|
||||||
</Body>
|
</Body>
|
||||||
<div class="fields">
|
|
||||||
|
<div class="params">
|
||||||
<Label small>Data Source</Label>
|
<Label small>Data Source</Label>
|
||||||
<Select
|
<Select
|
||||||
bind:value={parameters.providerId}
|
bind:value={parameters.providerId}
|
||||||
|
@ -51,37 +52,58 @@
|
||||||
getOptionValue={option => option._id}
|
getOptionValue={option => option._id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if parameters.tableId}
|
<Label small />
|
||||||
|
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||||
|
|
||||||
|
{#if parameters.confirm}
|
||||||
|
<Label small>Confirm text</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Are you sure you want to save this row?"
|
||||||
|
bind:value={parameters.confirmText}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if parameters.tableId}
|
||||||
|
<div class="fields">
|
||||||
<SaveFields
|
<SaveFields
|
||||||
parameterFields={parameters.fields}
|
parameterFields={parameters.fields}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
on:change={onFieldsChanged}
|
on:change={onFieldsChanged}
|
||||||
/>
|
/>
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
|
width: 100%;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(p) {
|
.root :global(p) {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.params {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: 60px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.fields {
|
.fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: var(--spacing-l);
|
column-gap: var(--spacing-l);
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-s);
|
||||||
grid-template-columns: auto 1fr auto 1fr auto;
|
grid-template-columns: 60px 1fr auto 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fields :global(> div:nth-child(2)),
|
|
||||||
.fields :global(> div:nth-child(4)) {
|
|
||||||
grid-column-start: 2;
|
|
||||||
grid-column-end: 6;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Input } from "@budibase/bbui"
|
import { Select, Label, Input, Checkbox } from "@budibase/bbui"
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fields">
|
<div class="params">
|
||||||
<Label small>Automation</Label>
|
<Label small>Automation</Label>
|
||||||
|
|
||||||
{#if automationStatus === AUTOMATION_STATUS.EXISTING}
|
{#if automationStatus === AUTOMATION_STATUS.EXISTING}
|
||||||
|
@ -90,6 +90,19 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<Label small />
|
||||||
|
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||||
|
|
||||||
|
{#if parameters.confirm}
|
||||||
|
<Label small>Confirm text</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Are you sure you want to trigger this automation?"
|
||||||
|
bind:value={parameters.confirmText}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fields">
|
||||||
{#key parameters.automationId}
|
{#key parameters.automationId}
|
||||||
<SaveFields
|
<SaveFields
|
||||||
schemaFields={selectedSchema}
|
schemaFields={selectedSchema}
|
||||||
|
@ -107,16 +120,21 @@
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fields {
|
.params {
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: var(--spacing-l);
|
column-gap: var(--spacing-l);
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-s);
|
||||||
grid-template-columns: auto 1fr auto 1fr auto;
|
grid-template-columns: 60px 1fr;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fields :global(> div:nth-child(2)) {
|
.fields {
|
||||||
grid-column: 2 / span 4;
|
margin-top: var(--spacing-l);
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: 60px 1fr auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radios,
|
.radios,
|
||||||
|
|
|
@ -24,15 +24,12 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: 60px 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(> div) {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: var(--spacing-l);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import DeleteRow from "./DeleteRow.svelte"
|
||||||
import ExecuteQuery from "./ExecuteQuery.svelte"
|
import ExecuteQuery from "./ExecuteQuery.svelte"
|
||||||
import TriggerAutomation from "./TriggerAutomation.svelte"
|
import TriggerAutomation from "./TriggerAutomation.svelte"
|
||||||
import ValidateForm from "./ValidateForm.svelte"
|
import ValidateForm from "./ValidateForm.svelte"
|
||||||
|
import LogOut from "./LogOut.svelte"
|
||||||
|
|
||||||
// Defines which actions are available to configure in the front end.
|
// Defines which actions are available to configure in the front end.
|
||||||
// Unfortunately the "name" property is used as the identifier so please don't
|
// Unfortunately the "name" property is used as the identifier so please don't
|
||||||
|
@ -37,4 +38,8 @@ export default [
|
||||||
name: "Validate Form",
|
name: "Validate Form",
|
||||||
component: ValidateForm,
|
component: ValidateForm,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Log Out",
|
||||||
|
component: LogOut,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
notifications,
|
notifications,
|
||||||
|
ActionButton,
|
||||||
Button,
|
Button,
|
||||||
Drawer,
|
Drawer,
|
||||||
Body,
|
Body,
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary on:click={drawer.show}>Define Filters</Button>
|
<ActionButton on:click={drawer.show}>Define Filters</ActionButton>
|
||||||
<Drawer bind:this={drawer} title="Filtering">
|
<Drawer bind:this={drawer} title="Filtering">
|
||||||
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
||||||
<DrawerContent slot="body">
|
<DrawerContent slot="body">
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Popover, Button, Input } from "@budibase/bbui"
|
import { Popover, ActionButton, Button, Input } from "@budibase/bbui"
|
||||||
import { createEventDispatcher, tick } from "svelte"
|
import { createEventDispatcher, tick } from "svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={buttonAnchor}>
|
<div bind:this={buttonAnchor}>
|
||||||
<Button secondary small on:click={dropdown.show}>{displayValue}</Button>
|
<ActionButton on:click={dropdown.show}>{displayValue}</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<Popover bind:this={dropdown} on:open={setSelectedUI} anchor={buttonAnchor}>
|
<Popover bind:this={dropdown} on:open={setSelectedUI} anchor={buttonAnchor}>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
DrawerContent,
|
||||||
|
Layout,
|
||||||
|
Input,
|
||||||
|
Combobox,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { flip } from "svelte/animate"
|
||||||
|
import { dndzone } from "svelte-dnd-action"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
export let links = []
|
||||||
|
|
||||||
|
const flipDurationMs = 150
|
||||||
|
|
||||||
|
$: links.forEach(link => {
|
||||||
|
if (!link.id) {
|
||||||
|
link.id = generate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$: urlOptions = $store.screens
|
||||||
|
.map(screen => screen.routing?.route)
|
||||||
|
.filter(x => x != null)
|
||||||
|
|
||||||
|
const addLink = () => {
|
||||||
|
links = [...links, {}]
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeLink = id => {
|
||||||
|
links = links.filter(link => link.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLinks = e => {
|
||||||
|
links = e.detail.items
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerContent>
|
||||||
|
<div class="container">
|
||||||
|
<Layout>
|
||||||
|
{#if links?.length}
|
||||||
|
<div
|
||||||
|
class="links"
|
||||||
|
use:dndzone={{
|
||||||
|
items: links,
|
||||||
|
flipDurationMs,
|
||||||
|
dropTargetStyle: { outline: "none" },
|
||||||
|
}}
|
||||||
|
on:finalize={updateLinks}
|
||||||
|
on:consider={updateLinks}
|
||||||
|
>
|
||||||
|
{#each links as link (link.id)}
|
||||||
|
<div class="link" animate:flip={{ duration: flipDurationMs }}>
|
||||||
|
<Icon name="DragHandle" size="XL" />
|
||||||
|
<Input bind:value={link.text} placeholder="Text" />
|
||||||
|
<Combobox
|
||||||
|
bind:value={link.url}
|
||||||
|
placeholder="URL"
|
||||||
|
options={urlOptions}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name="Close"
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
on:click={() => removeLink(link.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="button-container">
|
||||||
|
<Button secondary icon="Add" on:click={addLink}>Add Link</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: var(--spacing-m) auto;
|
||||||
|
}
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
padding: 4px 8px;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
transition: background-color ease-in-out 130ms;
|
||||||
|
}
|
||||||
|
.link:hover {
|
||||||
|
background-color: var(--spectrum-global-color-gray-100);
|
||||||
|
}
|
||||||
|
.link > :global(.spectrum-Form-item) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
.button-container {
|
||||||
|
margin-left: var(--spacing-l);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
import { Button, ActionButton, Drawer } from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import NavigationDrawer from "./NavigationDrawer.svelte"
|
||||||
|
|
||||||
|
export let value = []
|
||||||
|
let drawer
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const save = () => {
|
||||||
|
dispatch("change", value)
|
||||||
|
drawer.hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionButton on:click={drawer.show}>Configure Links</ActionButton>
|
||||||
|
<Drawer bind:this={drawer} title={"Navigation Links"}>
|
||||||
|
<svelte:fragment slot="description">
|
||||||
|
Configure the links in your navigation bar.
|
||||||
|
</svelte:fragment>
|
||||||
|
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||||
|
<NavigationDrawer slot="body" bind:links={value} />
|
||||||
|
</Drawer>
|
|
@ -0,0 +1,75 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { ActionButton, Body, Icon, Modal, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let value = ""
|
||||||
|
let selected = value
|
||||||
|
|
||||||
|
let modal
|
||||||
|
let layoutMap = {
|
||||||
|
mainSidebar: {
|
||||||
|
name: "Main with Sidebar",
|
||||||
|
icon: "ColumnTwoB",
|
||||||
|
},
|
||||||
|
sidebarMain: {
|
||||||
|
name: "Sidebar with Main",
|
||||||
|
icon: "ColumnTwoC",
|
||||||
|
},
|
||||||
|
twoColumns: {
|
||||||
|
name: "Two columns",
|
||||||
|
icon: "ColumnTwoA",
|
||||||
|
},
|
||||||
|
threeColumns: {
|
||||||
|
name: "Three columns",
|
||||||
|
icon: "ViewColumn",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionButton on:click={modal.show}>{layoutMap[value].name}</ActionButton>
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<ModalContent
|
||||||
|
onConfirm={() => dispatch("change", selected)}
|
||||||
|
size="L"
|
||||||
|
title="Select layout"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
{#each Object.entries(layoutMap) as [key, value]}
|
||||||
|
<button
|
||||||
|
class:selected={selected === key}
|
||||||
|
on:click={() => (selected = key)}
|
||||||
|
class="layout"
|
||||||
|
>
|
||||||
|
<Icon color="white" size="L" name={value.icon} />
|
||||||
|
<Body size="XS">{value.name}</Body>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 20px;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
color: var(--spectrum-body-m-text-color, var(--spectrum-alias-text-color));
|
||||||
|
border: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: var(--background-alt);
|
||||||
|
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
||||||
|
padding: var(--spectrum-alias-item-padding-s);
|
||||||
|
transition: 0.3s all;
|
||||||
|
border-radius: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.selected {
|
||||||
|
background: var(--spectrum-alias-background-color-tertiary);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -15,6 +15,8 @@
|
||||||
import FieldSelect from "./PropertyControls/FieldSelect.svelte"
|
import FieldSelect from "./PropertyControls/FieldSelect.svelte"
|
||||||
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
||||||
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
||||||
|
import SectionSelect from "./PropertyControls/SectionSelect.svelte"
|
||||||
|
import NavigationEditor from "./PropertyControls/NavigationEditor/NavigationEditor.svelte"
|
||||||
import EventsEditor from "./PropertyControls/EventsEditor"
|
import EventsEditor from "./PropertyControls/EventsEditor"
|
||||||
import FilterEditor from "./PropertyControls/FilterEditor/FilterEditor.svelte"
|
import FilterEditor from "./PropertyControls/FilterEditor/FilterEditor.svelte"
|
||||||
import { IconSelect } from "./PropertyControls/IconSelect"
|
import { IconSelect } from "./PropertyControls/IconSelect"
|
||||||
|
@ -62,6 +64,8 @@
|
||||||
field: FieldSelect,
|
field: FieldSelect,
|
||||||
multifield: MultiFieldSelect,
|
multifield: MultiFieldSelect,
|
||||||
schema: SchemaSelect,
|
schema: SchemaSelect,
|
||||||
|
section: SectionSelect,
|
||||||
|
navigation: NavigationEditor,
|
||||||
filter: FilterEditor,
|
filter: FilterEditor,
|
||||||
"field/string": StringFieldSelect,
|
"field/string": StringFieldSelect,
|
||||||
"field/number": NumberFieldSelect,
|
"field/number": NumberFieldSelect,
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Button } from "@budibase/bbui"
|
|
||||||
export let remove = false
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="content">
|
|
||||||
<div class="img"><img src="https://picsum.photos/60/60" alt="zoom" /></div>
|
|
||||||
<div class="body">
|
|
||||||
<div class="title">Zoom</div>
|
|
||||||
<div class="description">
|
|
||||||
Lorem, ipsum dolor sit amet consectetur adipisicing elit
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<Button wide error={remove} secondary={!remove} on:click>
|
|
||||||
<span>{remove ? "Remove" : "Add"}</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
padding: 12px;
|
|
||||||
background: var(--light-grey);
|
|
||||||
grid-gap: 20px;
|
|
||||||
}
|
|
||||||
span {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 12px;
|
|
||||||
grid-template-columns: 60px auto;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.img {
|
|
||||||
border-radius: 3px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
height: 60px;
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,33 +0,0 @@
|
||||||
<script>
|
|
||||||
import SettingsModal from "./SettingsModal.svelte"
|
|
||||||
import { Modal, Icon } from "@budibase/bbui"
|
|
||||||
|
|
||||||
let modal
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="topnavitemright settings"
|
|
||||||
data-cy="settings-icon"
|
|
||||||
on:click={modal.show}
|
|
||||||
>
|
|
||||||
<Icon hoverable name="Settings" />
|
|
||||||
</div>
|
|
||||||
<Modal bind:this={modal} width="600px">
|
|
||||||
<SettingsModal />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.topnavitemright {
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--grey-7);
|
|
||||||
margin: 0 12px 0 0;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,34 +0,0 @@
|
||||||
<script>
|
|
||||||
import { General, DangerZone } from "./tabs"
|
|
||||||
import { ModalContent, Tab, Tabs } from "@budibase/bbui"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent
|
|
||||||
title="Settings"
|
|
||||||
showConfirmButton={false}
|
|
||||||
showCancelButton={false}
|
|
||||||
>
|
|
||||||
<div class="container">
|
|
||||||
<Tabs selected="General">
|
|
||||||
<Tab title="General">
|
|
||||||
<General />
|
|
||||||
</Tab>
|
|
||||||
<!-- <Tab title="API Keys">
|
|
||||||
<APIKeys />
|
|
||||||
</Tab> -->
|
|
||||||
<Tab title="Danger Zone">
|
|
||||||
<DangerZone />
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</ModalContent>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container :global(section > header) {
|
|
||||||
/* Fix margin defined in BBUI as L rather than XL */
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.container :global(textarea) {
|
|
||||||
min-height: 60px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,62 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Input, Label, Link } from "@budibase/bbui"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import { notifications } from "@budibase/bbui"
|
|
||||||
import { database } from "stores/backend"
|
|
||||||
import analytics from "analytics"
|
|
||||||
|
|
||||||
let keys = { budibase: "" }
|
|
||||||
|
|
||||||
async function updateKey([key, value]) {
|
|
||||||
if (key === "budibase") {
|
|
||||||
const isValid = await analytics.identifyByApiKey(value)
|
|
||||||
if (!isValid) {
|
|
||||||
notifications.error("Your API Key is invalid.")
|
|
||||||
keys = { ...keys }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const response = await api.put(`/api/keys/${key}`, { value })
|
|
||||||
const res = await response.json()
|
|
||||||
keys = { ...keys, ...res }
|
|
||||||
notifications.success("API Key saved.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Keys
|
|
||||||
async function fetchKeys() {
|
|
||||||
const response = await api.get(`/api/keys/`)
|
|
||||||
const res = await response.json()
|
|
||||||
// dont want this to ever be editable, as its fetched based on Api Key
|
|
||||||
if (res.userId) delete res.userId
|
|
||||||
keys = res
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchKeys()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<Input
|
|
||||||
on:change={e => updateKey(["budibase", e.detail])}
|
|
||||||
value={keys.budibase}
|
|
||||||
label="Budibase Cloud API Key"
|
|
||||||
/>
|
|
||||||
<Link primary href="https://portal.budi.live">
|
|
||||||
Log in to the Budibase Hosting Portal to get your API Key. →
|
|
||||||
</Link>
|
|
||||||
<div>
|
|
||||||
<Label extraSmall grey>Instance ID (Webhooks)</Label>
|
|
||||||
<span>{$database._id}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,56 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params, goto } from "@roxi/routify"
|
|
||||||
import { Input, Button, Body } from "@budibase/bbui"
|
|
||||||
import { del } from "builderStore/api"
|
|
||||||
|
|
||||||
let value = ""
|
|
||||||
let loading = false
|
|
||||||
|
|
||||||
async function deleteApp() {
|
|
||||||
loading = true
|
|
||||||
const id = $params.application
|
|
||||||
await del(`/api/applications/${id}`)
|
|
||||||
loading = false
|
|
||||||
$goto("/builder")
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="background">
|
|
||||||
<Body>
|
|
||||||
Type
|
|
||||||
<b>DELETE</b>
|
|
||||||
into the textbox, then click the following button to delete your entire web app.
|
|
||||||
</Body>
|
|
||||||
<Input
|
|
||||||
on:change={e => (value = e.detail)}
|
|
||||||
disabled={loading}
|
|
||||||
placeholder=""
|
|
||||||
/>
|
|
||||||
<div class="buttons">
|
|
||||||
<Button
|
|
||||||
warning
|
|
||||||
disabled={value !== "DELETE" || loading}
|
|
||||||
on:click={deleteApp}
|
|
||||||
>
|
|
||||||
Delete Entire App
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.background {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.background :global(p) {
|
|
||||||
line-height: 1.2;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,97 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Input, TextArea } from "@budibase/bbui"
|
|
||||||
import { store, hostingStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import { object, string } from "yup"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
|
|
||||||
let nameValidation, nameError
|
|
||||||
let urlValidation, urlError
|
|
||||||
|
|
||||||
$: checkName($store.name)
|
|
||||||
$: checkUrl($store.url)
|
|
||||||
|
|
||||||
async function updateApplication(data) {
|
|
||||||
const response = await api.put(`/api/applications/${$store.appId}`, data)
|
|
||||||
await response.json()
|
|
||||||
store.update(state => {
|
|
||||||
state = {
|
|
||||||
...state,
|
|
||||||
...data,
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkValidation(input, validation) {
|
|
||||||
if (!input || !validation) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await object(validation).validate(input, { abortEarly: false })
|
|
||||||
} catch (error) {
|
|
||||||
if (!error || !error.inner) return ""
|
|
||||||
return error.inner.reduce((acc, err) => {
|
|
||||||
return acc + err.message
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkName(name) {
|
|
||||||
nameError = await checkValidation({ name }, nameValidation)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkUrl(url) {
|
|
||||||
urlError = await checkValidation({ url: url.toLowerCase() }, urlValidation)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const nameError = "Your application must have a name.",
|
|
||||||
urlError = "Your application must have a URL."
|
|
||||||
await hostingStore.actions.fetchDeployedApps()
|
|
||||||
const existingAppNames = get(hostingStore).deployedAppNames
|
|
||||||
const existingAppUrls = get(hostingStore).deployedAppUrls
|
|
||||||
const nameIdx = existingAppNames.indexOf(get(store).name)
|
|
||||||
const urlIdx = existingAppUrls.indexOf(get(store).url)
|
|
||||||
if (nameIdx !== -1) {
|
|
||||||
existingAppNames.splice(nameIdx, 1)
|
|
||||||
}
|
|
||||||
if (urlIdx !== -1) {
|
|
||||||
existingAppUrls.splice(urlIdx, 1)
|
|
||||||
}
|
|
||||||
nameValidation = {
|
|
||||||
name: string().required(nameError).notOneOf(existingAppNames),
|
|
||||||
}
|
|
||||||
urlValidation = {
|
|
||||||
url: string().required(urlError).notOneOf(existingAppUrls),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<Input
|
|
||||||
on:change={e => updateApplication({ name: e.detail })}
|
|
||||||
value={$store.name}
|
|
||||||
error={nameError}
|
|
||||||
label="App Name"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
on:change={e => updateApplication({ url: e.detail })}
|
|
||||||
value={$store.url}
|
|
||||||
error={urlError}
|
|
||||||
label="App URL"
|
|
||||||
/>
|
|
||||||
<TextArea
|
|
||||||
on:change={e => updateApplication({ description: e.detail })}
|
|
||||||
value={$store.description}
|
|
||||||
label="App Description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,40 +0,0 @@
|
||||||
<script>
|
|
||||||
import Integration from "../Integration.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="title">Your Integrations</div>
|
|
||||||
<div class="integrations">
|
|
||||||
<Integration remove />
|
|
||||||
<Integration remove />
|
|
||||||
<Integration remove />
|
|
||||||
<Integration remove />
|
|
||||||
</div>
|
|
||||||
<div class="apps">Recommended apps</div>
|
|
||||||
<div class="integrations">
|
|
||||||
<Integration />
|
|
||||||
<Integration />
|
|
||||||
<Integration />
|
|
||||||
<Integration />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 16px;
|
|
||||||
}
|
|
||||||
.integrations {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-gap: 20px;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.apps {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1 +0,0 @@
|
||||||
Permissions
|
|
|
@ -1,5 +0,0 @@
|
||||||
export { default as General } from "./General.svelte"
|
|
||||||
export { default as Integrations } from "./Integrations.svelte"
|
|
||||||
export { default as Permissions } from "./Permissions.svelte"
|
|
||||||
export { default as APIKeys } from "./APIKeys.svelte"
|
|
||||||
export { default as DangerZone } from "./DangerZone.svelte"
|
|
|
@ -31,3 +31,5 @@ export const LAYOUT_NAMES = {
|
||||||
PUBLIC: "layout_private_master",
|
PUBLIC: "layout_private_master",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BUDIBASE_INTERNAL_DB = "bb_internal"
|
||||||
|
|
|
@ -1,49 +1,27 @@
|
||||||
<script>
|
<script>
|
||||||
import { isActive, goto } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { Icon, Modal, Tabs, Tab } from "@budibase/bbui"
|
import { Icon, Modal, Tabs, Tab } from "@budibase/bbui"
|
||||||
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
import { BUDIBASE_INTERNAL_DB } from "constants"
|
||||||
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
|
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
|
||||||
import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
|
import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
|
||||||
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
title: "Internal",
|
|
||||||
key: "table",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "External",
|
|
||||||
key: "datasource",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
let selected = $isActive("./datasource") ? "External" : "Internal"
|
|
||||||
|
|
||||||
function selectFirstTableOrSource({ detail }) {
|
|
||||||
const { key } = tabs.find(t => t.title === detail)
|
|
||||||
if (key === "datasource") {
|
|
||||||
$goto("./datasource")
|
|
||||||
} else {
|
|
||||||
$goto("./table")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let selected = "Sources"
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
$: isExternal =
|
||||||
|
$params.selectedDatasource &&
|
||||||
|
$params.selectedDatasource !== BUDIBASE_INTERNAL_DB
|
||||||
|
|
||||||
|
function selectFirstDatasource() {
|
||||||
|
$goto("./table")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=0 -->
|
<!-- routify:options index=0 -->
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<Tabs {selected} on:select={selectFirstTableOrSource}>
|
<Tabs {selected} on:select={selectFirstDatasource}>
|
||||||
<Tab title="Internal">
|
<Tab title="Sources">
|
||||||
<div class="tab-content-padding">
|
|
||||||
<TableNavigator />
|
|
||||||
<Modal bind:this={modal}>
|
|
||||||
<CreateTableModal />
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
<Tab title="External">
|
|
||||||
<div class="tab-content-padding">
|
<div class="tab-content-padding">
|
||||||
<DatasourceNavigator />
|
<DatasourceNavigator />
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
@ -54,7 +32,7 @@
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div
|
<div
|
||||||
class="add-button"
|
class="add-button"
|
||||||
data-cy={`new-${selected === "External" ? "datasource" : "table"}`}
|
data-cy={`new-${isExternal ? "datasource" : "table"}`}
|
||||||
>
|
>
|
||||||
<Icon hoverable name="AddCircle" on:click={modal.show} />
|
<Icon hoverable name="AddCircle" on:click={modal.show} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1 @@
|
||||||
<script>
|
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
import { queries } from "stores/backend"
|
|
||||||
|
|
||||||
if ($params.query) {
|
|
||||||
const query = $queries.list.find(m => m._id === $params.query)
|
|
||||||
if (query) {
|
|
||||||
queries.select(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto, beforeUrlChange } from "@roxi/routify"
|
import { goto, beforeUrlChange } from "@roxi/routify"
|
||||||
import { Button, Heading, Body, Divider, Layout } from "@budibase/bbui"
|
import { Button, Heading, Body, Divider, Layout } from "@budibase/bbui"
|
||||||
import { datasources, integrations, queries } from "stores/backend"
|
import { datasources, integrations, queries, tables } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||||
|
@ -13,10 +13,25 @@
|
||||||
$: integration = datasource && $integrations[datasource.source]
|
$: integration = datasource && $integrations[datasource.source]
|
||||||
|
|
||||||
async function saveDatasource() {
|
async function saveDatasource() {
|
||||||
// Create datasource
|
try {
|
||||||
await datasources.save(datasource)
|
// Create datasource
|
||||||
notifications.success(`Datasource ${name} saved successfully.`)
|
await datasources.save(datasource)
|
||||||
unsaved = false
|
notifications.success(`Datasource ${name} updated successfully.`)
|
||||||
|
unsaved = false
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Error saving datasource: ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDatasourceSchema() {
|
||||||
|
try {
|
||||||
|
await datasources.updateSchema(datasource)
|
||||||
|
notifications.success(`Datasource ${name} tables updated successfully.`)
|
||||||
|
unsaved = false
|
||||||
|
await tables.fetch()
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Error updating datasource schema: ${err}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickQuery(query) {
|
function onClickQuery(query) {
|
||||||
|
@ -24,6 +39,11 @@
|
||||||
$goto(`./${query._id}`)
|
$goto(`./${query._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onClickTable(table) {
|
||||||
|
tables.select(table)
|
||||||
|
$goto(`../../table/${table._id}`)
|
||||||
|
}
|
||||||
|
|
||||||
function setUnsaved() {
|
function setUnsaved() {
|
||||||
unsaved = true
|
unsaved = true
|
||||||
}
|
}
|
||||||
|
@ -39,7 +59,7 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if datasource}
|
{#if datasource && integration}
|
||||||
<section>
|
<section>
|
||||||
<Layout>
|
<Layout>
|
||||||
<header>
|
<header>
|
||||||
|
@ -66,6 +86,34 @@
|
||||||
on:change={setUnsaved}
|
on:change={setUnsaved}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{#if datasource.plus}
|
||||||
|
<Divider />
|
||||||
|
<div class="query-header">
|
||||||
|
<Heading size="S">Tables</Heading>
|
||||||
|
<Button primary on:click={updateDatasourceSchema}
|
||||||
|
>Fetch Tables From Database</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<Body>
|
||||||
|
This datasource can determine tables automatically. Budibase can fetch
|
||||||
|
your tables directly from the database and you can use them without
|
||||||
|
having to write any queries at all.
|
||||||
|
</Body>
|
||||||
|
<div class="query-list">
|
||||||
|
{#if datasource.entities}
|
||||||
|
{#each Object.keys(datasource.entities) as entity}
|
||||||
|
<div
|
||||||
|
class="query-list-item"
|
||||||
|
on:click={() => onClickTable(datasource.entities[entity])}
|
||||||
|
>
|
||||||
|
<p class="query-name">{entity}</p>
|
||||||
|
<p>Primary Key: {datasource.entities[entity].primary}</p>
|
||||||
|
<p>→</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<Divider />
|
<Divider />
|
||||||
<div class="query-header">
|
<div class="query-header">
|
||||||
<Heading size="S">Queries</Heading>
|
<Heading size="S">Queries</Heading>
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
<script>
|
||||||
|
import { Button, Heading, Body, Layout, Modal, Divider } from "@budibase/bbui"
|
||||||
|
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
|
||||||
|
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||||
|
import { tables } from "stores/backend"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
let modal
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<CreateTableModal />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Layout>
|
||||||
|
<header>
|
||||||
|
<svelte:component this={ICONS.BUDIBASE} height="26" width="26" />
|
||||||
|
<Heading size="M">Budibase Internal</Heading>
|
||||||
|
</header>
|
||||||
|
<Body size="S" grey lh
|
||||||
|
>Budibase internal tables are part of your app, the data will be stored in
|
||||||
|
your apps context.</Body
|
||||||
|
>
|
||||||
|
<Divider />
|
||||||
|
<Heading size="S">Tables</Heading>
|
||||||
|
<div class="table-list">
|
||||||
|
{#each $tables.list.filter(table => table.type !== "external") as table}
|
||||||
|
<div
|
||||||
|
class="table-list-item"
|
||||||
|
on:click={$goto(`../../table/${table._id}`)}
|
||||||
|
>
|
||||||
|
<Body size="S">{table.name}</Body>
|
||||||
|
{#if table.primaryDisplay}
|
||||||
|
<Body size="S">display column: {table.primaryDisplay}</Body>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button cta on:click={modal.show}>Create new table</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin: 0 0 var(--spacing-xs) 0;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-list-item {
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
background: var(--background);
|
||||||
|
border: var(--border-dark);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 0.75fr 20px;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
gap: var(--layout-xs);
|
||||||
|
transition: 200ms background ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-list-item:hover {
|
||||||
|
background: var(--grey-1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script>
|
||||||
|
import { params } from "@roxi/routify"
|
||||||
|
import { tables } from "stores/backend"
|
||||||
|
|
||||||
|
if ($params.selectedTable) {
|
||||||
|
const table = $tables.list.find(m => m._id === $params.selectedTable)
|
||||||
|
if (table) {
|
||||||
|
tables.select(table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script>
|
||||||
|
import TableDataTable from "components/backend/DataTable/DataTable.svelte"
|
||||||
|
import { tables, database } from "stores/backend"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $database?._id && $tables?.selected?.name}
|
||||||
|
<TableDataTable />
|
||||||
|
{:else}<i>Create your first table to start building</i>{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
i {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
color: var(--grey-5);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script>
|
||||||
|
import { params } from "@roxi/routify"
|
||||||
|
import RelationshipDataTable from "components/backend/DataTable/RelationshipDataTable.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<RelationshipDataTable
|
||||||
|
tableId={$params.selectedTable}
|
||||||
|
rowId={$params.selectedRow}
|
||||||
|
fieldName={decodeURI($params.selectedField)}
|
||||||
|
/>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
$goto("../../")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- routify:options index=false -->
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
$goto("../")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- routify:options index=false -->
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
import { tables } from "stores/backend"
|
||||||
|
import { goto, leftover } from "@roxi/routify"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// navigate to first table in list, if not already selected
|
||||||
|
// and this is the final url (i.e. no selectedTable)
|
||||||
|
if (
|
||||||
|
!$leftover &&
|
||||||
|
$tables.list.length > 0
|
||||||
|
// (!$tables.selected || !$tables.selected._id)
|
||||||
|
) {
|
||||||
|
$goto(`./${$tables.list[0]._id}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { tables } from "stores/backend"
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
$tables.list.length > 0 && $goto(`./${$tables.list[0]._id}`)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $tables.list.length === 0}
|
||||||
|
<i>Create your first table to start building</i>
|
||||||
|
{:else}<i>Select a table to edit</i>{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
i {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
color: var(--grey-5);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -190,11 +190,10 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-m);
|
||||||
padding: var(--spacing-l) 40px var(--spacing-xl) 40px;
|
padding: var(--spacing-xl) 40px;
|
||||||
}
|
}
|
||||||
.preview-content {
|
.preview-content {
|
||||||
box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { organisation, auth } from "stores/portal"
|
import { organisation, auth } from "stores/portal"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let email = ""
|
let email = ""
|
||||||
|
|
||||||
|
@ -20,6 +21,10 @@
|
||||||
notifications.error("Unable to send reset password link")
|
notifications.error("Unable to send reset password link")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await organisation.init()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="login">
|
<div class="login">
|
||||||
|
|
|
@ -10,13 +10,16 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { auth } from "stores/portal"
|
import { auth, organisation } from "stores/portal"
|
||||||
import GoogleButton from "./_components/GoogleButton.svelte"
|
import GoogleButton from "./_components/GoogleButton.svelte"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let username = ""
|
let username = ""
|
||||||
let password = ""
|
let password = ""
|
||||||
|
|
||||||
|
$: company = $organisation.company || "Budibase"
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
try {
|
try {
|
||||||
await auth.login({
|
await auth.login({
|
||||||
|
@ -43,6 +46,10 @@
|
||||||
function handleKeydown(evt) {
|
function handleKeydown(evt) {
|
||||||
if (evt.key === "Enter") login()
|
if (evt.key === "Enter") login()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await organisation.init()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
<svelte:window on:keydown={handleKeydown} />
|
||||||
|
@ -50,8 +57,8 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout noPadding justifyItems="center">
|
<Layout noPadding justifyItems="center">
|
||||||
<img alt="logo" src={Logo} />
|
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||||
<Heading>Sign in to Budibase</Heading>
|
<Heading>Sign in to {company}</Heading>
|
||||||
</Layout>
|
</Layout>
|
||||||
<GoogleButton />
|
<GoogleButton />
|
||||||
<Divider noGrid />
|
<Divider noGrid />
|
||||||
|
@ -66,7 +73,7 @@
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Button cta on:click={login}>Sign in to Budibase</Button>
|
<Button cta on:click={login}>Sign in to {company}</Button>
|
||||||
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
import { Body, Button, Heading, Layout, notifications } from "@budibase/bbui"
|
import { Body, Button, Heading, Layout, notifications } from "@budibase/bbui"
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
import { auth } from "stores/portal"
|
import { auth, organisation } from "stores/portal"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
const resetCode = $params["?code"]
|
const resetCode = $params["?code"]
|
||||||
let password, error
|
let password, error
|
||||||
|
@ -28,13 +29,17 @@
|
||||||
notifications.error("Unable to reset password")
|
notifications.error("Unable to reset password")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await organisation.init()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="login">
|
<div class="login">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout noPadding justifyItems="center">
|
<Layout noPadding justifyItems="center">
|
||||||
<img src={Logo} alt="Organisation logo" />
|
<img src={$organisation.logoUrl || Logo} alt="Organisation logo" />
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading textAlign="center">Reset your password</Heading>
|
<Heading textAlign="center">Reset your password</Heading>
|
||||||
|
|
|
@ -57,11 +57,17 @@
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update settings
|
const config = {
|
||||||
const res = await organisation.save({
|
|
||||||
company: $values.company ?? "",
|
company: $values.company ?? "",
|
||||||
platformUrl: $values.platformUrl ?? "",
|
platformUrl: $values.platformUrl ?? "",
|
||||||
})
|
}
|
||||||
|
// remove logo if required
|
||||||
|
if (!$values.logo) {
|
||||||
|
config.logoUrl = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update settings
|
||||||
|
const res = await organisation.save(config)
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
notifications.success("Settings saved successfully")
|
notifications.success("Settings saved successfully")
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,7 +104,11 @@
|
||||||
<Dropzone
|
<Dropzone
|
||||||
value={[$values.logo]}
|
value={[$values.logo]}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
$values.logo = e.detail?.[0]
|
if (!e.detail || e.detail.length === 0) {
|
||||||
|
$values.logo = null
|
||||||
|
} else {
|
||||||
|
$values.logo = e.detail[0]
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
import {
|
import {
|
||||||
Layout,
|
Layout,
|
||||||
Heading,
|
Heading,
|
||||||
|
@ -7,9 +8,12 @@
|
||||||
Divider,
|
Divider,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
import api from "builderStore/api"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
|
let version
|
||||||
|
|
||||||
// Only admins allowed here
|
// Only admins allowed here
|
||||||
$: {
|
$: {
|
||||||
if (!$auth.isAdmin) {
|
if (!$auth.isAdmin) {
|
||||||
|
@ -26,10 +30,20 @@
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
notifications.success("Your budibase installation is up to date.")
|
notifications.success("Your budibase installation is up to date.")
|
||||||
|
getVersion()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error installing budibase update ${err}`)
|
notifications.error(`Error installing budibase update ${err}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getVersion() {
|
||||||
|
const response = await api.get("/api/dev/version")
|
||||||
|
version = await response.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getVersion()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $auth.isAdmin}
|
{#if $auth.isAdmin}
|
||||||
|
@ -43,6 +57,11 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
{#if version}
|
||||||
|
Current Version: {version}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Button cta on:click={updateBudibase}>Check For Updates</Button>
|
<Button cta on:click={updateBudibase}>Check For Updates</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { queries } from "./"
|
import { queries, tables, views } from "./"
|
||||||
import api from "../../builderStore/api"
|
import api from "../../builderStore/api"
|
||||||
|
|
||||||
export const INITIAL_DATASOURCE_VALUES = {
|
export const INITIAL_DATASOURCE_VALUES = {
|
||||||
|
@ -21,17 +21,53 @@ export function createDatasourcesStore() {
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
const response = await api.get(`/api/datasources`)
|
const response = await api.get(`/api/datasources`)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
update(state => ({ ...state, list: json }))
|
update(state => ({ ...state, list: json, selected: null }))
|
||||||
return json
|
return json
|
||||||
},
|
},
|
||||||
select: async datasourceId => {
|
select: async datasourceId => {
|
||||||
update(state => ({ ...state, selected: datasourceId }))
|
update(state => ({ ...state, selected: datasourceId }))
|
||||||
queries.update(state => ({ ...state, selected: null }))
|
queries.unselect()
|
||||||
|
tables.unselect()
|
||||||
|
views.unselect()
|
||||||
|
},
|
||||||
|
unselect: () => {
|
||||||
|
update(state => ({ ...state, selected: null }))
|
||||||
|
},
|
||||||
|
updateSchema: async datasource => {
|
||||||
|
let url = `/api/datasources/${datasource._id}/schema`
|
||||||
|
|
||||||
|
const response = await api.post(url)
|
||||||
|
const json = await response.json()
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(json.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(state => {
|
||||||
|
const currentIdx = state.list.findIndex(ds => ds._id === json._id)
|
||||||
|
|
||||||
|
const sources = state.list
|
||||||
|
|
||||||
|
if (currentIdx >= 0) {
|
||||||
|
sources.splice(currentIdx, 1, json)
|
||||||
|
} else {
|
||||||
|
sources.push(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list: sources, selected: json._id }
|
||||||
|
})
|
||||||
|
return json
|
||||||
},
|
},
|
||||||
save: async datasource => {
|
save: async datasource => {
|
||||||
const response = await api.post("/api/datasources", datasource)
|
let url = "/api/datasources"
|
||||||
|
|
||||||
|
const response = await api.post(url, datasource)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(json.message)
|
||||||
|
}
|
||||||
|
|
||||||
update(state => {
|
update(state => {
|
||||||
const currentIdx = state.list.findIndex(ds => ds._id === json._id)
|
const currentIdx = state.list.findIndex(ds => ds._id === json._id)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { datasources, integrations } from "./"
|
import { datasources, integrations, tables } from "./"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
export function createQueriesStore() {
|
export function createQueriesStore() {
|
||||||
|
@ -55,11 +55,14 @@ export function createQueriesStore() {
|
||||||
},
|
},
|
||||||
select: query => {
|
select: query => {
|
||||||
update(state => ({ ...state, selected: query._id }))
|
update(state => ({ ...state, selected: query._id }))
|
||||||
datasources.update(state => ({
|
tables.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
selected: query.datasourceId,
|
selected: null,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
unselect: () => {
|
||||||
|
update(state => ({ ...state, selected: null }))
|
||||||
|
},
|
||||||
delete: async query => {
|
delete: async query => {
|
||||||
const response = await api.delete(
|
const response = await api.delete(
|
||||||
`/api/queries/${query._id}/${query._rev}`
|
`/api/queries/${query._id}/${query._rev}`
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { views } from "./"
|
import { tables } from "./"
|
||||||
|
|
||||||
export function createRowsStore() {
|
export function createRowsStore() {
|
||||||
const { subscribe } = writable([])
|
const { subscribe } = writable([])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
save: () => views.select(get(views).selected),
|
save: () => tables.select(get(tables).selected),
|
||||||
delete: () => views.select(get(views).selected),
|
delete: () => tables.select(get(tables).selected),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { views } from "./"
|
import { views, queries, datasources } from "./"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
@ -25,7 +25,9 @@ export function createTablesStore() {
|
||||||
selected: table,
|
selected: table,
|
||||||
draft: cloneDeep(table),
|
draft: cloneDeep(table),
|
||||||
}))
|
}))
|
||||||
views.select({ name: `all_${table._id}` })
|
views.unselect()
|
||||||
|
queries.unselect()
|
||||||
|
datasources.unselect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +68,15 @@ export function createTablesStore() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
update,
|
||||||
fetch,
|
fetch,
|
||||||
select,
|
select,
|
||||||
|
unselect: () => {
|
||||||
|
update(state => ({
|
||||||
|
...state,
|
||||||
|
selected: null,
|
||||||
|
}))
|
||||||
|
},
|
||||||
save,
|
save,
|
||||||
init: async () => {
|
init: async () => {
|
||||||
const response = await api.get("/api/tables")
|
const response = await api.get("/api/tables")
|
||||||
|
|
|
@ -24,10 +24,10 @@ describe("Datasources Store", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fetches all the datasources and updates the store", async () => {
|
it("fetches all the datasources and updates the store", async () => {
|
||||||
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]})
|
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE] })
|
||||||
|
|
||||||
await store.fetch()
|
await store.fetch()
|
||||||
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null})
|
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("selects a datasource", async () => {
|
it("selects a datasource", async () => {
|
||||||
|
@ -44,7 +44,7 @@ describe("Datasources Store", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("saves the datasource, updates the store and returns status message", async () => {
|
it("saves the datasource, updates the store and returns status message", async () => {
|
||||||
api.post.mockReturnValue({ json: () => SAVE_DATASOURCE})
|
api.post.mockReturnValue({ status: 200, json: () => SAVE_DATASOURCE})
|
||||||
|
|
||||||
await store.save({
|
await store.save({
|
||||||
name: 'CoolDB',
|
name: 'CoolDB',
|
||||||
|
|
|
@ -30,13 +30,6 @@ describe("Queries Store", () => {
|
||||||
expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null})
|
expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("selects a query and updates selected datasource", async () => {
|
|
||||||
await store.select(SOME_QUERY)
|
|
||||||
|
|
||||||
expect(get(store).selected).toEqual(SOME_QUERY._id)
|
|
||||||
expect(get(datasources).selected).toEqual(SOME_QUERY.datasourceId)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("saves the query, updates the store and returns status message", async () => {
|
it("saves the query, updates the store and returns status message", async () => {
|
||||||
api.post.mockReturnValue({ json: () => SAVE_QUERY_RESPONSE})
|
api.post.mockReturnValue({ json: () => SAVE_QUERY_RESPONSE})
|
||||||
|
|
||||||
|
|
|
@ -41,14 +41,6 @@ describe("Tables Store", () => {
|
||||||
expect(get(store).draft).toEqual({})
|
expect(get(store).draft).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("selecting a table updates the view store", async () => {
|
|
||||||
const tableToSelect = SOME_TABLES[0]
|
|
||||||
await store.select(tableToSelect)
|
|
||||||
|
|
||||||
expect(get(store).selected).toEqual(tableToSelect)
|
|
||||||
expect(get(views).selected).toEqual({ name: `all_${tableToSelect._id}` })
|
|
||||||
})
|
|
||||||
|
|
||||||
it("saving a table also selects it", async () => {
|
it("saving a table also selects it", async () => {
|
||||||
api.post.mockReturnValue({ json: () => SAVE_TABLES_RESPONSE})
|
api.post.mockReturnValue({ json: () => SAVE_TABLES_RESPONSE})
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue