forked from tsai/budibase
committed by
GitHub
33 changed files with 1375 additions and 261 deletions
@ -1,15 +1,37 @@ |
|||||
<script> |
<script> |
||||
import { Input, TextArea, Spacer } from "@budibase/bbui" |
import { Label, Input, TextArea, Spacer } from "@budibase/bbui" |
||||
|
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" |
||||
|
|
||||
export let integration |
export let integration |
||||
|
|
||||
|
let unsaved = false |
||||
</script> |
</script> |
||||
|
|
||||
<form> |
<form> |
||||
{#each Object.keys(integration) as configKey} |
{#each Object.keys(integration) as configKey} |
||||
<Input |
{#if typeof integration[configKey] === 'object'} |
||||
type={integration[configKey].type} |
<Label small>{configKey}</Label> |
||||
label={configKey} |
<Spacer small /> |
||||
bind:value={integration[configKey]} /> |
<KeyValueBuilder bind:object={integration[configKey]} on:change /> |
||||
<Spacer large /> |
{:else} |
||||
|
<div class="form-row"> |
||||
|
<Label small>{configKey}</Label> |
||||
|
<Input |
||||
|
outline |
||||
|
type={integration[configKey].type} |
||||
|
on:change |
||||
|
bind:value={integration[configKey]} /> |
||||
|
</div> |
||||
|
{/if} |
||||
{/each} |
{/each} |
||||
</form> |
</form> |
||||
|
|
||||
|
<style> |
||||
|
.form-row { |
||||
|
display: grid; |
||||
|
grid-template-columns: 20% 1fr; |
||||
|
grid-gap: var(--spacing-l); |
||||
|
align-items: center; |
||||
|
margin-bottom: var(--spacing-m); |
||||
|
} |
||||
|
</style> |
||||
|
|||||
@ -0,0 +1,36 @@ |
|||||
|
<script> |
||||
|
export let width = "100" |
||||
|
export let height = "100" |
||||
|
</script> |
||||
|
|
||||
|
<svg |
||||
|
{width} |
||||
|
{height} |
||||
|
viewBox="0 0 120 120" |
||||
|
fill="none" |
||||
|
xmlns="http://www.w3.org/2000/svg"> |
||||
|
<path |
||||
|
d="M103.125 20.625H68.25L60.375 |
||||
|
36.375H16.5V99.375H111V20.625H103.125ZM103.125 36.375H72.375L76.5 |
||||
|
28.5H103.125V36.375Z" |
||||
|
fill="#FFBA58" /> |
||||
|
<path |
||||
|
d="M75 46.875V52.5H60C58.0127 52.5059 56.1085 53.298 54.7033 54.7033C53.298 |
||||
|
56.1085 52.5059 58.0127 52.5 60V75H46.875C44.3886 75 42.004 75.9877 40.2459 |
||||
|
77.7459C38.4877 79.504 37.5 81.8886 37.5 84.375C37.5 86.8614 38.4877 89.246 |
||||
|
40.2459 91.0041C42.004 92.7623 44.3886 93.75 46.875 |
||||
|
93.75H52.5V108.75C52.5059 110.737 53.298 112.642 54.7033 114.047C56.1085 |
||||
|
115.452 58.0127 116.244 60 116.25H74.25V110.625C74.25 107.94 75.3167 105.364 |
||||
|
77.2155 103.466C79.1143 101.567 81.6897 100.5 84.375 100.5C87.0603 100.5 |
||||
|
89.6357 101.567 91.5345 103.466C93.4333 105.364 94.5 107.94 94.5 |
||||
|
110.625V116.25H108.75C110.737 116.244 112.642 115.452 114.047 |
||||
|
114.047C115.452 112.642 116.244 110.737 116.25 108.75V94.5H110.625C107.94 |
||||
|
94.5 105.364 93.4333 103.466 91.5345C101.567 89.6357 100.5 87.0603 100.5 |
||||
|
84.375C100.5 81.6897 101.567 79.1143 103.466 77.2155C105.364 75.3167 107.94 |
||||
|
74.25 110.625 74.25H116.25V60C116.244 58.0127 115.452 56.1085 114.047 |
||||
|
54.7033C112.642 53.298 110.737 52.5059 108.75 52.5H93.75V46.875C93.75 |
||||
|
44.3886 92.7623 42.004 91.0041 40.2459C89.246 38.4877 86.8614 37.5 84.375 |
||||
|
37.5C81.8886 37.5 79.504 38.4877 77.7459 40.2459C75.9877 42.004 75 44.3886 |
||||
|
75 46.875Z" |
||||
|
fill="#E76A00" /> |
||||
|
</svg> |
||||
@ -1,61 +0,0 @@ |
|||||
<script> |
|
||||
import { goto, params } from "@sveltech/routify" |
|
||||
import { backendUiStore, store } from "builderStore" |
|
||||
import { notifier } from "builderStore/store/notifications" |
|
||||
import { Input, Label, ModalContent, Button, Spacer } from "@budibase/bbui" |
|
||||
import TableIntegrationMenu from "../TableIntegrationMenu/index.svelte" |
|
||||
import analytics from "analytics" |
|
||||
|
|
||||
let modal |
|
||||
let error = "" |
|
||||
|
|
||||
let name |
|
||||
let source |
|
||||
let integration |
|
||||
let datasource |
|
||||
|
|
||||
function checkValid(evt) { |
|
||||
const datasourceName = evt.target.value |
|
||||
if ( |
|
||||
$backendUiStore.datasources?.some( |
|
||||
datasource => datasource.name === datasourceName |
|
||||
) |
|
||||
) { |
|
||||
error = `Datasource with name ${tableName} already exists. Please choose another name.` |
|
||||
return |
|
||||
} |
|
||||
error = "" |
|
||||
} |
|
||||
|
|
||||
async function saveDatasource() { |
|
||||
const { type, ...config } = integration |
|
||||
|
|
||||
// Create datasource |
|
||||
await backendUiStore.actions.datasources.save({ |
|
||||
name, |
|
||||
source: type, |
|
||||
config, |
|
||||
}) |
|
||||
notifier.success(`Datasource ${name} created successfully.`) |
|
||||
analytics.captureEvent("Datasource Created", { name }) |
|
||||
|
|
||||
// Navigate to new datasource |
|
||||
$goto(`./datasource/${datasource._id}`) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<ModalContent |
|
||||
title="Create Datasource" |
|
||||
confirmText="Create" |
|
||||
onConfirm={saveDatasource} |
|
||||
disabled={error || !name}> |
|
||||
<Input |
|
||||
data-cy="datasource-name-input" |
|
||||
thin |
|
||||
label="Datasource Name" |
|
||||
on:input={checkValid} |
|
||||
bind:value={name} |
|
||||
{error} /> |
|
||||
<Label grey extraSmall>Create Integrated Table from External Source</Label> |
|
||||
<TableIntegrationMenu bind:integration /> |
|
||||
</ModalContent> |
|
||||
@ -1,39 +0,0 @@ |
|||||
<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> |
|
||||
@ -0,0 +1,50 @@ |
|||||
|
<script> |
||||
|
import { Button, Input } from "@budibase/bbui" |
||||
|
|
||||
|
export let object = {} |
||||
|
export let readOnly |
||||
|
|
||||
|
let fields = Object.entries(object).map(([name, value]) => ({ name, value })) |
||||
|
|
||||
|
$: object = fields.reduce( |
||||
|
(acc, next) => ({ ...acc, [next.name]: next.value }), |
||||
|
{} |
||||
|
) |
||||
|
|
||||
|
function addEntry() { |
||||
|
fields = [...fields, {}] |
||||
|
} |
||||
|
|
||||
|
function deleteEntry(idx) { |
||||
|
fields.splice(idx, 1) |
||||
|
fields = fields |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<!-- Builds Objects with Key Value Pairs. Useful for building things like Request Headers. --> |
||||
|
<div class="container" class:readOnly> |
||||
|
{#each fields as field, idx} |
||||
|
<Input placeholder="Key" thin outline bind:value={field.name} /> |
||||
|
<Input placeholder="Value" thin outline bind:value={field.value} /> |
||||
|
{#if !readOnly} |
||||
|
<i class="ri-close-circle-fill" on:click={() => deleteEntry(idx)} /> |
||||
|
{/if} |
||||
|
{/each} |
||||
|
</div> |
||||
|
{#if !readOnly} |
||||
|
<Button secondary thin outline on:click={addEntry}>Add</Button> |
||||
|
{/if} |
||||
|
|
||||
|
<style> |
||||
|
.container { |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 20px; |
||||
|
grid-gap: var(--spacing-m); |
||||
|
align-items: center; |
||||
|
margin-bottom: var(--spacing-m); |
||||
|
} |
||||
|
|
||||
|
.ri-close-circle-fill { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
</style> |
||||
File diff suppressed because it is too large
@ -0,0 +1,175 @@ |
|||||
|
const fetch = require("node-fetch") |
||||
|
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
||||
|
|
||||
|
const SCHEMA = { |
||||
|
docs: "https://github.com/node-fetch/node-fetch", |
||||
|
description: |
||||
|
"Representational state transfer (REST) is a de-facto standard for a software architecture for interactive applications that typically use multiple Web services. ", |
||||
|
friendlyName: "REST API", |
||||
|
datasource: { |
||||
|
url: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
default: "localhost", |
||||
|
required: true, |
||||
|
}, |
||||
|
defaultHeaders: { |
||||
|
type: FIELD_TYPES.OBJECT, |
||||
|
required: false, |
||||
|
default: {}, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
displayName: "POST", |
||||
|
type: QUERY_TYPES.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: FIELD_TYPES.OBJECT, |
||||
|
}, |
||||
|
requestBody: { |
||||
|
type: FIELD_TYPES.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
read: { |
||||
|
displayName: "GET", |
||||
|
type: QUERY_TYPES.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: FIELD_TYPES.OBJECT, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
update: { |
||||
|
displayName: "PUT", |
||||
|
type: QUERY_TYPES.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: FIELD_TYPES.OBJECT, |
||||
|
}, |
||||
|
requestBody: { |
||||
|
type: FIELD_TYPES.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
delete: { |
||||
|
displayName: "DELETE", |
||||
|
type: QUERY_TYPES.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: FIELD_TYPES.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: FIELD_TYPES.OBJECT, |
||||
|
}, |
||||
|
requestBody: { |
||||
|
type: FIELD_TYPES.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class RestIntegration { |
||||
|
constructor(config) { |
||||
|
this.config = config |
||||
|
} |
||||
|
|
||||
|
async parseResponse(response) { |
||||
|
switch (this.headers.Accept) { |
||||
|
case "application/json": |
||||
|
return await response.json() |
||||
|
case "text/html": |
||||
|
return await response.text() |
||||
|
default: |
||||
|
return await response.json() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async create({ path, queryString, headers = {}, json }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
method: "POST", |
||||
|
headers: this.headers, |
||||
|
body: JSON.stringify(json), |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
|
||||
|
async read({ path, queryString, headers = {} }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
headers: this.headers, |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
|
||||
|
async update({ path, queryString, headers = {}, json }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
method: "POST", |
||||
|
headers: this.headers, |
||||
|
body: JSON.stringify(json), |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
|
||||
|
async delete({ path, queryString, headers = {} }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
method: "DELETE", |
||||
|
headers: this.headers, |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: RestIntegration, |
||||
|
} |
||||
Loading…
Reference in new issue