Merge branch 'master' of github.com:Budibase/budibase into component-binding-refactor
This commit is contained in:
commit
5bec329581
30
README.md
30
README.md
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://i.imgur.com/tMCahK8.png">
|
<img src="https://i.imgur.com/tPQHruf.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -69,17 +69,10 @@ When other platforms chose the closed source route, we decided to go open source
|
||||||
|
|
||||||
- **Cloud hosting and self-hosting.** Users can self-host (see below), or host their apps with Budibase. Currently, our cloud hosting offering is limited to the free tier but we aim to change this in the future. For heavy usage, we advise users to self-host.
|
- **Cloud hosting and self-hosting.** Users can self-host (see below), or host their apps with Budibase. Currently, our cloud hosting offering is limited to the free tier but we aim to change this in the future. For heavy usage, we advise users to self-host.
|
||||||
|
|
||||||
|
|
||||||
## 🤖 Self-hosting
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://i.imgur.com/Z52cEvT.png?1" />
|
<img alt="Budibase design ui" src="https://imgur.com/v8m6v3q.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Budibase wants to make sure anyone can use the tools we develop and we know a lot of people need to be able to host the apps they make on their own systems - that is why we've decided to try and make self hosting as easy as possible!
|
|
||||||
|
|
||||||
Currently, you can host your apps using Docker. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting).
|
|
||||||
|
|
||||||
|
|
||||||
## ⌛ Status
|
## ⌛ Status
|
||||||
- [x] Alpha: We are demoing Budibase to users and receiving feedback
|
- [x] Alpha: We are demoing Budibase to users and receiving feedback
|
||||||
|
@ -95,10 +88,6 @@ Watch "releases" of this repo to get notified of major updates, and give the sta
|
||||||
|
|
||||||
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment.
|
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment.
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
Checkout our [Public Roadmap](https://github.com/Budibase/budibase/projects/10). If you would like to discuss some of the items on the roadmap, please feel to reach out on [Discord](https://discord.gg/rCYayfe), or via [Github discussions](https://github.com/Budibase/budibase/discussions)
|
|
||||||
|
|
||||||
|
|
||||||
## 🏁 Getting Started with Budibase
|
## 🏁 Getting Started with Budibase
|
||||||
|
|
||||||
|
@ -111,10 +100,17 @@ The Budibase builder runs in Electron, on Mac, PC and Linux. Follow the steps be
|
||||||
|
|
||||||
[Here is a guided tutorial](https://docs.budibase.com/tutorial/tutorial-signing-up) if you need extra help.
|
[Here is a guided tutorial](https://docs.budibase.com/tutorial/tutorial-signing-up) if you need extra help.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Self-hosting
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Budibase design ui" src="https://imgur.com/v8m6v3q.png">
|
<img src="https://i.imgur.com/Z52cEvT.png?1" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
Budibase wants to make sure anyone can use the tools we develop and we know a lot of people need to be able to host the apps they make on their own systems - that is why we've decided to try and make self hosting as easy as possible!
|
||||||
|
|
||||||
|
Currently, you can host your apps using Docker. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting).
|
||||||
|
|
||||||
|
|
||||||
## 🎓 Learning Budibase
|
## 🎓 Learning Budibase
|
||||||
|
|
||||||
|
@ -122,6 +118,12 @@ The Budibase [documentation lives here](https://docs.budibase.com).
|
||||||
|
|
||||||
You can also follow a quick tutorial on [how to build a CRM with Budibase](https://docs.budibase.com/tutorial/tutorial-introduction)
|
You can also follow a quick tutorial on [how to build a CRM with Budibase](https://docs.budibase.com/tutorial/tutorial-introduction)
|
||||||
|
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
Checkout our [Public Roadmap](https://github.com/Budibase/budibase/projects/10). If you would like to discuss some of the items on the roadmap, please feel to reach out on [Discord](https://discord.gg/rCYayfe), or via [Github discussions](https://github.com/Budibase/budibase/discussions)
|
||||||
|
|
||||||
|
|
||||||
## ❗ Code of Conduct
|
## ❗ Code of Conduct
|
||||||
|
|
||||||
Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/master/.github/CODE_OF_CONDUCT.md). Please read it.
|
Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/master/.github/CODE_OF_CONDUCT.md). Please read it.
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
GITHUB_BASE_URL=https://raw.githubusercontent.com/Budibase/budibase/master/hosting
|
||||||
|
|
||||||
|
if ! [ -x "$(command -v wget)" ]; then
|
||||||
|
echo 'Error: wget is not installed. Please install it for your operating system.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
fetch_config_files() {
|
||||||
|
wget $GITHUB_BASE_URL/docker-compose.yaml
|
||||||
|
wget $GITHUB_BASE_URL/envoy.yaml
|
||||||
|
wget $GITHUB_BASE_URL/hosting.properties
|
||||||
|
wget $GITHUB_BASE_URL/start.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_config_files
|
||||||
|
|
||||||
|
# Start budibase
|
||||||
|
docker-compose --env-file hosting.properties up -d
|
|
@ -2,6 +2,7 @@ version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app-service:
|
app-service:
|
||||||
|
restart: always
|
||||||
image: budibase/budibase-apps
|
image: budibase/budibase-apps
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT}:4002"
|
- "${APP_PORT}:4002"
|
||||||
|
@ -18,6 +19,7 @@ services:
|
||||||
- worker-service
|
- worker-service
|
||||||
|
|
||||||
worker-service:
|
worker-service:
|
||||||
|
restart: always
|
||||||
image: budibase/budibase-worker
|
image: budibase/budibase-worker
|
||||||
ports:
|
ports:
|
||||||
- "${WORKER_PORT}:4003"
|
- "${WORKER_PORT}:4003"
|
||||||
|
@ -36,6 +38,7 @@ services:
|
||||||
- couch-init
|
- couch-init
|
||||||
|
|
||||||
minio-service:
|
minio-service:
|
||||||
|
restart: always
|
||||||
image: minio/minio
|
image: minio/minio
|
||||||
volumes:
|
volumes:
|
||||||
- minio_data:/data
|
- minio_data:/data
|
||||||
|
@ -53,6 +56,7 @@ services:
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
proxy-service:
|
proxy-service:
|
||||||
|
restart: always
|
||||||
image: envoyproxy/envoy:v1.16-latest
|
image: envoyproxy/envoy:v1.16-latest
|
||||||
volumes:
|
volumes:
|
||||||
- ./envoy.yaml:/etc/envoy/envoy.yaml
|
- ./envoy.yaml:/etc/envoy/envoy.yaml
|
||||||
|
@ -66,6 +70,7 @@ services:
|
||||||
- couchdb-service
|
- couchdb-service
|
||||||
|
|
||||||
couchdb-service:
|
couchdb-service:
|
||||||
|
restart: always
|
||||||
image: apache/couchdb:3.0
|
image: apache/couchdb:3.0
|
||||||
environment:
|
environment:
|
||||||
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.54.0",
|
"@budibase/bbui": "^1.54.1",
|
||||||
"@budibase/client": "^0.5.3",
|
"@budibase/client": "^0.5.3",
|
||||||
"@budibase/colorpicker": "^1.0.1",
|
"@budibase/colorpicker": "^1.0.1",
|
||||||
"@budibase/string-templates": "^0.5.3",
|
"@budibase/string-templates": "^0.5.3",
|
||||||
|
|
|
@ -103,6 +103,15 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column-header-name {
|
||||||
|
white-space: normal !important;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.sort-icon {
|
.sort-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
|
|
|
@ -43,8 +43,8 @@
|
||||||
<div class="datasource-icon" slot="icon">
|
<div class="datasource-icon" slot="icon">
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={ICONS[datasource.source]}
|
this={ICONS[datasource.source]}
|
||||||
height="15"
|
height="18"
|
||||||
width="15" />
|
width="18" />
|
||||||
</div>
|
</div>
|
||||||
<EditDatasourcePopover {datasource} />
|
<EditDatasourcePopover {datasource} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
@ -61,3 +61,10 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.datasource-icon {
|
||||||
|
margin-right: 3px;
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
<form>
|
<form>
|
||||||
{#each Object.keys(integration) as configKey}
|
{#each Object.keys(integration) as configKey}
|
||||||
<Input
|
<Input
|
||||||
thin
|
|
||||||
type={integration[configKey].type}
|
type={integration[configKey].type}
|
||||||
label={configKey}
|
label={configKey}
|
||||||
bind:value={integration[configKey]} />
|
bind:value={integration[configKey]} />
|
||||||
<Spacer medium />
|
<Spacer large />
|
||||||
{/each}
|
{/each}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore, store, allScreens } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import { DropdownMenu, Button, Input, TextButton, Icon } from "@budibase/bbui"
|
||||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import IntegrationConfigForm from "../TableIntegrationMenu//IntegrationConfigForm.svelte"
|
||||||
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||||
|
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||||
|
|
||||||
|
export let bindable
|
||||||
|
export let parameters
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let dropdown
|
||||||
|
let confirmDeleteDialog
|
||||||
|
|
||||||
|
function hideEditor() {
|
||||||
|
dropdown?.hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div on:click|stopPropagation bind:this={anchor}>
|
||||||
|
<TextButton
|
||||||
|
text
|
||||||
|
on:click={dropdown.show}
|
||||||
|
active={false}>
|
||||||
|
<Icon name="add" />
|
||||||
|
Add Parameters
|
||||||
|
</TextButton>
|
||||||
|
<DropdownMenu align="right" {anchor} bind:this={dropdown}>
|
||||||
|
<div class="wrapper">
|
||||||
|
<ParameterBuilder bind:parameters {bindable} />
|
||||||
|
</div>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { notificationStore } from "builderStore/store/notifications"
|
import { notificationStore } from "builderStore/store/notifications"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { flip } from 'svelte/animate';
|
||||||
import { fly } from "svelte/transition"
|
import { fly } from "svelte/transition"
|
||||||
|
|
||||||
export let themes = {
|
export let themes = {
|
||||||
|
@ -27,6 +27,7 @@
|
||||||
<div class="notifications">
|
<div class="notifications">
|
||||||
{#each $notificationStore.notifications as notification (notification.id)}
|
{#each $notificationStore.notifications as notification (notification.id)}
|
||||||
<div
|
<div
|
||||||
|
animate:flip
|
||||||
class="toast"
|
class="toast"
|
||||||
style="background: {themes[notification.type]};"
|
style="background: {themes[notification.type]};"
|
||||||
transition:fly={{ y: -30 }}>
|
transition:fly={{ y: -30 }}>
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary small on:click={drawer.show}>Define Actions</Button>
|
<Button secondary wide on:click={drawer.show}>Define Actions</Button>
|
||||||
<Drawer bind:this={drawer} title={'Actions'}>
|
<Drawer bind:this={drawer} title={'Actions'}>
|
||||||
<heading slot="buttons">
|
<heading slot="buttons">
|
||||||
<Button thin blue on:click={saveEventData}>Save</Button>
|
<Button thin blue on:click={saveEventData}>Save</Button>
|
||||||
|
|
|
@ -78,9 +78,7 @@
|
||||||
const source = $backendUiStore.datasources.find(
|
const source = $backendUiStore.datasources.find(
|
||||||
ds => ds._id === query.datasourceId
|
ds => ds._id === query.datasourceId
|
||||||
).source
|
).source
|
||||||
return $backendUiStore.integrations[source].query[query.queryVerb][
|
return $backendUiStore.integrations[source].query[query.queryVerb]
|
||||||
query.queryType
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -155,8 +155,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.CodeMirror) {
|
:global(.CodeMirror) {
|
||||||
height: auto !important;
|
height: 500px !important;
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: var(--border-radius-s);
|
||||||
font-family: var(--font-sans) !important;
|
font-family: monospace !important;
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -24,18 +24,18 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault>
|
<form on:submit|preventDefault>
|
||||||
|
<div class="field">
|
||||||
{#each schemaKeys as field}
|
{#each schemaKeys as field}
|
||||||
<Label extraSmall grey>{field}</Label>
|
|
||||||
<div class="field">
|
|
||||||
<Input
|
<Input
|
||||||
|
placeholder="Enter {field} name"
|
||||||
|
outline
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
type={schema.fields[field]?.type}
|
type={schema.fields[field]?.type}
|
||||||
required={schema.fields[field]?.required}
|
required={schema.fields[field]?.required}
|
||||||
bind:value={fields[field]} />
|
bind:value={fields[field]} />
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<Label extraSmall grey>Data</Label>
|
|
||||||
{#if schema.customisable}
|
{#if schema.customisable}
|
||||||
<Editor
|
<Editor
|
||||||
label="Query"
|
label="Query"
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
.field {
|
.field {
|
||||||
margin-bottom: var(--spacing-m);
|
margin-bottom: var(--spacing-m);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 2%;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-gap: var(--spacing-m);
|
grid-gap: var(--spacing-m);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Label, Input, Heading } from "@budibase/bbui"
|
import { Button, Input, Heading, Spacer } from "@budibase/bbui"
|
||||||
import BindableInput from "components/common/BindableInput.svelte"
|
import BindableInput from "components/common/BindableInput.svelte"
|
||||||
import {
|
import {
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
|
@ -31,19 +31,22 @@
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Heading extraSmall black>Parameters</Heading>
|
<Heading extraSmall black>Parameters</Heading>
|
||||||
|
<Spacer large />
|
||||||
<div class="parameters" class:bindable>
|
<div class="parameters" class:bindable>
|
||||||
<Label extraSmall grey>Parameter Name</Label>
|
|
||||||
<Label extraSmall grey>Default</Label>
|
|
||||||
{#if bindable}
|
|
||||||
<Label extraSmall grey>Value</Label>
|
|
||||||
{:else}
|
|
||||||
<div />
|
|
||||||
{/if}
|
|
||||||
{#each parameters as parameter, idx}
|
{#each parameters as parameter, idx}
|
||||||
<Input thin disabled={bindable} bind:value={parameter.name} />
|
<Input
|
||||||
<Input thin disabled={bindable} bind:value={parameter.default} />
|
placeholder="Parameter Name"
|
||||||
|
thin
|
||||||
|
disabled={bindable}
|
||||||
|
bind:value={parameter.name} />
|
||||||
|
<Input
|
||||||
|
placeholder="Default"
|
||||||
|
thin
|
||||||
|
disabled={bindable}
|
||||||
|
bind:value={parameter.default} />
|
||||||
{#if bindable}
|
{#if bindable}
|
||||||
<BindableInput
|
<BindableInput
|
||||||
|
placeholder="Value"
|
||||||
type="string"
|
type="string"
|
||||||
thin
|
thin
|
||||||
on:change={evt => onBindingChange(parameter.name, evt.detail)}
|
on:change={evt => onBindingChange(parameter.name, evt.detail)}
|
||||||
|
@ -57,9 +60,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if !bindable}
|
{#if !bindable}
|
||||||
<Button thin secondary small on:click={newQueryParameter}>
|
<Button secondary on:click={newQueryParameter}>Add Parameter</Button>
|
||||||
Add Parameter
|
|
||||||
</Button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
|
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
|
||||||
|
import EditQueryParamsPopover from "components/backend/DatasourceNavigator/popovers/EditQueryParamsPopover.svelte"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
const PREVIEW_HEADINGS = [
|
const PREVIEW_HEADINGS = [
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
let tab = "JSON"
|
let tab = "JSON"
|
||||||
let parameters
|
let parameters
|
||||||
let data = []
|
let data = []
|
||||||
|
let popover
|
||||||
|
|
||||||
$: datasource = $backendUiStore.datasources.find(
|
$: datasource = $backendUiStore.datasources.find(
|
||||||
ds => ds._id === query.datasourceId
|
ds => ds._id === query.datasourceId
|
||||||
|
@ -61,7 +63,7 @@
|
||||||
$: config = $backendUiStore.integrations[datasourceType]?.query
|
$: config = $backendUiStore.integrations[datasourceType]?.query
|
||||||
$: docsLink = $backendUiStore.integrations[datasourceType]?.docs
|
$: docsLink = $backendUiStore.integrations[datasourceType]?.docs
|
||||||
|
|
||||||
$: shouldShowQueryConfig = config && query.queryVerb && query.queryType
|
$: shouldShowQueryConfig = config && query.queryVerb
|
||||||
|
|
||||||
function newField() {
|
function newField() {
|
||||||
fields = [...fields, {}]
|
fields = [...fields, {}]
|
||||||
|
@ -129,62 +131,44 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<Heading small>{query.name}</Heading>
|
<div class="input">
|
||||||
|
<Input placeholder="✎ Edit Query Name" bind:value={query.name} />
|
||||||
|
</div>
|
||||||
{#if config}
|
{#if config}
|
||||||
<div class="queryVerbs">
|
<div class="props">
|
||||||
|
<div class="query-type">Query type: <span class="query-type-span">{config[query.queryVerb].type}</span></div>
|
||||||
|
<div class="select">
|
||||||
|
<Select primary thin bind:value={query.queryVerb}>
|
||||||
{#each Object.keys(config) as queryVerb}
|
{#each Object.keys(config) as queryVerb}
|
||||||
<div
|
<option value={queryVerb}>{queryVerb}</option>
|
||||||
class="queryVerb"
|
|
||||||
class:selected={queryVerb === query.queryVerb}
|
|
||||||
on:click={() => {
|
|
||||||
query.queryVerb = queryVerb
|
|
||||||
}}>
|
|
||||||
{queryVerb}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{#if query.queryVerb}
|
|
||||||
<Select thin secondary bind:value={query.queryType}>
|
|
||||||
<option value={''}>Select an option</option>
|
|
||||||
{#each Object.keys(config[query.queryVerb]) as queryType}
|
|
||||||
<option value={queryType}>{queryType}</option>
|
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
{/if}
|
</div>
|
||||||
<Spacer medium />
|
</div>
|
||||||
<Button primary href={docsLink} target="_blank">
|
<EditQueryParamsPopover bind:parameters={query.parameters} bindable={false} />
|
||||||
<i class="ri-book-2-line" />
|
|
||||||
</Button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</header>
|
</header>
|
||||||
|
<Spacer extraLarge />
|
||||||
<Spacer large />
|
|
||||||
|
|
||||||
{#if shouldShowQueryConfig}
|
{#if shouldShowQueryConfig}
|
||||||
<section>
|
<section>
|
||||||
<div class="config">
|
<div class="config">
|
||||||
<Label extraSmall grey>Query Name</Label>
|
|
||||||
<Input thin bind:value={query.name} />
|
|
||||||
|
|
||||||
<Spacer medium />
|
|
||||||
|
|
||||||
<IntegrationQueryEditor
|
<IntegrationQueryEditor
|
||||||
{query}
|
{query}
|
||||||
schema={config[query.queryVerb][query.queryType]}
|
schema={config[query.queryVerb]}
|
||||||
bind:parameters />
|
bind:parameters />
|
||||||
|
|
||||||
<Spacer medium />
|
<Spacer extraLarge />
|
||||||
|
<Spacer large />
|
||||||
|
|
||||||
<div class="viewer-controls">
|
<div class="viewer-controls">
|
||||||
<Button
|
<Button
|
||||||
wide
|
|
||||||
thin
|
|
||||||
blue
|
blue
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
on:click={saveQuery}>
|
on:click={saveQuery}>
|
||||||
Save
|
Save Query
|
||||||
</Button>
|
</Button>
|
||||||
<Button wide thin primary on:click={previewQuery}>Run</Button>
|
<Button primary on:click={previewQuery}>Run Query</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="viewer">
|
<section class="viewer">
|
||||||
|
@ -196,10 +180,11 @@
|
||||||
<ExternalDataSourceTable {query} {data} />
|
<ExternalDataSourceTable {query} {data} />
|
||||||
{:else if tab === 'SCHEMA'}
|
{:else if tab === 'SCHEMA'}
|
||||||
{#each fields as field, idx}
|
{#each fields as field, idx}
|
||||||
|
<Spacer small />
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Input thin type={'text'} bind:value={field.name} />
|
<Input outline placeholder="Field Name" type={'text'} bind:value={field.name} />
|
||||||
<Select secondary thin bind:value={field.type}>
|
<Select thin border bind:value={field.type}>
|
||||||
<option value={''}>Select an option</option>
|
<option value={''}>Select a field type</option>
|
||||||
<option value={'STRING'}>Text</option>
|
<option value={'STRING'}>Text</option>
|
||||||
<option value={'NUMBER'}>Number</option>
|
<option value={'NUMBER'}>Number</option>
|
||||||
<option value={'BOOLEAN'}>Boolean</option>
|
<option value={'BOOLEAN'}>Boolean</option>
|
||||||
|
@ -210,6 +195,7 @@
|
||||||
on:click={() => deleteField(idx)} />
|
on:click={() => deleteField(idx)} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
<Spacer small />
|
||||||
<Button thin secondary on:click={newField}>Add Field</Button>
|
<Button thin secondary on:click={newField}>Add Field</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</Switcher>
|
</Switcher>
|
||||||
|
@ -220,11 +206,28 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
width: 200px;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.props {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-left: auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--layout-l);
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 10px;
|
|
||||||
grid-template-columns: 1fr 1fr 50px;
|
grid-template-columns: 1fr 1fr 50px;
|
||||||
margin-bottom: var(--spacing-m);
|
gap: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -240,6 +243,16 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.query-type {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
color: var(--grey-8);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-type-span {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
width: 800px;
|
width: 800px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -253,32 +266,18 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queryVerbs {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
font-size: var(--font-size-m);
|
|
||||||
align-items: center;
|
|
||||||
margin-left: var(--spacing-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryVerb {
|
|
||||||
text-transform: capitalize;
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
color: var(--grey-5);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
color: var(--white);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewer-controls {
|
.viewer-controls {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-gap: var(--spacing-m);
|
flex-direction: row;
|
||||||
grid-auto-flow: column;
|
margin-left: auto;
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
grid-template-columns: 10% 10% 1fr;
|
z-index: 5;
|
||||||
margin-bottom: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer {
|
||||||
|
margin-top: -28px;
|
||||||
|
z-index: -2;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -20,13 +20,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if editable}
|
|
||||||
<ParameterBuilder bind:parameters={query.parameters} bindable={false} />
|
|
||||||
<Spacer large />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Heading extraSmall black>Query</Heading>
|
|
||||||
<Spacer medium />
|
|
||||||
|
|
||||||
{#if schema}
|
{#if schema}
|
||||||
{#key query._id}
|
{#key query._id}
|
||||||
|
@ -38,7 +31,6 @@
|
||||||
readOnly={!editable}
|
readOnly={!editable}
|
||||||
value={query.fields.sql} />
|
value={query.fields.sql} />
|
||||||
{:else if schema.type === QueryTypes.JSON}
|
{:else if schema.type === QueryTypes.JSON}
|
||||||
<Spacer large />
|
|
||||||
<Editor
|
<Editor
|
||||||
label="Query"
|
label="Query"
|
||||||
mode="json"
|
mode="json"
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/Budibase/budibase/discussions">
|
href="https://github.com/Budibase/budibase/discussions">
|
||||||
<i class="ri-question-line" />
|
<i class="ri-github-fill" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<SettingsLink />
|
<SettingsLink />
|
||||||
|
@ -89,8 +89,8 @@
|
||||||
<div class="beta">
|
<div class="beta">
|
||||||
<Button
|
<Button
|
||||||
secondary
|
secondary
|
||||||
href="https://www.budibase.com/blog/budibase-public-beta/">
|
href="https://github.com/Budibase/budibase/discussions/categories/ideas">
|
||||||
Budibase is in Beta
|
Request feature
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
query => query._id === $backendUiStore.selectedQueryId
|
query => query._id === $backendUiStore.selectedQueryId
|
||||||
) || {
|
) || {
|
||||||
datasourceId: $params.selectedDatasource,
|
datasourceId: $params.selectedDatasource,
|
||||||
name: "New Query",
|
|
||||||
parameters: [],
|
parameters: [],
|
||||||
fields: {},
|
fields: {},
|
||||||
|
queryVerb: "read",
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -35,8 +35,10 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
section {
|
||||||
background: var(--background);
|
overflow: scroll;
|
||||||
padding: var(--spacing-xl);
|
}
|
||||||
border-radius: var(--border-radius-m);
|
::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
background: transparent; /* make scrollbar transparent */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { Button, Spacer, Icon, TextButton } from "@budibase/bbui"
|
import { Button, Spacer, Icon } from "@budibase/bbui"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||||
|
@ -14,31 +14,129 @@
|
||||||
await backendUiStore.actions.datasources.save(datasource)
|
await backendUiStore.actions.datasources.save(datasource)
|
||||||
notifier.success(`Datasource ${name} saved successfully.`)
|
notifier.success(`Datasource ${name} saved successfully.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onClickQuery(query) {
|
||||||
|
if ($backendUiStore.selectedQueryId === query._id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
backendUiStore.actions.queries.select(query)
|
||||||
|
$goto(`../${query._id}`)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if datasource}
|
{#if datasource}
|
||||||
<TextButton text small on:click={() => $goto('../new')}>
|
|
||||||
<Icon name="filter" />
|
|
||||||
Create Query
|
|
||||||
</TextButton>
|
|
||||||
<section>
|
<section>
|
||||||
<h4>{datasource.name}: Configuration</h4>
|
|
||||||
<IntegrationConfigForm integration={datasource.config} />
|
|
||||||
<Spacer medium />
|
<Spacer medium />
|
||||||
<footer>
|
<header>
|
||||||
<Button blue wide on:click={saveDatasource}>Save</Button>
|
<h3 class="section-title">{datasource.name}</h3>
|
||||||
</footer>
|
</header>
|
||||||
|
<Spacer extraLarge />
|
||||||
|
<div class="container">
|
||||||
|
<div class="config-header">
|
||||||
|
<h5>Configuration</h5>
|
||||||
|
<Button secondary on:click={saveDatasource}>Save</Button>
|
||||||
|
</div>
|
||||||
|
<Spacer medium />
|
||||||
|
<IntegrationConfigForm integration={datasource.config} />
|
||||||
|
</div>
|
||||||
|
<Spacer extraLarge />
|
||||||
|
<div class="container">
|
||||||
|
<div class="query-header">
|
||||||
|
<h5>Queries</h5>
|
||||||
|
<Button blue on:click={() => $goto('../new')}>Create Query</Button>
|
||||||
|
</div>
|
||||||
|
<Spacer extraLarge />
|
||||||
|
<div class="query-list">
|
||||||
|
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
|
||||||
|
<div class="query-list-item" on:click={() => onClickQuery(query)}>
|
||||||
|
<p class="query-name">{query.name}</p>
|
||||||
|
<p>{query.queryVerb}</p>
|
||||||
|
<p>4000 records</p>
|
||||||
|
<p>→</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<Spacer medium />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h4 {
|
h3 {
|
||||||
margin-top: var(--spacing-xl);
|
margin: 0;
|
||||||
margin-bottom: var(--spacing-s);
|
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
background: var(--background);
|
margin: 0 auto;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin: 0 0 var(--spacing-xs) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0 0 var(--spacing-xs) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: var(--border-radius-m);
|
||||||
padding: var(--spacing-xl);
|
background: var(--background);
|
||||||
|
padding: var(--layout-s);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 0 var(--spacing-s) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-list-item {
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
background: var(--background);
|
||||||
|
border: var(--border-grey);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 0.75fr 0.75fr 1fr 20px;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-m) var(--layout-xs);
|
||||||
|
gap: var(--layout-xs);
|
||||||
|
transition: 200ms background ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-list-item:hover {
|
||||||
|
background: var(--grey-1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--grey-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-name {
|
||||||
|
color: var(--ink);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: var(--font-size-s);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -842,10 +842,10 @@
|
||||||
lodash "^4.17.19"
|
lodash "^4.17.19"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@budibase/bbui@^1.54.0":
|
"@budibase/bbui@^1.54.1":
|
||||||
version "1.54.0"
|
version "1.54.1"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.54.0.tgz#60e6c0faa3d8f1781c503e74f8b8990f75ba2c40"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.54.1.tgz#ad0439c0be6a4dc818cd9dacda00f053b0daa9d5"
|
||||||
integrity sha512-98koXkueqda6oQT6q0NPNvdL878ETRevtmmm34aSz9C6B4Oz68VVCsiFzRWuHvP/7wiNaAxMgY1nsEsCwP3LpQ==
|
integrity sha512-ZY2OP/tF+ReMSyzZIGZV6wpQ4eIEzYGxZV3n+C+oNjzK5u3rwWPCDEVDlZgJSqJ61z+sEf2zuIyAh88lq9RTaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
markdown-it "^12.0.2"
|
markdown-it "^12.0.2"
|
||||||
quill "^1.3.7"
|
quill "^1.3.7"
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"@budibase/string-templates": "^0.5.3",
|
"@budibase/string-templates": "^0.5.3",
|
||||||
"deep-equal": "^2.0.1",
|
"deep-equal": "^2.0.1",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* API cache for cached request responses.
|
* API cache for cached request responses.
|
||||||
*/
|
*/
|
||||||
|
import { notificationStore } from "../store/notification"
|
||||||
let cache = {}
|
let cache = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,10 +36,12 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
|
||||||
case 200:
|
case 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
case 404:
|
case 404:
|
||||||
|
notificationStore.danger("Not found")
|
||||||
return handleError(`${url}: Not Found`)
|
return handleError(`${url}: Not Found`)
|
||||||
case 400:
|
case 400:
|
||||||
return handleError(`${url}: Bad Request`)
|
return handleError(`${url}: Bad Request`)
|
||||||
case 403:
|
case 403:
|
||||||
|
notificationStore.danger("Forbidden")
|
||||||
return handleError(`${url}: Forbidden`)
|
return handleError(`${url}: Forbidden`)
|
||||||
default:
|
default:
|
||||||
if (response.status >= 200 && response.status < 400) {
|
if (response.status >= 200 && response.status < 400) {
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
import { notificationStore } from "../store/notification"
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
/**
|
/**
|
||||||
* Executes an automation. Must have "App Action" trigger.
|
* Executes an automation. Must have "App Action" trigger.
|
||||||
*/
|
*/
|
||||||
export const triggerAutomation = async (automationId, fields) => {
|
export const triggerAutomation = async (automationId, fields) => {
|
||||||
return await API.post({
|
const res = await API.post({
|
||||||
url: `/api/automations/${automationId}/trigger`,
|
url: `/api/automations/${automationId}/trigger`,
|
||||||
body: { fields },
|
body: { fields },
|
||||||
})
|
})
|
||||||
|
res.error
|
||||||
|
? notificationStore.danger("An error has occurred")
|
||||||
|
: notificationStore.success("Automation triggered")
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
|
import { notificationStore } from "../store/notification"
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a query against an external data connector.
|
* Executes a query against an external data connector.
|
||||||
*/
|
*/
|
||||||
export const executeQuery = async ({ queryId, parameters }) => {
|
export const executeQuery = async ({ queryId, parameters }) => {
|
||||||
const response = await API.post({
|
const res = await API.post({
|
||||||
url: `/api/queries/${queryId}`,
|
url: `/api/queries/${queryId}`,
|
||||||
body: {
|
body: {
|
||||||
parameters,
|
parameters,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return response
|
res.error
|
||||||
|
? notificationStore.danger("An error has occurred")
|
||||||
|
: notificationStore.success("Query successful")
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { notificationStore } from "../store/notification"
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
import { fetchTableDefinition } from "./tables"
|
import { fetchTableDefinition } from "./tables"
|
||||||
|
|
||||||
|
@ -15,42 +16,58 @@ export const fetchRow = async ({ tableId, rowId }) => {
|
||||||
* Creates a row in a table.
|
* Creates a row in a table.
|
||||||
*/
|
*/
|
||||||
export const saveRow = async row => {
|
export const saveRow = async row => {
|
||||||
return await API.post({
|
const res = await API.post({
|
||||||
url: `/api/${row.tableId}/rows`,
|
url: `/api/${row.tableId}/rows`,
|
||||||
body: row,
|
body: row,
|
||||||
})
|
})
|
||||||
|
res.error
|
||||||
|
? notificationStore.danger("An error has occurred")
|
||||||
|
: notificationStore.success("Row saved")
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a row in a table.
|
* Updates a row in a table.
|
||||||
*/
|
*/
|
||||||
export const updateRow = async row => {
|
export const updateRow = async row => {
|
||||||
return await API.patch({
|
const res = await API.patch({
|
||||||
url: `/api/${row.tableId}/rows/${row._id}`,
|
url: `/api/${row.tableId}/rows/${row._id}`,
|
||||||
body: row,
|
body: row,
|
||||||
})
|
})
|
||||||
|
res.error
|
||||||
|
? notificationStore.danger("An error has occurred")
|
||||||
|
: notificationStore.success("Row updated")
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a row from a table.
|
* Deletes a row from a table.
|
||||||
*/
|
*/
|
||||||
export const deleteRow = async ({ tableId, rowId, revId }) => {
|
export const deleteRow = async ({ tableId, rowId, revId }) => {
|
||||||
return await API.del({
|
const res = await API.del({
|
||||||
url: `/api/${tableId}/rows/${rowId}/${revId}`,
|
url: `/api/${tableId}/rows/${rowId}/${revId}`,
|
||||||
})
|
})
|
||||||
|
res.error
|
||||||
|
? notificationStore.danger("An error has occurred")
|
||||||
|
: notificationStore.success("Row deleted")
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes many rows from a table.
|
* Deletes many rows from a table.
|
||||||
*/
|
*/
|
||||||
export const deleteRows = async ({ tableId, rows }) => {
|
export const deleteRows = async ({ tableId, rows }) => {
|
||||||
return await API.post({
|
const res = await API.post({
|
||||||
url: `/api/${tableId}/rows`,
|
url: `/api/${tableId}/rows`,
|
||||||
body: {
|
body: {
|
||||||
rows,
|
rows,
|
||||||
type: "delete",
|
type: "delete",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
res.error
|
||||||
|
? notificationStore.danger("An error has occurred")
|
||||||
|
: notificationStore.success(`${rows.length} row(s) deleted`)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { setContext, onMount } from "svelte"
|
import { setContext, onMount } from "svelte"
|
||||||
import Component from "./Component.svelte"
|
import Component from "./Component.svelte"
|
||||||
|
import NotificationDisplay from './NotificationDisplay.svelte'
|
||||||
import SDK from "../sdk"
|
import SDK from "../sdk"
|
||||||
import { createDataStore, initialise, screenStore } from "../store"
|
import { createDataStore, initialise, screenStore, notificationStore } from "../store"
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
setContext("sdk", SDK)
|
setContext("sdk", SDK)
|
||||||
|
@ -23,3 +24,4 @@
|
||||||
{#if loaded && $screenStore.activeLayout}
|
{#if loaded && $screenStore.activeLayout}
|
||||||
<Component definition={$screenStore.activeLayout.props} />
|
<Component definition={$screenStore.activeLayout.props} />
|
||||||
{/if}
|
{/if}
|
||||||
|
<NotificationDisplay />
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script>
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
import { fly } from "svelte/transition"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
const { notifications } = getContext("sdk")
|
||||||
|
|
||||||
|
export let themes = {
|
||||||
|
danger: "#E26D69",
|
||||||
|
success: "#84C991",
|
||||||
|
warning: "#f0ad4e",
|
||||||
|
info: "#5bc0de",
|
||||||
|
default: "#aaaaaa",
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="notifications">
|
||||||
|
{#each $notifications as notification (notification.id)}
|
||||||
|
<div
|
||||||
|
animate:flip
|
||||||
|
class="toast"
|
||||||
|
style="background: {themes[notification.type]};"
|
||||||
|
transition:fly={{ y: -30 }}>
|
||||||
|
<div class="content">{notification.message}</div>
|
||||||
|
{#if notification.icon}<i class={notification.icon} />{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.notifications {
|
||||||
|
position: fixed;
|
||||||
|
top: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
/* The toasts now support being auto sized, so this static width could be removed */
|
||||||
|
width: 40vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 10px;
|
||||||
|
display: block;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import * as API from "./api"
|
import * as API from "./api"
|
||||||
import { authStore, routeStore, screenStore, bindingStore } from "./store"
|
import {
|
||||||
|
authStore,
|
||||||
|
notificationStore,
|
||||||
|
routeStore,
|
||||||
|
screenStore,
|
||||||
|
bindingStore,
|
||||||
|
} from "./store"
|
||||||
import { styleable } from "./utils/styleable"
|
import { styleable } from "./utils/styleable"
|
||||||
import { linkable } from "./utils/linkable"
|
import { linkable } from "./utils/linkable"
|
||||||
import DataProvider from "./components/DataProvider.svelte"
|
import DataProvider from "./components/DataProvider.svelte"
|
||||||
|
@ -7,6 +13,7 @@ import DataProvider from "./components/DataProvider.svelte"
|
||||||
export default {
|
export default {
|
||||||
API,
|
API,
|
||||||
authStore,
|
authStore,
|
||||||
|
notifications: notificationStore,
|
||||||
routeStore,
|
routeStore,
|
||||||
screenStore,
|
screenStore,
|
||||||
styleable,
|
styleable,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export { authStore } from "./auth"
|
export { authStore } from "./auth"
|
||||||
|
export { notificationStore } from "./notification"
|
||||||
export { routeStore } from "./routes"
|
export { routeStore } from "./routes"
|
||||||
export { screenStore } from "./screens"
|
export { screenStore } from "./screens"
|
||||||
export { builderStore } from "./builder"
|
export { builderStore } from "./builder"
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { writable, derived } from "svelte/store"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
|
||||||
|
const NOTIFICATION_TIMEOUT = 3000
|
||||||
|
|
||||||
|
const createNotificationStore = () => {
|
||||||
|
const _notifications = writable([])
|
||||||
|
|
||||||
|
const send = (message, type = "default") => {
|
||||||
|
_notifications.update(state => {
|
||||||
|
return [...state, { id: generate(), type, message }]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifications = derived(_notifications, ($_notifications, set) => {
|
||||||
|
set($_notifications)
|
||||||
|
if ($_notifications.length > 0) {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
_notifications.update(state => {
|
||||||
|
state.shift()
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
set($_notifications)
|
||||||
|
}, NOTIFICATION_TIMEOUT)
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { subscribe } = notifications
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
send,
|
||||||
|
danger: msg => send(msg, "danger"),
|
||||||
|
warning: msg => send(msg, "warning"),
|
||||||
|
info: msg => send(msg, "info"),
|
||||||
|
success: msg => send(msg, "success"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notificationStore = createNotificationStore()
|
|
@ -1362,6 +1362,11 @@ minimatch@^3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
|
nanoid@^2.1.0:
|
||||||
|
version "2.1.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
|
||||||
|
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
|
||||||
|
|
||||||
nwsapi@^2.2.0:
|
nwsapi@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
|
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
|
||||||
|
@ -1803,6 +1808,13 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
shortid@^2.2.15:
|
||||||
|
version "2.2.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608"
|
||||||
|
integrity sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^2.1.0"
|
||||||
|
|
||||||
side-channel@^1.0.2:
|
side-channel@^1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3"
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3"
|
||||||
|
|
|
@ -31,7 +31,6 @@ function generateQueryValidation() {
|
||||||
default: Joi.string()
|
default: Joi.string()
|
||||||
})),
|
})),
|
||||||
queryVerb: Joi.string().allow(...Object.values(QueryVerb)).required(),
|
queryVerb: Joi.string().allow(...Object.values(QueryVerb)).required(),
|
||||||
queryType: Joi.string().required(),
|
|
||||||
schema: Joi.object({}).required().unknown(true)
|
schema: Joi.object({}).required().unknown(true)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ const TEST_QUERY = {
|
||||||
fields:{},
|
fields:{},
|
||||||
schema:{},
|
schema:{},
|
||||||
queryVerb:"read",
|
queryVerb:"read",
|
||||||
queryType:"Table",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("/datasources", () => {
|
describe("/datasources", () => {
|
||||||
|
|
|
@ -26,7 +26,6 @@ const TEST_QUERY = {
|
||||||
fields:{},
|
fields:{},
|
||||||
schema:{},
|
schema:{},
|
||||||
queryVerb:"read",
|
queryVerb:"read",
|
||||||
queryType:"Table",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("/queries", () => {
|
describe("/queries", () => {
|
||||||
|
|
|
@ -17,7 +17,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
"Airtable Record": {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
customisable: true,
|
customisable: true,
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -27,9 +26,7 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
read: {
|
read: {
|
||||||
Table: {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
table: {
|
table: {
|
||||||
|
@ -42,9 +39,7 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
update: {
|
update: {
|
||||||
Fields: {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
customisable: true,
|
customisable: true,
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -54,13 +49,10 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
delete: {
|
delete: {
|
||||||
"Airtable Ids": {
|
|
||||||
type: FIELD_TYPES.JSON,
|
type: FIELD_TYPES.JSON,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AirtableIntegration {
|
class AirtableIntegration {
|
||||||
|
|
|
@ -16,22 +16,15 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
"CouchDB DSL": {
|
|
||||||
type: QUERY_TYPES.JSON,
|
type: QUERY_TYPES.JSON,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
read: {
|
read: {
|
||||||
"CouchDB DSL": {
|
|
||||||
type: QUERY_TYPES.JSON,
|
type: QUERY_TYPES.JSON,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
update: {
|
update: {
|
||||||
"CouchDB Document": {
|
|
||||||
type: QUERY_TYPES.JSON,
|
type: QUERY_TYPES.JSON,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
delete: {
|
delete: {
|
||||||
"Document ID": {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
id: {
|
id: {
|
||||||
|
@ -41,7 +34,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CouchDBIntegration {
|
class CouchDBIntegration {
|
||||||
|
|
|
@ -20,7 +20,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
DynamoConfig: {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
table: {
|
table: {
|
||||||
|
@ -30,9 +29,7 @@ const SCHEMA = {
|
||||||
customisable: true,
|
customisable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
read: {
|
read: {
|
||||||
DynamoConfig: {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
table: {
|
table: {
|
||||||
|
@ -45,9 +42,7 @@ const SCHEMA = {
|
||||||
customisable: true,
|
customisable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
update: {
|
update: {
|
||||||
DynamoConfig: {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
table: {
|
table: {
|
||||||
|
@ -57,9 +52,7 @@ const SCHEMA = {
|
||||||
customisable: true,
|
customisable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
delete: {
|
delete: {
|
||||||
"Dynamo Partition Key": {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
table: {
|
table: {
|
||||||
|
@ -73,7 +66,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DynamoDBIntegration {
|
class DynamoDBIntegration {
|
||||||
|
|
|
@ -13,7 +13,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
"ES Query DSL": {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
customisable: true,
|
customisable: true,
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -23,9 +22,7 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
read: {
|
read: {
|
||||||
"ES Query DSL": {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
customisable: true,
|
customisable: true,
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -35,9 +32,7 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
update: {
|
update: {
|
||||||
"ES Query DSL": {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
customisable: true,
|
customisable: true,
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -51,9 +46,7 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
delete: {
|
delete: {
|
||||||
"Document ID": {
|
|
||||||
type: QUERY_TYPES.FIELDS,
|
type: QUERY_TYPES.FIELDS,
|
||||||
fields: {
|
fields: {
|
||||||
index: {
|
index: {
|
||||||
|
@ -67,7 +60,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElasticSearchIntegration {
|
class ElasticSearchIntegration {
|
||||||
|
|
|
@ -24,16 +24,12 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
SQL: {
|
|
||||||
type: "sql",
|
type: "sql",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
read: {
|
read: {
|
||||||
SQL: {
|
|
||||||
type: "sql",
|
type: "sql",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SqlServerIntegration {
|
class SqlServerIntegration {
|
||||||
|
|
|
@ -20,16 +20,12 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
JSON: {
|
|
||||||
type: QUERY_TYPES.JSON,
|
type: QUERY_TYPES.JSON,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
read: {
|
read: {
|
||||||
JSON: {
|
|
||||||
type: QUERY_TYPES.JSON,
|
type: QUERY_TYPES.JSON,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MongoIntegration {
|
class MongoIntegration {
|
||||||
|
|
|
@ -31,26 +31,18 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
SQL: {
|
|
||||||
type: "sql",
|
type: "sql",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
read: {
|
read: {
|
||||||
SQL: {
|
|
||||||
type: "sql",
|
type: "sql",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
update: {
|
update: {
|
||||||
SQL: {
|
|
||||||
type: "sql",
|
type: "sql",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
delete: {
|
delete: {
|
||||||
SQL: {
|
|
||||||
type: "sql",
|
type: "sql",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostgresIntegration {
|
class PostgresIntegration {
|
||||||
|
|
|
@ -19,7 +19,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
read: {
|
read: {
|
||||||
Bucket: {
|
|
||||||
type: "fields",
|
type: "fields",
|
||||||
fields: {
|
fields: {
|
||||||
bucket: {
|
bucket: {
|
||||||
|
@ -29,7 +28,6 @@ const SCHEMA = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class S3Integration {
|
class S3Integration {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue