mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
91 changed files with 4954 additions and 164 deletions
@ -0,0 +1,28 @@ |
|||
<script> |
|||
import { params } from "@sveltech/routify" |
|||
import { backendUiStore } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import * as api from "./api" |
|||
import Table from "./Table.svelte" |
|||
|
|||
export let query = {} |
|||
export let data = [] |
|||
|
|||
let loading = false |
|||
let error = false |
|||
</script> |
|||
|
|||
{#if error} |
|||
<div class="errors">{error}</div> |
|||
{/if} |
|||
<Table title={''} schema={query.schema} {data} {loading} /> |
|||
|
|||
<style> |
|||
.errors { |
|||
color: var(--red); |
|||
background: var(--red-light); |
|||
padding: var(--spacing-m); |
|||
border-radius: var(--border-radius-m); |
|||
margin-bottom: var(--spacing-m); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,63 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { goto } from "@sveltech/routify" |
|||
import { backendUiStore } from "builderStore" |
|||
import { TableNames } from "constants" |
|||
import CreateDatasourceModal from "./modals/CreateDatasourceModal.svelte" |
|||
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte" |
|||
import EditQueryPopover from "./popovers/EditQueryPopover.svelte" |
|||
import { Modal, Switcher } from "@budibase/bbui" |
|||
import NavItem from "components/common/NavItem.svelte" |
|||
import ICONS from "./icons" |
|||
|
|||
$: selectedView = |
|||
$backendUiStore.selectedView && $backendUiStore.selectedView.name |
|||
|
|||
function selectDatasource(datasource) { |
|||
backendUiStore.actions.datasources.select(datasource._id) |
|||
$goto(`./datasource/${datasource._id}`) |
|||
} |
|||
|
|||
function onClickQuery(query) { |
|||
if ($backendUiStore.selectedQueryId === query._id) { |
|||
return |
|||
} |
|||
backendUiStore.actions.queries.select(query) |
|||
$goto(`./datasource/${query.datasourceId}/${query._id}`) |
|||
} |
|||
|
|||
onMount(() => { |
|||
backendUiStore.actions.datasources.fetch() |
|||
backendUiStore.actions.queries.fetch() |
|||
}) |
|||
</script> |
|||
|
|||
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id} |
|||
<div class="hierarchy-items-container"> |
|||
{#each $backendUiStore.datasources as datasource, idx} |
|||
<NavItem |
|||
border={idx > 0} |
|||
text={datasource.name} |
|||
selected={$backendUiStore.selectedDatasourceId === datasource._id} |
|||
on:click={() => selectDatasource(datasource)}> |
|||
<div class="datasource-icon" slot="icon"> |
|||
<svelte:component |
|||
this={ICONS[datasource.source]} |
|||
height="15" |
|||
width="15" /> |
|||
</div> |
|||
<EditDatasourcePopover {datasource} /> |
|||
</NavItem> |
|||
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query} |
|||
<NavItem |
|||
indentLevel={1} |
|||
icon="ri-eye-line" |
|||
text={query.name} |
|||
selected={$backendUiStore.selectedQueryId === query._id} |
|||
on:click={() => onClickQuery(query)}> |
|||
<EditQueryPopover {query} /> |
|||
</NavItem> |
|||
{/each} |
|||
{/each} |
|||
</div> |
|||
{/if} |
|||
@ -0,0 +1,55 @@ |
|||
<script> |
|||
export let icon |
|||
export let className |
|||
export let title |
|||
export let selected |
|||
export let indented |
|||
</script> |
|||
|
|||
<div |
|||
data-cy="table-nav-item" |
|||
class:indented |
|||
class:selected |
|||
on:click |
|||
class={className}> |
|||
<i class={icon} /> |
|||
<span>{title}</span> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
.indented { |
|||
grid-template-columns: 46px 1fr 20px; |
|||
} |
|||
.indented i { |
|||
justify-self: end; |
|||
} |
|||
|
|||
div { |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
border-radius: var(--border-radius-m); |
|||
display: grid; |
|||
grid-template-columns: 20px 1fr 20px; |
|||
align-items: center; |
|||
transition: 0.3s background-color; |
|||
color: var(--ink); |
|||
font-weight: 400; |
|||
font-size: 14px; |
|||
margin-bottom: var(--spacing-xs); |
|||
grid-gap: var(--spacing-s); |
|||
} |
|||
|
|||
.selected { |
|||
background-color: var(--grey-2); |
|||
} |
|||
|
|||
div:hover { |
|||
background-color: var(--grey-1); |
|||
cursor: pointer; |
|||
} |
|||
|
|||
i { |
|||
color: var(--grey-7); |
|||
font-size: 20px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,16 @@ |
|||
<script> |
|||
import { Input, TextArea, Spacer } from "@budibase/bbui" |
|||
|
|||
export let integration |
|||
</script> |
|||
|
|||
<form> |
|||
{#each Object.keys(integration) as configKey} |
|||
<Input |
|||
thin |
|||
type={integration[configKey].type} |
|||
label={configKey} |
|||
bind:value={integration[configKey]} /> |
|||
<Spacer medium /> |
|||
{/each} |
|||
</form> |
|||
@ -0,0 +1,97 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { backendUiStore } from "builderStore" |
|||
import api from "builderStore/api" |
|||
import { Input, TextArea, Spacer } from "@budibase/bbui" |
|||
import ICONS from "../icons" |
|||
|
|||
export let integration = {} |
|||
|
|||
let schema |
|||
let integrations = [] |
|||
|
|||
async function fetchIntegrations() { |
|||
const response = await api.get("/api/integrations") |
|||
const json = await response.json() |
|||
|
|||
integrations = json |
|||
return json |
|||
} |
|||
|
|||
function selectIntegration(integrationType) { |
|||
schema = integrations[integrationType].datasource |
|||
integration = { |
|||
type: integrationType, |
|||
...Object.keys(schema).reduce( |
|||
(acc, next) => ({ ...acc, [next]: schema[next].default }), |
|||
{} |
|||
), |
|||
} |
|||
} |
|||
|
|||
onMount(() => { |
|||
fetchIntegrations() |
|||
}) |
|||
</script> |
|||
|
|||
<section> |
|||
<div class="integration-list"> |
|||
{#each Object.keys(integrations) as integrationType} |
|||
<div |
|||
class="integration hoverable" |
|||
class:selected={integration.type === integrationType} |
|||
on:click={() => selectIntegration(integrationType)}> |
|||
<svelte:component |
|||
this={ICONS[integrationType]} |
|||
height="100" |
|||
width="100" /> |
|||
<span>{integrationType}</span> |
|||
</div> |
|||
{/each} |
|||
</div> |
|||
|
|||
{#if schema} |
|||
{#each Object.keys(schema) as configKey} |
|||
<Input |
|||
thin |
|||
type={schema[configKey].type} |
|||
label={configKey} |
|||
bind:value={integration[configKey]} /> |
|||
<Spacer medium /> |
|||
{/each} |
|||
{/if} |
|||
</section> |
|||
|
|||
<style> |
|||
section { |
|||
display: grid; |
|||
} |
|||
|
|||
.integration-list { |
|||
display: grid; |
|||
grid-template-columns: repeat(3, 1fr); |
|||
grid-gap: var(--spacing-m); |
|||
} |
|||
|
|||
.integration { |
|||
display: flex; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
padding: 5px; |
|||
transition: 0.3s all; |
|||
border-radius: var(--border-radius-s); |
|||
height: 75px; |
|||
width: 200px; |
|||
} |
|||
|
|||
span { |
|||
font-size: var(--font-size-xs); |
|||
margin-top: var(--spacing-m); |
|||
margin-bottom: var(--spacing-xs); |
|||
} |
|||
|
|||
.integration:hover, |
|||
.selected { |
|||
background-color: var(--grey-3); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,46 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
viewBox="0 0 256 215" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<g clip-path="url(#clip0)"> |
|||
<path |
|||
d="M114.259 2.701L18.86 42.176C13.555 44.371 13.61 51.906 18.949 |
|||
54.023L114.746 92.012C123.163 95.3503 132.537 95.3503 140.954 |
|||
92.012L236.753 54.022C242.09 51.907 242.146 44.372 236.839 42.176L141.442 |
|||
2.7C132.739 -0.901099 122.962 -0.901099 114.259 2.7" |
|||
fill="#FFBF00" /> |
|||
<path |
|||
d="M136.35 112.757V207.659C136.35 212.173 140.9 215.264 145.096 |
|||
213.601L251.844 172.166C253.034 171.694 254.056 170.875 254.775 |
|||
169.816C255.495 168.757 255.879 167.506 255.879 166.225V71.322C255.879 |
|||
66.808 251.328 63.718 247.132 65.381L140.384 106.815C139.194 107.287 |
|||
138.172 108.106 137.453 109.166C136.734 110.225 136.349 111.476 136.349 |
|||
112.757" |
|||
fill="#26B5F8" /> |
|||
<path |
|||
d="M111.423 117.654L79.743 132.95L76.526 134.505L9.65 166.548C5.411 |
|||
168.593 0 165.504 0 160.795V71.72C0 70.016 0.874 68.545 2.046 |
|||
67.437C2.52616 66.9592 3.07076 66.5509 3.664 66.224C5.262 65.265 7.542 |
|||
65.009 9.48 65.776L110.89 105.956C116.045 108.001 116.45 115.224 111.423 |
|||
117.653" |
|||
fill="#ED3049" /> |
|||
<path |
|||
d="M111.423 117.654L79.743 132.95L2.045 67.438C2.52516 66.9602 3.06976 |
|||
66.5519 3.663 66.225C5.261 65.266 7.541 65.01 9.479 65.777L110.889 |
|||
105.957C116.044 108.002 116.449 115.225 111.422 117.654" |
|||
fill="black" |
|||
fill-opacity="0.25" /> |
|||
</g> |
|||
<defs> |
|||
<clipPath id="clip0"> |
|||
<rect width="256" height="215" fill="white" /> |
|||
</clipPath> |
|||
</defs> |
|||
</svg> |
|||
@ -0,0 +1,35 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
viewBox="0 0 175 115" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<path |
|||
d="M142.187 76.6667C142.187 83.9288 138.367 87.4876 131.249 |
|||
87.6136V87.6184H43.7507V87.6136C36.6331 87.4876 32.8132 83.9288 32.8132 |
|||
76.666C32.8132 69.4052 36.6331 65.845 43.7507 |
|||
65.7204V65.7136H131.249V65.7204C138.367 65.845 142.187 69.4052 142.187 |
|||
76.666V76.6667ZM131.249 93.1014V93.0952H43.7507V93.1021C36.6331 93.2267 |
|||
32.8132 96.7869 32.8132 104.05C32.8132 111.312 36.6331 114.869 43.7507 |
|||
114.995V115H131.249V114.993C138.367 114.869 142.187 111.31 142.187 |
|||
104.048C142.187 96.7848 138.367 93.2267 131.249 93.1021V93.1014ZM158.593 |
|||
32.8695V32.8633C151.477 32.9893 147.656 36.5481 147.656 |
|||
43.8109V104.048C147.656 111.31 151.477 114.867 158.593 |
|||
114.993V114.982C169.269 114.606 175 103.929 175 82.1429V54.7619C175 40.2384 |
|||
169.269 33.1214 158.594 32.8688L158.593 32.8695ZM16.4062 |
|||
32.8626V32.8695C5.73193 33.1207 0 40.2377 0 54.7619V82.1429C0 103.929 |
|||
5.73125 114.605 16.4062 114.982V114.993C23.5238 114.869 27.3438 111.31 |
|||
27.3438 104.048V43.8095C27.3438 36.5474 23.5238 32.9886 16.4062 |
|||
32.8626ZM158.594 27.381C158.594 9.22533 149.042 0.328571 131.25 |
|||
0.014375V0H43.75V0.014375C25.9595 0.329256 16.4062 9.22533 16.4062 |
|||
27.3816V27.3905C27.0819 27.5788 32.8132 32.9167 32.8132 43.8102C32.8132 |
|||
54.703 38.5444 60.0403 49.2194 60.2292V60.2381H125.782V60.2299C136.456 |
|||
60.0416 142.187 54.7037 142.187 43.8109C142.187 32.9174 147.918 27.5795 |
|||
158.593 27.3912V27.381H158.594Z" |
|||
fill="#E42528" /> |
|||
</svg> |
|||
@ -0,0 +1,58 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
viewBox="0 0 256 289" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<g clip-path="url(#clip0)"> |
|||
<path |
|||
d="M165.258 288.501H168.766L226.027 259.867L226.98 258.52V29.964L226.027 |
|||
28.61L168.766 0H165.215L165.258 288.501Z" |
|||
fill="#5294CF" /> |
|||
<path |
|||
d="M90.741 288.501H87.184L29.972 259.867L28.811 257.87L28.222 |
|||
31.128L29.972 28.61L87.184 0H90.785L90.741 288.501Z" |
|||
fill="#1F5B98" /> |
|||
<path d="M87.285 0H168.711V288.501H87.285V0Z" fill="#2D72B8" /> |
|||
<path |
|||
d="M256 137.769L254.065 137.34L226.437 134.764L226.027 134.968L168.715 |
|||
132.676H87.285L29.972 134.968V91.264L29.912 91.296L29.972 91.168L87.285 |
|||
77.888H168.715L226.027 91.168L247.096 102.367V95.167L256 94.193L255.078 |
|||
92.395L226.886 72.236L226.027 72.515L168.715 54.756H87.285L29.972 |
|||
72.515V28.61L0 63.723V94.389L0.232 94.221L8.904 95.167V102.515L0 |
|||
107.28V137.793L0.232 137.769L8.904 137.897V150.704L1.422 150.816L0 |
|||
150.68V181.205L8.904 185.993V193.426L0.373 194.368L0 |
|||
194.088V224.749L29.972 259.867V215.966L87.285 233.725H168.715L226.196 |
|||
215.914L226.96 216.249L254.781 196.387L256 194.408L247.096 |
|||
193.426V186.142L245.929 185.676L226.886 195.941L226.196 197.381L168.715 |
|||
210.584V210.6H87.285V210.584L29.972 197.325V153.461L87.285 |
|||
155.745V155.801H168.715L226.027 153.461L227.332 154.061L254.111 |
|||
151.755L256 150.832L247.096 150.704V137.897L256 137.769" |
|||
fill="#1A476F" /> |
|||
<path |
|||
d="M226.027 215.966V259.867L256 224.749V194.288L226.2 215.914L226.027 |
|||
215.966Z" |
|||
fill="#2D72B8" /> |
|||
<path |
|||
d="M226.027 197.421L226.2 197.381L256 181.353V150.704L226.027 |
|||
153.461V197.421" |
|||
fill="#2D72B8" /> |
|||
<path |
|||
d="M226.2 91.208L226.027 91.168V134.968L256 137.769V107.135L226.2 91.208Z" |
|||
fill="#2D72B8" /> |
|||
<path |
|||
d="M226.2 72.687L256 94.193V63.731L226.027 28.61V72.515L226.2 |
|||
72.575V72.687Z" |
|||
fill="#2D72B8" /> |
|||
</g> |
|||
<defs> |
|||
<clipPath id="clip0"> |
|||
<rect width="256" height="289" fill="white" /> |
|||
</clipPath> |
|||
</defs> |
|||
</svg> |
|||
@ -0,0 +1,47 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
viewBox="0 0 128 143" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<g clip-path="url(#clip0)"> |
|||
<path |
|||
d="M7.172 40.0865H101.775C112.201 40.0865 121.51 35.3065 127.757 |
|||
27.843C114.727 10.918 94.2765 0 71.265 0C43.089 0 18.738 16.3555 7.172 |
|||
40.0865Z" |
|||
fill="#F0BF1A" /> |
|||
<path |
|||
d="M93.7575 51.222H2.8775C0.969102 57.732 0.000154178 64.4811 0 71.265C0 |
|||
78.227 1.0165 84.9485 2.8775 91.3085H93.7575C104.67 91.3085 113.801 |
|||
82.4005 113.801 71.265C113.801 60.13 104.893 51.222 93.7575 51.222" |
|||
fill="#07A5DE" /> |
|||
<path |
|||
d="M128 114.378C121.793 107.082 112.559 102.443 102.22 |
|||
102.443H7.173C18.7385 126.175 43.0895 142.53 71.265 142.53C94.418 142.53 |
|||
114.983 131.482 128 114.378Z" |
|||
fill="#3EBEB0" /> |
|||
<path |
|||
d="M2.87749 51.222C-0.959651 64.3087 -0.959651 78.2218 2.87749 |
|||
91.3085H62.357C63.916 85.2955 64.8065 78.6145 64.8065 71.265C64.8065 |
|||
63.916 63.916 57.235 62.3565 51.222H2.87849H2.87749Z" |
|||
fill="#231F20" /> |
|||
<path |
|||
d="M35.41 9.5765C23.3835 16.703 13.362 27.3925 7.1265 |
|||
40.0865H59.6845C54.3395 27.838 45.877 17.594 35.4095 9.5765" |
|||
fill="#D7A229" /> |
|||
<path |
|||
d="M37.637 134.068C47.881 125.828 56.121 114.915 61.2435 |
|||
102.443H7.1265C13.8075 115.806 24.4975 126.941 37.637 134.068Z" |
|||
fill="#019B8F" /> |
|||
</g> |
|||
<defs> |
|||
<clipPath id="clip0"> |
|||
<rect width="128" height="143" fill="white" /> |
|||
</clipPath> |
|||
</defs> |
|||
</svg> |
|||
@ -0,0 +1,77 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
viewBox="0 0 179 179" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<path |
|||
d="M153.828 30.7656H101.806L90.0594 |
|||
54.2594H24.6125V148.234H165.575V30.7656H153.828ZM153.828 |
|||
54.2594H107.959L114.112 42.5125H153.828V54.2594Z" |
|||
fill="#458248" /> |
|||
<path |
|||
d="M149.773 173.406L146.623 172.36C146.623 172.36 147.026 156.412 141.276 |
|||
155.299C137.483 150.903 141.835 -32.4046 155.635 154.673C153.015 156 151.011 |
|||
158.289 150.041 161.061C149.521 165.155 149.431 169.293 149.773 |
|||
173.406V173.406Z" |
|||
fill="url(#paint0_linear)" /> |
|||
<path |
|||
d="M151.473 158.007C159.86 151.69 166.283 143.124 169.994 133.302C173.705 |
|||
123.48 174.553 112.807 172.439 102.522C166.285 75.3142 151.68 66.3698 |
|||
150.108 62.9577C148.75 60.8316 147.591 58.5845 146.646 56.2452L147.809 |
|||
132.052C147.809 132.052 145.398 155.215 151.473 158.007Z" |
|||
fill="url(#paint1_linear)" /> |
|||
<path |
|||
d="M145.018 159.014C145.018 159.014 119.337 141.522 120.825 110.583C120.964 |
|||
101.255 123.102 92.0662 127.096 83.636C131.091 75.2058 136.847 67.7308 |
|||
143.978 61.7158C144.809 61.0059 145.469 60.1177 145.91 59.1173C146.351 |
|||
58.1169 146.56 57.03 146.523 55.9375C148.123 59.3777 147.86 107.299 148.027 |
|||
112.904C148.682 134.709 146.819 154.897 145.018 159.014V159.014Z" |
|||
fill="url(#paint2_linear)" /> |
|||
<defs> |
|||
<linearGradient |
|||
id="paint0_linear" |
|||
x1="128.589" |
|||
y1="115.729" |
|||
x2="164.805" |
|||
y2="128.016" |
|||
gradientUnits="userSpaceOnUse"> |
|||
<stop offset="0.231" stop-color="#999875" /> |
|||
<stop offset="0.563" stop-color="#9B9977" /> |
|||
<stop offset="0.683" stop-color="#A09F7E" /> |
|||
<stop offset="0.768" stop-color="#A9A889" /> |
|||
<stop offset="0.837" stop-color="#B7B69A" /> |
|||
<stop offset="0.896" stop-color="#C9C7B0" /> |
|||
<stop offset="0.948" stop-color="#DEDDCB" /> |
|||
<stop offset="0.994" stop-color="#F8F6EB" /> |
|||
<stop offset="1" stop-color="#FBF9EF" /> |
|||
</linearGradient> |
|||
<linearGradient |
|||
id="paint1_linear" |
|||
x1="141.872" |
|||
y1="55.7038" |
|||
x2="157.883" |
|||
y2="155.673" |
|||
gradientUnits="userSpaceOnUse"> |
|||
<stop stop-color="#48A547" /> |
|||
<stop offset="1" stop-color="#3F9143" /> |
|||
</linearGradient> |
|||
<linearGradient |
|||
id="paint2_linear" |
|||
x1="118.614" |
|||
y1="113.645" |
|||
x2="161.577" |
|||
y2="99.2572" |
|||
gradientUnits="userSpaceOnUse"> |
|||
<stop stop-color="#41A247" /> |
|||
<stop offset="0.352" stop-color="#4BA74B" /> |
|||
<stop offset="0.956" stop-color="#67B554" /> |
|||
<stop offset="1" stop-color="#69B655" /> |
|||
</linearGradient> |
|||
</defs> |
|||
</svg> |
|||
@ -0,0 +1,149 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
viewBox="0 0 170 175" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<path |
|||
d="M169.341 104.792C168.322 101.71 165.652 99.5637 162.199 99.0487C160.57 |
|||
98.8061 158.706 98.9095 156.499 99.3635C152.653 100.156 149.799 100.457 |
|||
147.717 100.516C155.577 87.268 161.969 72.161 165.648 57.9402C171.598 |
|||
34.9456 168.419 24.4702 164.704 19.7319C154.871 7.19025 140.525 0.452754 |
|||
123.218 0.246599C113.986 0.13391 105.881 1.95351 101.653 3.26203C97.7162 |
|||
2.56866 93.4835 2.18154 89.0422 2.10995C80.7169 1.97738 73.361 3.78902 |
|||
67.0757 7.51241C63.596 6.33779 58.0112 4.68192 51.5619 3.62529C36.3933 |
|||
1.1395 24.1679 3.07643 15.2257 9.38239C4.39748 17.0174 -0.622171 30.283 |
|||
0.306188 48.8104C0.601032 54.6928 3.89744 72.5905 9.08775 89.5642C12.0707 |
|||
99.3205 15.2516 107.422 18.542 113.647C23.2084 122.473 28.2015 127.67 |
|||
33.8068 129.537C36.9485 130.582 42.6568 131.314 48.6606 126.322C49.4216 |
|||
127.242 50.4369 128.156 51.7843 129.005C53.4956 130.083 55.5881 130.963 |
|||
57.6779 131.485C65.209 133.364 72.2633 132.894 78.2817 130.26C78.3189 |
|||
131.328 78.3475 132.349 78.3714 133.231C78.4112 134.661 78.4511 136.063 |
|||
78.5035 137.374C78.8601 146.239 79.4644 153.132 81.2548 157.955C81.353 |
|||
158.221 81.4852 158.625 81.6246 159.054C82.5178 161.784 84.0113 166.353 |
|||
87.811 169.932C91.7456 173.64 96.5049 174.777 100.864 174.777C103.05 174.777 |
|||
105.136 174.49 106.965 174.099C113.486 172.704 120.893 170.579 126.25 |
|||
162.963C131.316 155.765 133.778 144.922 134.224 127.838C134.281 127.354 |
|||
134.335 126.893 134.386 126.453L134.492 125.551L135.685 125.655L135.993 |
|||
125.676C142.635 125.978 150.756 124.572 155.744 122.259C159.685 120.434 |
|||
172.315 113.778 169.341 104.792" |
|||
fill="black" /> |
|||
<path |
|||
d="M157.984 106.539C138.235 110.606 136.877 103.931 136.877 103.931C157.729 |
|||
73.046 166.446 33.8413 158.924 24.2462C138.402 -1.92761 102.878 10.451 |
|||
102.285 10.7718L102.094 10.8063C98.1923 9.99759 93.8261 9.51633 88.918 |
|||
9.43613C79.9824 9.29029 73.2036 11.7748 68.0598 15.6685C68.0598 15.6685 |
|||
4.68831 -10.3919 7.63609 48.4439C8.26296 60.9604 25.6083 143.151 46.2965 |
|||
118.326C53.8582 109.248 61.1648 101.572 61.1648 101.572C64.7933 103.979 |
|||
69.1376 105.206 73.6917 104.765L74.0457 104.466C73.9354 105.593 73.9859 |
|||
106.695 74.1871 108C68.8573 113.944 70.4239 114.988 59.7696 117.177C48.9886 |
|||
119.395 55.3218 123.343 59.4569 124.376C64.4699 125.627 76.0671 127.399 |
|||
83.903 116.449L83.5909 117.699C85.6787 119.369 87.1449 128.559 86.8992 |
|||
136.89C86.6535 145.222 86.4895 150.942 88.1344 155.409C89.7806 159.877 |
|||
91.4202 169.93 105.427 166.934C117.129 164.43 123.194 157.943 124.038 |
|||
147.121C124.637 139.427 125.992 140.565 126.077 133.686L127.164 |
|||
130.43C128.417 120.001 127.363 116.636 134.573 118.201L136.326 |
|||
118.355C141.632 118.596 148.578 117.503 152.655 115.611C161.432 111.545 |
|||
166.639 104.755 157.983 106.539H157.984" |
|||
fill="#336791" /> |
|||
<path |
|||
d="M71.7692 54.0412C69.9895 53.7939 68.3778 54.0226 67.5623 54.6391C67.1041 |
|||
54.9858 66.962 55.3875 66.9235 55.6639C66.8212 56.3964 67.3352 57.2064 |
|||
67.6513 57.624C68.5452 58.8066 69.8507 59.6193 71.143 59.7983C71.3302 |
|||
59.8248 71.5168 59.8367 71.7021 59.8367C73.857 59.8367 75.8167 58.1616 |
|||
75.9893 56.9254C76.2051 55.3769 73.9533 54.3448 71.7692 54.0418" |
|||
fill="white" /> |
|||
<path |
|||
d="M130.727 54.0902C130.557 52.8765 128.394 52.5305 126.341 52.8155C124.29 |
|||
53.1006 122.302 54.0246 122.468 55.241C122.601 56.1869 124.312 57.8017 |
|||
126.337 57.8017C126.509 57.8017 126.681 57.7904 126.855 57.7659C128.207 |
|||
57.579 129.199 56.7219 129.671 56.228C130.388 55.475 130.804 54.6358 130.727 |
|||
54.0902" |
|||
fill="white" /> |
|||
<path |
|||
d="M164.556 106.077C163.803 103.804 161.379 103.073 157.352 103.903C145.396 |
|||
106.366 141.114 104.66 139.708 103.627C149.002 89.494 156.647 72.4103 |
|||
160.771 56.4713C162.725 48.9212 163.804 41.9092 163.893 36.1939C163.99 |
|||
29.9211 162.92 25.3114 160.712 22.4955C151.813 11.1437 138.751 5.05449 |
|||
122.939 4.88744C112.069 4.76547 102.885 7.54227 101.105 8.32314C97.3555 |
|||
7.39246 93.2682 6.82106 88.8183 6.74814C80.6583 6.61623 73.6046 8.56642 |
|||
67.7649 12.5417C65.2282 11.5991 58.6725 9.35259 50.6553 8.06329C36.795 |
|||
5.83602 25.7809 7.52371 17.921 13.0819C8.54246 19.7147 4.21277 31.571 |
|||
5.05148 48.3206C5.33371 53.9557 8.55043 71.2913 13.6265 87.8931C20.3083 |
|||
109.744 27.5718 122.114 35.2139 124.659C36.1084 124.957 37.1396 125.165 |
|||
38.2772 125.165C41.0649 125.165 44.4829 123.911 48.0389 119.643C52.3763 |
|||
114.449 56.863 109.381 61.4935 104.446C64.4977 106.055 67.7981 106.954 |
|||
71.1735 107.044C71.1802 107.133 71.1888 107.221 71.1968 107.308C70.6157 108 |
|||
70.0468 108.701 69.4901 109.413C67.1513 112.377 66.6645 112.994 59.1361 |
|||
114.541C56.9945 114.982 51.3068 116.152 51.2231 120.132C51.1328 124.481 |
|||
57.9467 126.307 58.723 126.501C61.4284 127.177 64.0348 127.51 66.5204 |
|||
127.51C72.5654 127.51 77.8852 125.527 82.1365 121.689C82.0057 137.191 |
|||
82.6532 152.467 84.5179 157.121C86.0452 160.931 89.7759 170.243 101.56 |
|||
170.242C103.29 170.242 105.193 170.041 107.287 169.593C119.586 166.961 |
|||
124.927 161.535 126.993 149.572C128.098 143.179 129.996 127.912 130.888 |
|||
119.723C132.771 120.309 135.196 120.578 137.817 120.577C143.283 120.577 |
|||
149.591 119.418 153.547 117.585C157.991 115.525 166.01 110.469 164.556 |
|||
106.077V106.077ZM135.267 50.7401C135.226 53.1576 134.893 55.3524 134.54 |
|||
57.6433C134.159 60.1072 133.766 62.6547 133.667 65.747C133.569 68.7565 |
|||
133.946 71.8853 134.31 74.9113C135.046 81.023 135.801 87.3151 132.878 |
|||
93.5236C132.393 92.665 131.96 91.7775 131.583 90.8661C131.22 89.9872 130.431 |
|||
88.5752 129.339 86.6211C125.089 79.0139 115.138 61.1997 120.232 |
|||
53.9305C121.75 51.7669 125.601 49.5423 135.267 50.7401V50.7401ZM123.55 |
|||
9.7828C137.717 10.095 148.924 15.3855 156.858 25.5063C162.944 33.2693 |
|||
156.243 68.5921 136.843 99.0653C136.648 98.8177 136.452 98.5708 136.255 |
|||
98.3248L136.009 98.0186C141.022 89.7545 140.042 81.5779 139.17 |
|||
74.3286C138.811 71.3536 138.472 68.5437 138.559 65.9041C138.648 63.1068 |
|||
139.018 60.7071 139.377 58.3871C139.817 55.5281 140.265 52.5696 140.142 |
|||
49.0822C140.234 48.7163 140.271 48.2841 140.223 47.7711C139.907 44.4268 |
|||
136.082 34.418 128.286 25.3591C124.022 20.4041 117.803 14.8591 109.311 |
|||
11.1192C112.964 10.3635 117.958 9.65884 123.55 9.7828V9.7828ZM44.2757 |
|||
116.52C40.3577 121.222 37.6523 120.32 36.7625 120.025C30.9652 118.095 |
|||
24.2382 105.863 18.3075 86.4673C13.1756 69.6845 10.1767 52.8083 9.93898 |
|||
48.076C9.18992 33.1095 12.8243 22.6791 20.742 17.0738C33.6274 7.95259 |
|||
54.8123 13.4121 63.325 16.1809C63.2028 16.3016 63.0753 16.4142 62.9544 |
|||
16.5369C48.9852 30.6191 49.3166 54.6789 49.3511 56.1498C49.3498 56.7173 |
|||
49.3976 57.5207 49.4627 58.6257C49.703 62.6726 50.1506 70.2042 48.9553 |
|||
78.7335C47.845 86.6595 50.2927 94.4172 55.6696 100.018C56.2215 100.592 |
|||
56.8015 101.138 57.4075 101.654C55.0142 104.213 49.8126 109.871 44.2757 |
|||
116.52V116.52ZM59.2031 96.6378C54.8695 92.1236 52.9012 85.8448 53.8023 |
|||
79.4096C55.064 70.3998 54.5985 62.5526 54.3482 58.3367C54.313 57.7467 |
|||
54.2818 57.2297 54.2638 56.822C56.3045 55.0157 65.7614 49.9579 72.5056 |
|||
51.5004C75.5829 52.2037 77.4582 54.2958 78.2378 57.8939C82.2727 76.5228 |
|||
78.7717 84.2871 75.9587 90.5267C75.379 91.8121 74.8312 93.0271 74.3637 |
|||
94.2839L74.0011 95.2557C73.0834 97.7123 72.2294 99.9966 71.7001 |
|||
102.166C67.0929 102.152 62.6111 100.188 59.2031 96.6372V96.6378ZM59.9104 |
|||
121.761C58.565 121.426 57.355 120.843 56.6452 120.36C57.2382 120.081 58.2934 |
|||
119.702 60.1235 119.326C68.9801 117.506 70.3481 116.221 73.335 |
|||
112.436C74.0197 111.567 74.796 110.583 75.8711 109.385L75.8724 |
|||
109.384C77.4735 107.594 78.2059 107.897 79.5341 108.448C80.6105 108.892 |
|||
81.6591 110.239 82.0841 111.721C82.2853 112.421 82.5111 113.749 81.772 |
|||
114.783C75.5324 123.504 66.4401 123.392 59.9104 121.761V121.761ZM106.261 |
|||
164.816C95.4264 167.134 91.5901 161.615 89.062 155.307C87.4304 151.234 |
|||
86.6282 132.868 87.1973 112.586C87.2047 112.316 87.1661 112.056 87.0918 |
|||
111.81C87.0252 111.326 86.924 110.847 86.7889 110.377C85.9429 107.426 83.881 |
|||
104.958 81.4074 103.934C80.4246 103.528 78.621 102.782 76.4535 |
|||
103.336C76.9157 101.434 77.7172 99.2873 78.5864 96.9626L78.951 |
|||
95.9849C79.3614 94.8825 79.8767 93.7404 80.4213 92.5313C83.365 86.0033 |
|||
87.3966 77.0617 83.0211 56.8618C81.3821 49.2957 75.9089 45.6008 67.6121 |
|||
46.4592C62.6383 46.973 58.0875 48.9762 55.8177 50.1249C55.3296 50.3715 |
|||
54.8834 50.6102 54.4664 50.8422C55.0999 43.2191 57.4932 28.9725 66.4461 |
|||
19.9587C72.0833 14.2844 79.5905 11.4818 88.7373 11.6329C106.76 11.9272 |
|||
118.317 21.1598 124.84 28.8538C130.46 35.4833 133.504 42.1618 134.718 |
|||
45.7639C125.584 44.8365 119.372 46.6362 116.223 51.1305C109.372 60.9067 |
|||
119.971 79.8809 125.065 89.0001C125.999 90.6712 126.805 92.1157 127.059 |
|||
92.7295C128.718 96.7426 130.865 99.4219 132.433 101.377C132.914 101.977 |
|||
133.38 102.558 133.735 103.066C130.968 103.862 125.999 105.701 126.452 |
|||
114.896C126.087 119.51 123.49 141.11 122.17 148.742C120.428 158.824 116.712 |
|||
162.58 106.261 164.817V164.816ZM151.487 113.154C148.658 114.465 143.924 |
|||
115.448 139.427 115.659C134.459 115.891 131.931 115.104 131.336 |
|||
114.619C131.057 108.889 133.193 108.291 135.454 107.657C135.809 107.557 |
|||
136.156 107.46 136.491 107.343C136.699 107.512 136.926 107.68 137.176 |
|||
107.844C141.168 110.475 148.289 110.758 158.342 108.687L158.453 |
|||
108.665C157.097 109.931 154.776 111.629 151.487 113.154Z" |
|||
fill="white" /> |
|||
</svg> |
|||
@ -0,0 +1,55 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
viewBox="0 0 128 155" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<path |
|||
d="M10.312 26.843L0 32V122.51L10.312 127.637L10.374 127.562V26.914L10.312 |
|||
26.843Z" |
|||
fill="#8C3123" /> |
|||
<path |
|||
d="M65.5 114.5L10.312 127.637V26.843L65.5 39.6935V114.5Z" |
|||
fill="#E05243" /> |
|||
<path |
|||
d="M40.589 93.933L63.998 96.913L64.145 96.574L64.2765 58.189L63.998 |
|||
57.889L40.589 60.826V93.933" |
|||
fill="#8C3123" /> |
|||
<path |
|||
d="M63.998 114.647L117.683 127.665L117.768 127.53L117.766 26.933L117.681 |
|||
26.843L63.998 39.841V114.647" |
|||
fill="#8C3123" /> |
|||
<path |
|||
d="M87.4135 93.933L63.998 96.913V57.889L87.4135 60.826V93.933Z" |
|||
fill="#E05243" /> |
|||
<path |
|||
d="M87.4135 44.8155L63.998 49.083L40.589 44.8155L63.9685 38.6875L87.4135 |
|||
44.8155Z" |
|||
fill="#5E1F18" /> |
|||
<path |
|||
d="M87.4135 109.901L63.998 105.605L40.589 109.901L63.9695 116.427L87.4135 |
|||
109.901Z" |
|||
fill="#F2B0A9" /> |
|||
<path |
|||
d="M40.589 44.8155L63.998 39.0225L64.1875 38.964V0.1565L63.998 0L40.589 |
|||
11.7065V44.8155Z" |
|||
fill="#8C3123" /> |
|||
<path |
|||
d="M87.4135 44.8155L63.998 39.0225V0L87.4135 11.7065V44.8155Z" |
|||
fill="#E05243" /> |
|||
<path |
|||
d="M63.998 154.714L40.5865 143.012V109.903L63.998 115.694L64.3425 |
|||
116.086L64.249 154.039L63.998 154.714Z" |
|||
fill="#8C3123" /> |
|||
<path |
|||
d="M63.998 154.714L87.4115 143.012V109.903L63.998 115.694V154.714" |
|||
fill="#E05243" /> |
|||
<path |
|||
d="M117.684 26.843L128 32V122.51L117.684 127.665V26.843Z" |
|||
fill="#E05243" /> |
|||
</svg> |
|||
@ -0,0 +1,391 @@ |
|||
<script> |
|||
export let width = "100" |
|||
export let height = "100" |
|||
</script> |
|||
|
|||
<svg |
|||
{width} |
|||
{height} |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
viewBox="0 0 1478.201 1195.111"> |
|||
<g transform="matrix(.569 0 0 .569 199.451 -82.735)"> |
|||
<linearGradient |
|||
id="a" |
|||
gradientUnits="userSpaceOnUse" |
|||
x1="-2901.952" |
|||
y1="923.573" |
|||
x2="-2061.249" |
|||
y2="1420.331" |
|||
gradientTransform="matrix(.1234 0 0 -.1234 1158.33 1550.273)"> |
|||
<stop offset="0" stop-color="#909ca9" /> |
|||
<stop offset="1" stop-color="#ededee" /> |
|||
</linearGradient> |
|||
<path |
|||
fill="url(#a)" |
|||
d="M1410.773 814.195l-286.9 93.683-249.599 110.161-69.829 18.435c-17.784 |
|||
16.916-36.431 34.049-56.599 51.397-22.119 19.082-42.72 36.433-58.553 |
|||
49.008-17.564 13.88-43.587 39.902-56.814 56.38-19.735 24.721-35.348 |
|||
50.96-42.071 71.13-11.928 36.433-6.07 73.297 16.916 107.346 29.492 43.369 |
|||
88.261 87.606 156.785 117.749 34.916 15.4 93.683 35.132 137.92 46.19 |
|||
73.512 18.651 215.771 38.819 294.054 41.857 15.828.65 37.082.65 37.947 0 |
|||
1.737-1.088 13.881-24.289 27.979-53.129 48.142-98.238 82.838-190.402 |
|||
101.703-269.119 11.276-47.706 20.166-111.246 26.019-186.492 1.521-21.036 |
|||
2.169-91.514.868-115.37-1.953-39.033-5.423-70.692-10.84-101.703-.868-4.555-1.088-8.676-.652-8.892.865-.65 |
|||
3.467-1.517 38.815-11.712l-7.153-16.912v-.005h.004zm-65.49 38.386c2.602 0 |
|||
9.539 66.573 11.273 108.646.436 8.89.216 14.745-.216 14.745-1.733 |
|||
0-36.649-20.599-61.583-36.212-21.687-13.663-62.888-40.988-69.393-46.192-2.173-1.517-1.957-1.733 |
|||
15.828-7.807 30.14-10.194 101.706-33.18 104.091-33.18zm-146.161 |
|||
48.143c1.953 0 6.937 2.816 18.865 10.191 44.671 27.974 105.393 61.805 |
|||
131.415 73.083 8.022 3.469 8.887 2.166-9.542 14.746-39.468 26.889-88.697 |
|||
53.344-148.983 80.018-10.624 4.771-19.514 8.456-19.73 8.456-.432 0 |
|||
.865-5.418 2.598-11.925 14.53-54.001 22.772-108.647 |
|||
23.208-152.452.216-21.687.216-21.687 2.169-22.334-.436.217-.22.217 0 |
|||
.217zm-30.142 11.492c1.297 1.299.432 49.877-1.304 63.104-3.903 |
|||
31.662-9.975 61.153-19.947 94.335-2.386 8.018-4.558 14.745-4.987 |
|||
15.177-.872 |
|||
1.083-30.581-27.975-40.339-39.251-16.916-19.518-30.141-39.035-39.9-58.117-4.988-9.759-12.793-28.84-12.144-29.492 |
|||
3.469-2.385 117.753-46.622 118.621-45.756zm-141.826 55.731c.216 0 .432 0 |
|||
.652.216.432.434 1.953 3.905 3.254 7.807 6.937 18.867 22.548 46.624 35.997 |
|||
64.407 14.746 19.518 34.048 40.334 50.091 53.996 5.207 4.337 9.975 8.456 |
|||
10.624 9.108 1.304 1.302 1.737 1.083-33.612 14.53-40.981 15.613-85.656 |
|||
31.226-136.835 47.706a6825.474 6825.474 0 0 0-36.643 |
|||
11.928c-1.955.652-1.303-.434 4.335-9.323 25.371-39.686 63.97-117.536 |
|||
85.657-172.618 3.687-9.542 7.373-19.082 8.025-21.251.868-3.038 1.95-4.121 |
|||
4.768-5.64 1.518-.43 3.038-.866 3.687-.866zm-43.367 17.999c.649.436-10.411 |
|||
23.637-21.254 44.889-21.036 40.985-44.022 81.323-74.815 130.331-5.204 |
|||
8.456-10.19 16.265-10.842 17.132-1.083 1.519-1.519 |
|||
1.083-4.988-5.638-7.373-14.53-13.447-33.181-16.699-50.313-3.254-16.916-2.602-46.406 |
|||
1.086-64.621 2.816-13.444 2.602-13.227 9.107-16.481 27.757-14.095 |
|||
117.537-56.166 118.405-55.299zm374.073 15.182v9.107c0 48.359-5.204 |
|||
114.716-12.797 163.077-1.301 8.456-2.389 15.393-2.602 15.613 0 |
|||
0-6.288-1.733-13.661-3.905-32.527-10.193-67.875-25.156-99.754-42.718-21.038-11.494-51.612-30.363-50.743-31.231.213-.215 |
|||
9.323-4.986 19.947-10.625 42.509-22.118 83.274-45.972 118.622-69.609 |
|||
13.229-8.892 33.176-23.202 37.518-27.107l3.47-2.602zm-537.802 64.185c.867 |
|||
0 .65 1.735-.651 9.542-.868 5.64-1.951 16.049-2.382 23.202-1.739 31.662 |
|||
3.469 55.084 19.082 87.177 4.337 8.892 7.809 16.265 7.589 16.48-1.519 |
|||
1.303-145.074 43.375-190.183 55.734-13.444 3.685-25.152 6.939-26.024 |
|||
7.153-1.515.436-1.733.22-1.083-3.47 4.987-31.875 29.276-73.512 |
|||
63.104-108.644 22.554-23.419 40.554-37.08 71.347-54.648 22.119-12.575 |
|||
56.165-31.439 58.767-32.309.002-.217.218-.217.434-.217zm338.295 |
|||
60.503c.216-.216 5.42 2.605 11.708 6.29 46.408 26.891 111.03 51.83 166.108 |
|||
64.623l4.991 1.086-6.941 3.899c-28.84 16.049-123.606 55.515-220.538 |
|||
91.732-14.098 5.202-27.975 10.409-30.581 11.492-2.602 1.083-4.988 |
|||
1.735-4.988 1.519 0-.22 3.906-7.809 8.89-17.132 27.107-50.744 |
|||
54.433-112.547 68.311-155.485 1.739-4.12 2.82-7.805 3.04-8.024zm-34.48 |
|||
11.278c.22.221-1.517 4.771-3.687 9.975-18.865 45.756-43.59 95.636-75.249 |
|||
151.583-8.022 14.314-14.746 25.808-14.966 25.808-.213 |
|||
0-6.721-3.906-14.527-8.676-45.976-28.192-86.743-62.888-113.414-96.501l-3.905-4.771 |
|||
19.732-5.422c70.696-19.298 130.762-40.116 190.4-65.704 8.459-3.471 |
|||
15.4-6.292 15.616-6.292zm214.253 74.815s.217.217 0 0c.216 4.988-10.844 |
|||
49.661-19.953 81.969-7.589 27.107-14.098 48.361-26.022 85.874-5.204 |
|||
16.485-9.755 30.143-9.975 30.143-.216 |
|||
0-1.517-.216-2.818-.647-64.405-11.714-122.089-27.977-176.303-49.661-15.182-6.074-36.866-15.833-38.167-16.916-.432-.438 |
|||
12.58-6.506 29.06-13.663 98.669-43.154 201.024-92.164 236.153-113.196 |
|||
4.119-2.603 7.373-3.903 8.025-3.903zm-494.646 16.916c.434.432-27.107 |
|||
40.118-65.709 94.114-13.444 18.867-29.057 40.985-34.911 49.225-5.856 |
|||
8.241-14.746 21.253-19.734 29.06l-9.112 |
|||
14.096-9.759-8.24c-11.494-9.544-31.442-29.927-40.333-41.204-18.651-23.201-31.226-47.706-36.214-70.04-2.386-10.411-2.386-15.618-.22-16.265 |
|||
3.252-.867 61.153-14.53 115.37-27.11 30.143-6.937 65.054-15.177 |
|||
77.632-18.213 12.579-3.041 22.774-5.423 22.99-5.423zm27.756 10.626l6.937 |
|||
7.806c31.231 34.914 63.108 60.724 101.708 83.272 6.941 3.906 12.144 7.373 |
|||
11.708 7.594-1.514 1.083-134.016 48.136-195.385 69.389-34.478 |
|||
12.143-62.888 21.901-63.102 21.901-.216 |
|||
0-2.169-1.299-4.341-2.818l-3.901-2.82 6.288-9.106c20.383-29.493 |
|||
45.976-61.803 101.707-129.028l38.381-46.19zm173.053 123.822c.213-.215 |
|||
9.755 3.252 21.464 7.594 28.195 10.624 50.527 17.345 80.456 24.936 36.866 |
|||
9.326 90.211 18.434 121.657 21.035 4.771.432 7.373.868 6.505 |
|||
1.519-1.521.868-33.395 11.494-56.816 18.867-37.302 11.708-151.149 |
|||
45.32-243.962 71.995-17.132 4.987-31.879 9.108-32.746 |
|||
9.323-2.166.436-9.325-1.519-9.325-2.386 0-.431 5.204-7.153 11.494-14.527 |
|||
31.225-37.3 62.238-78.935 88.044-118.403 7.154-10.846 13.229-19.736 |
|||
13.229-19.953zm-38.17 1.087c.216.216-15.179 24.936-42.066 67.439-11.496 |
|||
17.999-24.291 38.383-28.846 45.54-4.337 6.939-10.842 17.784-14.527 |
|||
23.854l-6.29 |
|||
11.061-3.252-.868c-7.809-2.169-62.672-21.471-77.202-27.325-18-7.157-36.649-15.829-50.529-23.202-17.346-9.326-39.03-23.206-37.297-23.637.433-.216 |
|||
30.143-8.243 65.922-17.999 94.984-25.809 147.678-40.77 182.161-51.612 |
|||
6.29-1.952 11.71-3.471 11.926-3.251zm269.985 63.318h.216c.868 2.171-34.26 |
|||
99.755-47.06 130.547-2.815 6.939-3.896 8.677-5.417 |
|||
8.456-3.687-.213-54.646-7.37-85.66-11.925-53.994-8.24-144.641-24.073-167.409-29.275l-5.204-1.083 |
|||
32.307-7.378c69.396-15.613 102.791-24.069 136.619-34.478 42.722-13.011 |
|||
85.011-29.276 127.729-49.225 6.722-3.037 12.361-5.422 13.879-5.639z" /> |
|||
<linearGradient |
|||
id="b" |
|||
gradientUnits="userSpaceOnUse" |
|||
x1="-2882.7" |
|||
y1="10288.81" |
|||
x2="-2206.249" |
|||
y2="10288.81" |
|||
gradientTransform="matrix(.1234 0 0 -.1234 1158.33 1550.273)"> |
|||
<stop offset="0" stop-color="#939fab" /> |
|||
<stop offset="1" stop-color="#dcdee1" /> |
|||
</linearGradient> |
|||
<path |
|||
fill="url(#b)" |
|||
d="M1114.983 145.414c-4.771-.647-81.757 27.11-131.415 47.275-67.01 |
|||
27.327-119.052 53.351-151.148 75.899-11.925 8.461-26.891 23.422-29.273 |
|||
29.276-.867 2.169-1.303 4.771-1.303 7.373l29.06 27.541 69.175 22.119 |
|||
164.594 29.493 188.228 32.312 1.953-16.264c-.649 |
|||
0-1.085-.216-1.73-.216l-24.728-3.905-4.984-8.89c-25.59-45.107-53.781-101.056-70.261-138.789-12.793-29.276-24.938-63.102-31.662-87.391-3.687-14.746-4.119-15.613-6.501-15.829v-.005h-.005zm-3.474 |
|||
11.063h.223c.213.214 1.081 6.29 1.95 13.442 3.683 30.364 10.411 59.635 |
|||
21.035 91.297 8.022 23.855 8.022 22.555-1.301 |
|||
19.734-22.119-6.07-121.221-23.202-193-33.177-11.494-1.519-21.253-3.036-21.253-3.252-.867-.867 |
|||
51.827-28.41 75.031-39.25 29.709-13.665 111.246-47.711 |
|||
117.315-48.794zm-209.047 97.15l8.461 2.816c45.97 15.616 161.551 37.736 |
|||
225.31 42.94 7.154.651 13.229 1.303 13.442 1.303.216.216-5.852 |
|||
3.469-13.661 7.154-30.79 15.397-64.621 34.264-88.042 48.794-6.937 |
|||
4.335-13.229 7.807-14.094 7.807-.868 |
|||
0-5.42-.868-10.191-1.519l-8.674-1.303-21.683-21.253c-38.167-37.08-68.094-65.704-79.588-76.549l-11.28-10.19zm-8.671 |
|||
6.721l30.576 38.168c16.696 21.035 33.611 41.635 37.301 46.187 3.683 4.557 |
|||
6.721 8.245 6.505 |
|||
8.461-.868.65-44.236-7.809-67.226-13.011-23.637-5.423-33.395-8.025-47.924-12.577l-11.928-3.905v-3.038c.216-14.53 |
|||
18.651-36.214 49.877-58.331l2.819-1.954zm259.791 52.046c.869 0 1.95 1.951 |
|||
4.552 7.806 7.373 16.263 30.364 60.07 35.997 68.526 1.74 2.822 4.771 |
|||
3.038-25.802-1.95-73.512-11.93-97.152-15.829-97.152-16.263 0-.216 |
|||
2.169-1.735 4.988-3.254 22.771-12.575 45.756-28.624 66.142-45.756 |
|||
4.988-4.121 9.542-8.024 10.407-8.676.216-.433.652-.649.868-.433z" /> |
|||
<radialGradient |
|||
id="c" |
|||
cx="-14217.448" |
|||
cy="7277.705" |
|||
r="898.12" |
|||
gradientTransform="matrix(-.1185 -.0178 -.036 .237 -198.955 -1314.415)" |
|||
gradientUnits="userSpaceOnUse"> |
|||
<stop offset="0" stop-color="#ee352c" /> |
|||
<stop offset="1" stop-color="#a91d22" /> |
|||
</radialGradient> |
|||
<path |
|||
fill="url(#c)" |
|||
d="M804.66 294.828s-4.768 7.593-.215 18.87c2.822 6.937 11.061 15.393 |
|||
20.384 24.069 0 0 96.5 94.114 108.211 107.561 53.344 61.585 76.549 122.305 |
|||
78.718 206.012 1.301 53.78-8.894 101.054-34.264 155.919-45.106 |
|||
98.453-140.307 207.098-287.117 327.67l21.472-7.157c13.878-10.411 |
|||
32.745-21.467 76.982-45.756 102.137-55.952 217.071-107.346 358.028-160.258 |
|||
202.971-76.335 536.715-165.681 |
|||
726.676-194.736l19.737-3.038-3.038-4.771c-17.345-26.891-29.276-43.587-43.59-61.369-41.633-51.612-92.157-93.463-153.964-128.161-85.007-47.489-194.956-84.571-334.173-112.112-26.239-5.207-83.923-15.181-130.763-22.337-99.321-15.393-163.51-26.021-234.203-38.165-25.37-4.339-63.323-10.843-88.478-16.263-13.011-2.822-37.947-8.676-57.464-15.398-15.613-6.075-38.168-12.147-42.939-30.58zm55.952 |
|||
54.216c.214-.214 3.683 1.083 8.24 2.602 8.24 2.816 18.865 6.07 31.446 |
|||
9.542a1599.47 1599.47 0 0 0 28.624 7.589c13.011 3.251 23.852 6.288 24.068 |
|||
6.288 1.521 1.519 23.424 71.558 30.797 98.449 2.815 10.195 4.988 18.867 |
|||
4.771 |
|||
18.867-.223.22-2.605-3.469-5.423-8.456-25.373-44.673-65.491-89.995-111.899-126.428-6.069-4.333-10.624-8.237-10.624-8.453zm106.692 |
|||
29.492c1.085 0 5.856.651 11.708 1.951 36.866 8.24 103.008 20.818 145.293 |
|||
27.975 7.157 1.083 12.797 2.387 12.797 2.818 0 .436-2.605 1.951-5.859 |
|||
3.688-7.153 3.685-35.997 20.815-45.536 27.322-24.073 16.047-45.756 |
|||
33.395-61.371 49.008-6.288 6.29-11.712 11.494-11.712 |
|||
11.494s-1.297-3.685-2.386-8.242c-7.802-30.143-24.069-74.816-38.815-106.258-2.386-4.986-4.339-9.541-4.339-9.973 |
|||
0 .433 0 .217.22.217zm187.795 35.781c1.301.432 3.47 7.806 7.806 24.069 |
|||
8.025 31.446 11.712 66.576 10.411 99.321-.436 9.108-.868 17.564-1.304 |
|||
18.651l-.649 |
|||
2.166-11.276-3.685c-23.204-7.373-60.935-18.435-93.245-27.541-18.436-4.988-33.395-9.542-33.395-9.975 |
|||
0-1.303 26.891-28.192 38.383-38.383 21.898-19.303 81.316-65.275 |
|||
83.269-64.623zm14.963 2.166c.652-.647 89.779 14.746 130.331 22.554 30.145 |
|||
5.854 73.948 14.963 76.549 16.049 1.301.432-3.254 3.034-17.784 |
|||
9.539-57.248 25.808-99.754 49.008-142.036 77.202-11.06 7.373-20.386 |
|||
13.444-20.602 13.444-.216 0-.433-6.287-.433-13.878 |
|||
0-41.201-8.241-82.838-23.424-117.968-1.517-3.47-2.818-6.722-2.601-6.942zm230.516 |
|||
45.542c.652.65-2.169 18.217-4.771 28.624-7.806 32.312-28.84 80.24-54.643 |
|||
125.343-4.558 8.024-8.677 14.53-9.114 |
|||
14.746-.429.216-6.285-3.038-13.009-6.941-25.154-14.746-53.778-28.624-85.007-41.637-8.671-3.685-16.263-6.723-16.48-7.153-1.521-1.303 |
|||
68.308-47.493 105.174-69.612 29.276-17.781 76.982-44.239 |
|||
77.85-43.37zm16.48 2.601c1.953 0 41.421 10.844 62.019 16.916 50.963 15.181 |
|||
109.512 36.648 147.679 53.996l15.828 7.159-11.056 2.6c-93.245 |
|||
21.467-173.049 46.192-250.034 77.418-6.289 2.602-11.928 4.771-12.357 |
|||
4.771-.436 0 1.733-4.987 4.552-11.061 23.204-49.225 38.167-100.62 |
|||
41.85-144.427.221-4.121.867-7.372 1.519-7.372zm-392.938 90.213c.649-.652 |
|||
30.793 6.506 47.057 11.056 24.721 6.942 77.198 24.505 77.198 25.808 0 |
|||
.216-5.853 5.204-12.79 11.278-28.408 23.637-55.734 48.572-88.481 |
|||
80.234-9.759 9.328-17.997 16.917-18.429 16.917-.436 |
|||
0-.649-1.304-.436-3.038 4.987-36.433 |
|||
3.906-83.272-3.034-130.763-.653-6.074-1.302-11.276-1.085-11.492zm633.433.652c.429.431-13.881 |
|||
22.984-22.988 35.777-13.009 18.649-32.098 43.375-75.252 97.588-22.765 |
|||
28.622-48.358 60.936-56.812 71.778-8.678 10.842-15.831 19.948-16.051 |
|||
19.948-.216 |
|||
0-3.031-3.901-6.069-8.671-24.289-36.433-53.349-68.311-87.829-96.935-6.505-5.423-13.658-11.278-16.044-13.013-2.386-1.734-4.339-3.469-4.339-3.685 |
|||
0-.649 36.862-16.483 64.841-27.757 49.01-19.952 115.794-43.805 |
|||
165.892-59.203 26.24-8.239 54.215-16.263 54.651-15.827zm16.696 |
|||
4.334c.865-.215 6.072 2.387 12.361 6.07 52.697 30.143 104.305 68.962 |
|||
145.077 108.864 11.492 11.278 39.9 40.77 39.464 40.986 0 |
|||
0-9.975.867-21.683 1.733-91.296 6.942-208.178 26.239-320.511 53.345-7.589 |
|||
1.733-14.31 3.252-14.746 3.252-.429 0 8.025-8.456 18.653-18.647 |
|||
65.922-63.538 96.067-103.656 131.628-175.22 4.986-10.623 9.325-19.731 |
|||
9.757-20.383-.216 0-.216 0 0 0zm-482.936 49.446c3.038.647 31.229 13.88 |
|||
52.48 24.503 19.517 9.755 48.794 25.372 50.311 26.671.216.216-10.195 |
|||
5.638-22.984 11.928-40.772 20.384-75.684 39.682-112.118 61.802-10.408 |
|||
6.29-19.082 11.497-19.298 11.497-.868 0-.652-.872 5.204-11.497 |
|||
19.518-35.561 35.129-78.065 44.023-119.486.864-3.252 1.733-5.418 |
|||
2.382-5.418zm-28.192 5.202c.652.652-6.721 27.323-11.273 41.853-8.894 |
|||
27.541-23.856 62.02-38.383 88.043-3.474 6.069-8.677 14.961-11.496 |
|||
19.948l-5.42 |
|||
8.674-12.144-11.707c-14.094-13.663-25.59-22.12-40.333-29.712-5.859-3.033-10.411-5.638-10.411-6.069 |
|||
0-1.735 37.082-35.347 65.49-59.635 20.383-17.566 63.321-52.045 |
|||
63.97-51.395zm172.404 70.913l10.627 6.937c24.282 15.833 52.906 36.866 |
|||
74.813 55.298 12.357 10.19 36.21 31.662 40.985 36.866l2.598 2.822-17.561 |
|||
4.986c-99.321 27.538-176.087 52.043-265.649 85.007-9.975 3.685-18.433 |
|||
6.721-19.085 6.721-1.297 0-2.385 1.083 19.954-19.519 57.251-52.691 |
|||
107.992-110.812 145.726-167.411l7.592-11.707zm-45.324 |
|||
11.276c.432.432-29.276 42.284-47.06 65.922-21.251 28.192-58.985 |
|||
75.465-85.007 106.256-10.84 12.797-20.163 23.422-20.599 |
|||
23.64-.652.216-.868-3.036-.868-8.024 |
|||
0-26.242-6.721-54.216-18.433-78.068-4.988-9.975-5.856-12.361-4.768-13.444 |
|||
4.119-3.688 67.223-39.686 107.123-61.153 26.89-14.312 68.956-35.563 |
|||
69.612-35.129zm-274.107 67.225c.652 0 5.64 2.6 11.279 5.638 13.878 7.589 |
|||
26.239 16.046 37.298 25.156.432.432-5.204 4.988-12.577 10.406-20.602 |
|||
14.746-51.828 38.385-70.041 52.915-19.088 15.18-19.734 15.613-17.568 |
|||
12.361 14.314-21.903 21.467-34.264 29.06-50.093 6.721-14.094 13.442-30.793 |
|||
18.213-45.323 1.734-6.289 3.904-11.06 4.336-11.06zm73.083 |
|||
57.248c1.081-.214 2.386 1.735 8.238 10.411 12.361 18.429 21.903 43.154 |
|||
24.292 63.104l.429 4.339-29.705 11.494c-53.133 20.599-102.139 |
|||
40.985-135.322 56.162-9.322 4.339-25.587 12.144-36.211 17.352-10.627 |
|||
5.418-19.301 9.539-19.301 9.323s6.721-5.204 14.961-11.278c64.844-47.055 |
|||
121.007-98.669 163.076-150.279 4.555-5.423 8.677-10.411 |
|||
9.107-10.627l.436-.001zm-33.612 8.242c.868.867-23.853 28.84-40.768 |
|||
45.971-41.853 42.723-83.273 76.12-134.669 108.649-6.505 4.119-12.359 |
|||
7.804-13.011 8.24-1.519.867.432-1.303 22.986-25.808 14.314-15.397 |
|||
25.155-28.408 37.516-44.453 8.24-10.624 9.759-12.143 21.688-20.604 |
|||
31.878-22.987 105.39-72.864 106.258-71.995z" /> |
|||
</g> |
|||
<path |
|||
fill="#231F1F" |
|||
d="M265.747 900.102c-2.276 0-4.553.217-6.809.217-45.975 2.45-76.983 |
|||
22.683-95.113 62.195-15.506 35.735-13.813 82.446.174 118.4 16.265 35.131 |
|||
42.547 53.672 86.416 60.675 9.282 1.52 15.506 6.616 33.483 27.606l22.12 |
|||
25.915h40.118l-26.676-26.892c-14.746-14.745-26.673-27.584-26.673-28.712 |
|||
0-1.127 5.641-3.599 12.469-5.68 22.51-6.812 41.203-24.202 54.279-50.854 |
|||
10.583-21.402 12.102-28.018 13.619-54.646 |
|||
3.969-79.26-37.82-128.813-107.409-128.247l.002.023zm35.173 207.27c-19.517 |
|||
9.453-47.857 11.34-66.356 |
|||
4.553-19.127-7.025-37.646-26.889-45.975-49.377-9.259-24.591-7.937-69.956 |
|||
2.646-90.386 17.023-32.528 39.534-47.49 72.43-47.49 48.792 0 76.549 29.884 |
|||
80.171 86.048 2.863 46.885-12.838 82.058-42.895 |
|||
96.632l-.021.02zm693.025-139.568c-16.828 0-29.709 6.811-38.385 20.231l-6.809 |
|||
10.627v-27.628h-29.123v165.678h29.104v-52.956c0-48.424.604-54.084 |
|||
7.371-67.335 9.326-18.172 25.371-27.234 40.879-22.897l10.408 |
|||
3.036v-28.712h-13.445v-.044zm-171.098-1.519c-5.705 0-11.756.76-17.781 |
|||
2.084-38.971 10.19-60.938 47.489-59.594 85.873 0 32.139 6.244 48.206 21.752 |
|||
65.057 31.77 26.065 60.502 28.146 99.275 14.161 6.615-2.819 13.814-6.072 |
|||
13.814-6.072v-26.065l-13.814 7.156c-31.379 13.661-55.016 |
|||
13.661-73.949-2.43-12.076-12.296-17.391-27.042-19.84-43.868h117.426v-22.339c0-45.539-27.41-74.294-67.313-73.557h.024zm-47.492 |
|||
72.647s4.338-28.407 20.428-39.554c7.744-5.466 16.633-8.11 25.328-8.11 8.719 |
|||
0 17.414 2.818 24.592 8.306 14.748 11.341 17.219 39.143 17.219 |
|||
39.143h-87.566v.215h-.001zm-702.111-29.881c-31.573-19.128-45.582-32.921-43.869-49.185 |
|||
4.9-44.997 60.503-38.773 |
|||
91.295-21.749l.219-30.272s-17.024-7.373-41.421-7.764c-37.429-.564-61.63 |
|||
11.709-72.97 36.691-16.656 36.865-1.908 64.665 51.396 95.677 29.925 17.412 |
|||
43.152 32.528 43.152 49.008 0 34.047-41.05 45.931-83.401 |
|||
24.57-8.716-4.337-16.09-7.959-16.48-7.959-1.519 9.651-.736 32.745-.736 |
|||
32.745s13.012 5.466 32.527 9.236c48.4 9.65 92.445-13.054 96.608-49.919 |
|||
3.622-34.609-8.893-52.761-56.318-81.104l-.002.025zm1178.454-43.155c-5.682 |
|||
0-11.711.78-18 2.103-38.924 10.192-60.85 47.492-59.354 85.876 0 32.095 6.225 |
|||
48.011 21.729 64.838 31.771 26.089 60.504 28.191 99.473 14.184 6.592-2.818 |
|||
13.77-6.026 13.77-6.026v-26.109l-13.791 7.197c-31.443 13.619-55.082 |
|||
13.619-73.947-2.471-12.145-12.274-17.414-26.847-19.865-43.871h117.232v-22.336c0-45.321-27.412-74.099-67.313-73.339l.066-.046zm-47.492 |
|||
72.646s4.381-28.365 20.449-39.729c7.721-5.485 16.611-8.132 25.307-8.132 |
|||
8.674 0 17.414 2.819 24.594 8.327 14.746 11.342 17.219 39.338 17.219 |
|||
39.338h-87.545l-.024.196zm-533.809-29.123c-31.573-19.083-45.54-32.92-43.848-49.185 |
|||
4.9-45.02 60.504-38.773 |
|||
91.296-21.749l.218-30.272s-17.024-7.374-41.421-7.722c-37.429-.563-61.63 |
|||
11.711-72.991 36.692-16.633 36.864-1.692 64.666 51.437 95.677 29.884 17.393 |
|||
43.111 32.312 43.111 48.792 0 34.047-41.029 46.126-83.381 |
|||
24.569-8.674-4.337-16.046-7.916-16.48-7.916-1.519 9.649-.736 32.746-.736 |
|||
32.746s12.858 5.27 32.31 9.237c48.445 9.672 92.51-13.012 96.653-49.877 |
|||
3.6-34.437-8.891-52.587-56.167-80.952v-.04zm752.421-42.005c-16.828 0-29.859 |
|||
6.829-38.383 20.254l-6.811 |
|||
10.582v-27.583h-29.123V1136.3h29.102v-52.954c0-48.403.584-54.085 |
|||
7.375-67.313 9.324-18.15 25.369-27.235 40.875-22.878l10.408 |
|||
3.035v-28.775h-13.443zm-984.021 |
|||
41.05V902.941h-29.361v233.728h123.478v-27.604h-94.116v-100.601zm679.015 |
|||
32.896l-24.201 62.975-23.27-63.322-23.637-70.173h-30.055c19.475 55.212 |
|||
40.658 111.376 62.02 165.829 9.26.216 18.541 0 27.799 0l32.682-82.058 |
|||
33.287-83.75h-28.732s-12.688 33.266-25.914 70.521l.021-.022zM506.455 |
|||
839.251c4.728 0 8.674-1.516 11.927-4.769 3.208-3.211 4.9-6.984 4.9-11.711 |
|||
0-4.728-1.692-8.675-4.9-11.711-3.253-3.035-7.005-4.555-11.711-4.555-4.769 |
|||
0-8.717 1.52-11.927 4.728-3.252 3.211-4.727 7.158-4.727 11.712 0 4.771 1.519 |
|||
8.716 4.727 11.711 3.037 3.034 6.984 4.553 11.711 |
|||
4.553v.042zm-10.408-26.889c2.818-2.818 6.245-4.121 10.625-4.121 4.121 0 |
|||
7.548 1.303 10.411 4.121 2.819 2.819 4.337 6.245 4.337 10.409 0 4.163-1.518 |
|||
7.764-4.337 10.582-2.862 2.817-6.29 4.163-10.411 4.163-4.185 |
|||
0-7.59-1.301-10.408-4.163-2.819-2.818-4.337-6.419-4.337-10.582 0-4.164 |
|||
1.301-7.589 4.12-10.409zm7.003 11.928h1.908c1.346 0 2.668 1.3 3.795 |
|||
3.773l2.279 5.116h3.577l-2.818-5.683c-1.149-2.275-2.276-3.598-3.6-3.969 |
|||
1.67-.39 2.992-.953 3.947-2.082.952-.974 1.3-2.298 1.3-3.795 |
|||
0-1.734-.542-3.034-1.69-3.989-1.302-1.084-3.384-1.669-6.074-1.669h-6.026v21.187h3.035v-8.891l.367.002zm0-9.846h2.647c1.908 |
|||
0 3.253.39 3.99.953.716.564.911 1.303.911 2.646 0 2.45-1.52 3.601-4.337 |
|||
3.601h-3.252v-7.2h.041zm-485.018 |
|||
7.958c0-7.373-.216-12.858-.39-16.09h.174c.758 3.814 1.691 6.657 2.45 |
|||
8.543l28.19 62.975h4.728l28.19-63.538c.761-1.733 1.52-4.337 |
|||
2.452-7.959h.216c-.563 6.29-.758 11.754-.758 |
|||
16.112v55.581h9.648v-82.622h-12.1L54.919 852.87c-.955 2.276-2.278 |
|||
5.683-3.969 |
|||
10.193h-.392c-.563-2.234-1.886-5.639-3.772-9.803l-25.33-58.053H8.598v82.621h9.281v-55.385l.153-.041zm96.045.154h8.329v51.458h-8.329v-51.458zm4.164-18.868c1.736 |
|||
0 3.21-.587 4.337-1.734 1.15-1.129 1.91-2.603 1.91-4.337 |
|||
0-1.692-.565-3.211-1.887-4.337-1.171-1.15-2.668-1.737-4.381-1.737-1.69 |
|||
0-3.208.587-4.338 1.737-1.146 1.126-1.907 2.645-1.907 4.337 0 1.887.586 |
|||
3.208 1.907 4.337 1.304 1.147 2.647 1.734 4.338 1.734h.021zm63.54 |
|||
71.455v-9.066c-4.555 3.405-9.456 5.098-14.53 5.098-6.07 |
|||
0-10.995-2.081-14.595-6.07-3.577-3.947-5.485-9.436-5.485-16.266 0-7.156 |
|||
1.908-12.84 5.854-17.177 3.795-4.163 8.719-6.245 14.748-6.245 4.922 0 9.647 |
|||
1.52 14.009 4.557v-9.65c-3.968-2.082-8.5-3.037-13.619-3.037-9.456 0-16.827 |
|||
3.037-22.335 8.894-5.466 5.854-8.285 13.813-8.285 23.42 0 8.543 2.45 15.722 |
|||
7.548 21.209 5.312 5.637 12.102 8.5 20.428 8.5 6.438-.178 11.707-1.523 |
|||
16.262-4.167zm23.831-27.433c0-6.788 1.518-12.273 4.337-16.049 2.647-3.403 |
|||
5.855-5.116 9.65-5.116 3.21 0 5.486.585 7.155 |
|||
1.908v-9.846c-1.3-.563-3.187-.758-5.637-.758-3.405 0-6.439 1.146-9.107 |
|||
3.253-2.819 2.231-5.074 5.638-6.397 |
|||
9.975h-.216v-12.08h-9.433v58.985h9.454V847.71h.194zm54.279 31.443c8.892 0 |
|||
16.048-2.863 21.36-8.543 5.29-5.641 7.936-13.229 7.936-22.686 |
|||
0-9.647-2.427-17.021-7.372-22.51-4.9-5.483-11.711-8.132-20.603-8.132s-16.048 |
|||
2.647-21.36 7.764c-5.681 5.641-8.674 13.599-8.674 23.813 0 8.891 2.429 |
|||
16.265 7.548 21.751 5.29 5.68 12.295 8.521 21.165 |
|||
8.521v.022zm-13.445-48.055c3.6-3.795 8.329-5.683 14.182-5.683 6.074 0 10.627 |
|||
1.888 14.01 5.683 3.404 3.969 5.097 9.63 5.097 17.197 0 7.198-1.519 |
|||
12.859-4.729 16.654-3.208 3.969-7.936 6.071-14.183 6.071-6.071 |
|||
0-10.777-2.104-14.377-6.071-3.577-3.99-5.291-9.456-5.291-16.654-.368-7.156 |
|||
1.519-13.01 5.291-17.197zm84.141 42.916c3.599-3.208 5.509-7.155 5.509-12.102 |
|||
0-4.337-1.52-7.936-4.338-10.777-2.3-2.275-5.854-4.337-10.994-6.419-4.556-1.906-7.374-3.6-8.893-4.923-1.517-1.517-2.45-3.402-2.45-6.071 |
|||
0-2.45.955-4.337 2.821-5.855 1.908-1.516 4.337-2.253 7.59-2.253 5.096 0 |
|||
9.454 1.343 13.443 4.185v-9.456c-3.816-1.906-7.958-2.817-12.686-2.817-6.071 |
|||
0-11.189 1.671-14.964 4.899-3.969 3.212-5.854 7.375-5.854 12.274 0 4.337 1.3 |
|||
7.938 3.771 10.582 2.082 2.256 5.641 4.556 10.583 6.614 4.729 2.083 7.938 |
|||
3.968 9.65 5.485 1.691 1.52 2.45 3.405 2.45 5.641 0 5.506-3.772 8.349-11.146 |
|||
8.349-5.682 0-10.776-1.866-15.333-5.638v10.189c4.121 2.475 9.066 3.601 14.53 |
|||
3.601 7.005-.368 12.49-2.081 16.264-5.486l.047-.022zm45.019-56.73c-8.893 |
|||
0-16.048 2.647-21.361 7.764-5.638 5.641-8.674 13.599-8.674 23.813 0 8.891 |
|||
2.452 16.265 7.547 21.751 5.313 5.68 12.295 8.521 21.187 8.521 9.107 0 |
|||
16.048-2.861 21.36-8.545 5.313-5.637 7.958-13.227 7.958-22.683 |
|||
0-9.65-2.472-17.022-7.374-22.509-5.115-5.487-11.927-8.133-20.601-8.133l-.042.021zm18.345 |
|||
31.012c0 7.198-1.518 12.859-4.727 16.654-3.21 3.969-7.938 6.071-14.184 |
|||
6.071-6.074 0-10.778-2.104-14.379-6.071-3.577-3.99-5.29-9.456-5.29-16.654 |
|||
0-7.59 1.888-13.444 5.683-17.393 3.576-3.773 8.306-5.682 14.182-5.682 5.854 |
|||
0 10.561 1.907 13.964 5.682 3.037 4.163 4.729 9.824 4.729 |
|||
17.393h.022zm25.547 29.513h9.433v-51.068h13.813v-7.938H428.93v-9.108c0-8.282 |
|||
3.208-12.446 9.845-12.446 2.234 0 4.511.563 6.203 |
|||
1.518v-8.521c-1.692-.759-3.969-.932-6.812-.932-5.095 0-9.258 1.519-12.664 |
|||
4.727-3.969 3.773-6.071 8.674-6.071 |
|||
15.312v9.672h-9.978v7.936h9.978v50.876l.067-.028zm38.75-16.091c0 11.538 |
|||
5.098 17.414 15.506 17.414 3.774 0 6.614-.606 8.891-1.951v-8.11c-1.734 |
|||
1.302-3.795 1.91-6.071 1.91-3.208 |
|||
0-5.464-.762-6.788-2.475-1.345-1.689-2.103-4.554-2.103-8.501v-33.286h14.961v-7.938h-14.961v-17.39c-3.253 |
|||
1.127-6.44 2.082-9.456 |
|||
3.034v14.355h-10.192v7.938h10.192v34.979l.021.021zm1014.88 |
|||
108.73c-3.209-3.034-7.004-4.553-11.709-4.553-4.77 0-8.719 1.519-11.928 |
|||
4.771-3.209 3.188-4.729 7.155-4.729 11.711 0 4.728 1.52 8.675 4.705 11.709 |
|||
3.211 3.036 7.156 4.556 11.928 4.556 4.705 0 8.674-1.52 11.928-4.729 |
|||
3.188-3.253 4.879-7.004 |
|||
4.879-11.709-.174-4.771-1.887-8.719-5.096-11.754l.022-.002zm-1.517 |
|||
22.338c-2.82 2.818-6.246 4.119-10.41 4.119-4.119 |
|||
0-7.545-1.301-10.408-4.119-2.818-2.863-4.338-6.441-4.338-10.627 0-4.121 |
|||
1.301-7.545 4.164-10.408 2.818-2.817 6.225-4.121 10.582-4.121 4.121 0 7.549 |
|||
1.304 10.41 4.121 2.818 2.863 4.336 6.287 4.336 10.408 0 4.382-1.301 |
|||
7.764-4.336 10.627zm-8.502-9.651c1.691-.39 3.037-1.149 3.969-2.081.955-.977 |
|||
1.303-2.301 1.303-3.815 |
|||
0-1.692-.543-3.037-1.691-3.969-1.301-1.085-3.404-1.671-6.07-1.671h-6.029v21.164h3.037v-8.891h1.885c1.303 |
|||
0 2.604 1.3 3.773 3.773l2.254 |
|||
5.096h3.602l-2.818-5.683c-.977-2.472-2.105-3.601-3.252-3.97l.037.047zm-2.082-1.907h-3.252v-7.155h2.668c1.887 |
|||
0 3.209.345 3.969.932.758.563.932 1.301.932 2.646 0 2.45-1.518 3.579-4.336 |
|||
3.579l.019-.002zM933.443 |
|||
816.353h2.646v-21.187h7.002v-2.646h-16.652v2.646h7.006v21.187h-.002zm16.047-15.917c0-2.062 |
|||
0-3.753-.152-4.705.174 1.126.564 1.887.738 2.45l8.133 |
|||
18.172h1.301l8.152-18.347c.219-.563.393-1.301.76-2.275-.174 1.887-.174 |
|||
3.401-.174 4.553v16.048h2.82V792.52h-3.406l-7.371 16.438c-.174.587-.762 |
|||
1.734-1.129 |
|||
3.037h-.217c-.152-.761-.541-1.519-1.084-2.818l-7.373-16.655h-3.816v23.854h2.666v-15.917l.152-.023z" /> |
|||
</svg> |
|||
@ -0,0 +1,19 @@ |
|||
import Postgres from "./Postgres.svelte" |
|||
import DynamoDB from "./DynamoDB.svelte" |
|||
import Elasticsearch from "./Elasticsearch.svelte" |
|||
import MongoDB from "./MongoDB.svelte" |
|||
import CouchDB from "./CouchDB.svelte" |
|||
import S3 from "./S3.svelte" |
|||
import Airtable from "./Airtable.svelte" |
|||
import SqlServer from "./SQLServer.svelte" |
|||
|
|||
export default { |
|||
POSTGRES: Postgres, |
|||
DYNAMODB: DynamoDB, |
|||
MONGODB: MongoDB, |
|||
ELASTICSEARCH: Elasticsearch, |
|||
COUCHDB: CouchDB, |
|||
SQL_SERVER: SqlServer, |
|||
S3: S3, |
|||
AIRTABLE: Airtable, |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
<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 ${datasourceName} already exists. Please choose another name.` |
|||
return |
|||
} |
|||
error = "" |
|||
} |
|||
|
|||
async function saveDatasource() { |
|||
const { type, ...config } = integration |
|||
|
|||
// Create datasource |
|||
const response = 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/${response._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>Source</Label> |
|||
<TableIntegrationMenu bind:integration /> |
|||
</ModalContent> |
|||
@ -0,0 +1,61 @@ |
|||
<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> |
|||
@ -0,0 +1,87 @@ |
|||
<script> |
|||
import { backendUiStore, store, allScreens } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import { DropdownMenu, Button, Input } from "@budibase/bbui" |
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|||
import IntegrationConfigForm from "../TableIntegrationMenu//IntegrationConfigForm.svelte" |
|||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" |
|||
|
|||
export let datasource |
|||
|
|||
let anchor |
|||
let dropdown |
|||
let confirmDeleteDialog |
|||
let error = "" |
|||
let originalName = datasource.name |
|||
let willBeDeleted |
|||
|
|||
function hideEditor() { |
|||
dropdown?.hide() |
|||
} |
|||
|
|||
function showModal() { |
|||
hideEditor() |
|||
confirmDeleteDialog.show() |
|||
} |
|||
|
|||
async function deleteDatasource() { |
|||
await backendUiStore.actions.datasources.delete(datasource) |
|||
notifier.success("Datasource deleted") |
|||
hideEditor() |
|||
} |
|||
</script> |
|||
|
|||
<div on:click|stopPropagation> |
|||
<div bind:this={anchor} class="icon" on:click={dropdown.show}> |
|||
<i class="ri-more-line" /> |
|||
</div> |
|||
<DropdownMenu align="left" {anchor} bind:this={dropdown}> |
|||
<DropdownContainer> |
|||
<DropdownItem |
|||
icon="ri-delete-bin-line" |
|||
title="Delete" |
|||
on:click={showModal} |
|||
data-cy="delete-datasource" /> |
|||
</DropdownContainer> |
|||
</DropdownMenu> |
|||
</div> |
|||
<ConfirmDialog |
|||
bind:this={confirmDeleteDialog} |
|||
okText="Delete Datasource" |
|||
onOk={deleteDatasource} |
|||
title="Confirm Deletion"> |
|||
Are you sure you wish to delete the datasource |
|||
<i>{datasource.name}?</i> |
|||
This action cannot be undone. |
|||
</ConfirmDialog> |
|||
|
|||
<style> |
|||
div.icon { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-end; |
|||
align-items: center; |
|||
} |
|||
|
|||
div.icon i { |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.actions { |
|||
padding: var(--spacing-xl); |
|||
display: grid; |
|||
grid-gap: var(--spacing-xl); |
|||
min-width: 400px; |
|||
} |
|||
|
|||
h5 { |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
footer { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: var(--spacing-m); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,84 @@ |
|||
<script> |
|||
import { backendUiStore, store, allScreens } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import { DropdownMenu, Button, Input } from "@budibase/bbui" |
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|||
import IntegrationConfigForm from "../TableIntegrationMenu//IntegrationConfigForm.svelte" |
|||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" |
|||
|
|||
export let query |
|||
|
|||
let anchor |
|||
let dropdown |
|||
let confirmDeleteDialog |
|||
let error = "" |
|||
let willBeDeleted |
|||
|
|||
function hideEditor() { |
|||
dropdown?.hide() |
|||
} |
|||
|
|||
function showModal() { |
|||
hideEditor() |
|||
confirmDeleteDialog.show() |
|||
} |
|||
|
|||
async function deleteQuery() { |
|||
await backendUiStore.actions.queries.delete(query) |
|||
notifier.success("Query deleted") |
|||
hideEditor() |
|||
} |
|||
</script> |
|||
|
|||
<div on:click|stopPropagation> |
|||
<div bind:this={anchor} class="icon" on:click={dropdown.show}> |
|||
<i class="ri-more-line" /> |
|||
</div> |
|||
<DropdownMenu align="left" {anchor} bind:this={dropdown}> |
|||
<DropdownContainer> |
|||
<DropdownItem |
|||
icon="ri-delete-bin-line" |
|||
title="Delete" |
|||
on:click={showModal} |
|||
data-cy="delete-datasource" /> |
|||
</DropdownContainer> |
|||
</DropdownMenu> |
|||
</div> |
|||
<ConfirmDialog |
|||
bind:this={confirmDeleteDialog} |
|||
okText="Delete Query" |
|||
onOk={deleteQuery} |
|||
title="Confirm Deletion"> |
|||
Are you sure you wish to delete this query? This action cannot be undone. |
|||
</ConfirmDialog> |
|||
|
|||
<style> |
|||
div.icon { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-end; |
|||
align-items: center; |
|||
} |
|||
|
|||
div.icon i { |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.actions { |
|||
padding: var(--spacing-xl); |
|||
display: grid; |
|||
grid-gap: var(--spacing-xl); |
|||
min-width: 400px; |
|||
} |
|||
|
|||
h5 { |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
footer { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: var(--spacing-m); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,52 @@ |
|||
<script> |
|||
import { slide } from "svelte/transition" |
|||
import Portal from "svelte-portal" |
|||
|
|||
export let title |
|||
export let onClose = () => {} |
|||
</script> |
|||
|
|||
<Portal> |
|||
<section class="drawer" transition:slide> |
|||
<header> |
|||
{title} |
|||
<div class="controls"> |
|||
<slot name="buttons" /> |
|||
<i class="ri-close-fill close" on:click={onClose} /> |
|||
</div> |
|||
</header> |
|||
<slot name="body" /> |
|||
</section> |
|||
</Portal> |
|||
|
|||
<style> |
|||
.drawer { |
|||
height: 50vh; |
|||
position: absolute; |
|||
bottom: 0; |
|||
width: 100vw; |
|||
background: var(--background); |
|||
border-top: var(--border-light); |
|||
z-index: 2; |
|||
} |
|||
|
|||
header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
border: var(--border-light); |
|||
padding: var(--spacing-m); |
|||
} |
|||
|
|||
.controls { |
|||
display: grid; |
|||
grid-auto-flow: column; |
|||
grid-gap: var(--spacing-m); |
|||
align-items: center; |
|||
} |
|||
|
|||
.close { |
|||
font-size: var(--font-size-xl); |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,162 @@ |
|||
<script> |
|||
import CodeMirror from "./codemirror" |
|||
import { onMount, createEventDispatcher } from "svelte" |
|||
import { themeStore } from "builderStore" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
const THEMES = { |
|||
DARK: "tomorrow-night-eighties", |
|||
LIGHT: "default", |
|||
} |
|||
|
|||
export let value = "" |
|||
export let readOnly = false |
|||
export let lineNumbers = true |
|||
export let tab = true |
|||
export let mode |
|||
|
|||
let width |
|||
let height |
|||
|
|||
// We have to expose set and update methods, rather |
|||
// than making this state-driven through props, |
|||
// because it's difficult to update an editor |
|||
// without resetting scroll otherwise |
|||
export async function set(new_value, new_mode) { |
|||
if (new_mode !== mode) { |
|||
await createEditor((mode = new_mode)) |
|||
} |
|||
|
|||
value = new_value |
|||
updating_externally = true |
|||
if (editor) editor.setValue(value) |
|||
updating_externally = false |
|||
} |
|||
|
|||
export function update(new_value) { |
|||
value = new_value |
|||
|
|||
if (editor) { |
|||
const { left, top } = editor.getScrollInfo() |
|||
editor.setValue((value = new_value)) |
|||
editor.scrollTo(left, top) |
|||
} |
|||
} |
|||
|
|||
export function resize() { |
|||
editor.refresh() |
|||
} |
|||
|
|||
export function focus() { |
|||
editor.focus() |
|||
} |
|||
|
|||
const modes = { |
|||
js: { |
|||
name: "javascript", |
|||
json: false, |
|||
}, |
|||
json: { |
|||
name: "javascript", |
|||
json: true, |
|||
}, |
|||
sql: { |
|||
name: "sql", |
|||
}, |
|||
svelte: { |
|||
name: "handlebars", |
|||
base: "text/html", |
|||
}, |
|||
} |
|||
|
|||
const refs = {} |
|||
let editor |
|||
let updating_externally = false |
|||
let marker |
|||
let error_line |
|||
let destroyed = false |
|||
|
|||
$: if (editor && width && height) { |
|||
editor.refresh() |
|||
} |
|||
|
|||
onMount(() => { |
|||
createEditor(mode).then(() => { |
|||
if (editor) editor.setValue(value || "") |
|||
}) |
|||
|
|||
return () => { |
|||
destroyed = true |
|||
if (editor) editor.toTextArea() |
|||
} |
|||
}) |
|||
|
|||
let first = true |
|||
|
|||
async function createEditor(mode) { |
|||
if (destroyed || !CodeMirror) return |
|||
|
|||
if (editor) editor.toTextArea() |
|||
|
|||
const opts = { |
|||
lineNumbers, |
|||
lineWrapping: true, |
|||
indentWithTabs: true, |
|||
indentUnit: 2, |
|||
tabSize: 2, |
|||
value: "", |
|||
mode: modes[mode] || { |
|||
name: mode, |
|||
}, |
|||
readOnly, |
|||
autoCloseBrackets: true, |
|||
autoCloseTags: true, |
|||
theme: $themeStore.darkMode ? THEMES.DARK : THEMES.LIGHT, |
|||
} |
|||
|
|||
if (!tab) |
|||
opts.extraKeys = { |
|||
Tab: tab, |
|||
"Shift-Tab": tab, |
|||
} |
|||
|
|||
// Creating a text editor is a lot of work, so we yield |
|||
// the main thread for a moment. This helps reduce jank |
|||
if (first) await sleep(50) |
|||
|
|||
if (destroyed) return |
|||
|
|||
editor = CodeMirror.fromTextArea(refs.editor, opts) |
|||
|
|||
editor.on("change", instance => { |
|||
if (!updating_externally) { |
|||
const value = instance.getValue() |
|||
dispatch("change", { value }) |
|||
} |
|||
}) |
|||
|
|||
if (first) await sleep(50) |
|||
editor.refresh() |
|||
|
|||
first = false |
|||
} |
|||
|
|||
function sleep(ms) { |
|||
return new Promise(fulfil => setTimeout(fulfil, ms)) |
|||
} |
|||
</script> |
|||
|
|||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} /> |
|||
|
|||
<style> |
|||
textarea { |
|||
visibility: hidden; |
|||
} |
|||
|
|||
:global(.CodeMirror) { |
|||
height: auto !important; |
|||
border-radius: var(--border-radius-m); |
|||
font-family: var(--font-sans) !important; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,56 @@ |
|||
<script> |
|||
import { |
|||
Button, |
|||
TextArea, |
|||
Label, |
|||
Input, |
|||
Heading, |
|||
Select, |
|||
} from "@budibase/bbui" |
|||
import Editor from "./QueryEditor.svelte" |
|||
|
|||
export let fields = {} |
|||
export let schema |
|||
export let editable |
|||
|
|||
let draftField = {} |
|||
|
|||
$: fieldKeys = Object.keys(fields) |
|||
$: schemaKeys = Object.keys(schema.fields) |
|||
|
|||
function updateCustomFields({ detail }) { |
|||
fields.customData = detail.value |
|||
} |
|||
</script> |
|||
|
|||
<form on:submit|preventDefault> |
|||
{#each schemaKeys as field} |
|||
<Label extraSmall grey>{field}</Label> |
|||
<div class="field"> |
|||
<Input |
|||
disabled={!editable} |
|||
type={schema.fields[field]?.type} |
|||
required={schema.fields[field]?.required} |
|||
bind:value={fields[field]} /> |
|||
</div> |
|||
{/each} |
|||
</form> |
|||
<Label extraSmall grey>Data</Label> |
|||
{#if schema.customisable} |
|||
<Editor |
|||
label="Query" |
|||
mode="json" |
|||
on:change={updateCustomFields} |
|||
readOnly={!editable} |
|||
value={fields.customData} /> |
|||
{/if} |
|||
|
|||
<style> |
|||
.field { |
|||
margin-bottom: var(--spacing-m); |
|||
display: grid; |
|||
grid-template-columns: 1fr 2%; |
|||
grid-gap: var(--spacing-m); |
|||
align-items: center; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,88 @@ |
|||
<script> |
|||
import { Button, TextArea, Label, Input, Heading } from "@budibase/bbui" |
|||
import BindableInput from "components/userInterface/BindableInput.svelte" |
|||
import { |
|||
readableToRuntimeBinding, |
|||
runtimeToReadableBinding, |
|||
} from "builderStore/replaceBindings" |
|||
|
|||
export let bindable = true |
|||
export let parameters = [] |
|||
export let bindings = [] |
|||
export let customParams = {} |
|||
|
|||
function newQueryParameter() { |
|||
parameters = [...parameters, {}] |
|||
} |
|||
|
|||
function deleteQueryParameter(idx) { |
|||
parameters.splice(idx, 1) |
|||
parameters = parameters |
|||
} |
|||
|
|||
// This is necessary due to the way readable and writable bindings are stored. |
|||
// The readable binding in the UI gets converted to a UUID value that the client understands |
|||
// for parsing, then converted back so we can display it the readable form in the UI |
|||
function onBindingChange(param, valueToParse) { |
|||
const parsedBindingValue = readableToRuntimeBinding(bindings, valueToParse) |
|||
customParams[param] = parsedBindingValue |
|||
} |
|||
</script> |
|||
|
|||
<section> |
|||
<Heading extraSmall black>Parameters</Heading> |
|||
<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} |
|||
<Input thin disabled={bindable} bind:value={parameter.name} /> |
|||
<Input thin disabled={bindable} bind:value={parameter.default} /> |
|||
{#if bindable} |
|||
<BindableInput |
|||
type="string" |
|||
thin |
|||
on:change={evt => onBindingChange(parameter.name, evt.detail)} |
|||
value={runtimeToReadableBinding(bindings, customParams[parameter.name])} |
|||
{bindings} /> |
|||
{:else} |
|||
<i |
|||
class="ri-close-circle-line delete" |
|||
on:click={() => deleteQueryParameter(idx)} /> |
|||
{/if} |
|||
{/each} |
|||
</div> |
|||
{#if !bindable} |
|||
<Button thin secondary small on:click={newQueryParameter}> |
|||
Add Parameter |
|||
</Button> |
|||
{/if} |
|||
</section> |
|||
|
|||
<style> |
|||
.parameters.bindable { |
|||
grid-template-columns: 1fr 1fr 1fr; |
|||
} |
|||
|
|||
.parameters { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr 5%; |
|||
grid-gap: 10px; |
|||
align-items: center; |
|||
margin-bottom: var(--spacing-xl); |
|||
} |
|||
|
|||
.delete { |
|||
transition: all 0.2s; |
|||
} |
|||
|
|||
.delete:hover { |
|||
transform: scale(1.1); |
|||
font-weight: 500; |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,284 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { goto } from "@sveltech/routify" |
|||
import { |
|||
Select, |
|||
Button, |
|||
Label, |
|||
Input, |
|||
TextArea, |
|||
Heading, |
|||
Spacer, |
|||
Switcher, |
|||
} from "@budibase/bbui" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import api from "builderStore/api" |
|||
import { FIELDS } from "constants/backend" |
|||
import IntegrationQueryEditor from "components/integration/index.svelte" |
|||
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte" |
|||
import { backendUiStore } from "builderStore" |
|||
|
|||
const PREVIEW_HEADINGS = [ |
|||
{ |
|||
title: "JSON", |
|||
key: "JSON", |
|||
}, |
|||
{ |
|||
title: "Schema", |
|||
key: "SCHEMA", |
|||
}, |
|||
{ |
|||
title: "Preview", |
|||
key: "PREVIEW", |
|||
}, |
|||
] |
|||
|
|||
export let query |
|||
export let fields = [] |
|||
|
|||
let config |
|||
let tab = "JSON" |
|||
let parameters |
|||
let data = [] |
|||
|
|||
$: datasource = $backendUiStore.datasources.find( |
|||
ds => ds._id === query.datasourceId |
|||
) |
|||
|
|||
$: query.schema = fields.reduce( |
|||
(acc, next) => ({ |
|||
...acc, |
|||
[next.name]: { |
|||
name: next.name, |
|||
type: "string", |
|||
}, |
|||
}), |
|||
{} |
|||
) |
|||
|
|||
$: datasourceType = datasource?.source |
|||
|
|||
$: config = $backendUiStore.integrations[datasourceType]?.query |
|||
$: docsLink = $backendUiStore.integrations[datasourceType]?.docs |
|||
|
|||
$: shouldShowQueryConfig = config && query.queryVerb && query.queryType |
|||
|
|||
function newField() { |
|||
fields = [...fields, {}] |
|||
} |
|||
|
|||
function deleteField(idx) { |
|||
fields.splice(idx, 1) |
|||
fields = fields |
|||
} |
|||
|
|||
async function previewQuery() { |
|||
try { |
|||
const response = await api.post(`/api/queries/preview`, { |
|||
fields: query.fields, |
|||
queryVerb: query.queryVerb, |
|||
parameters: query.parameters.reduce( |
|||
(acc, next) => ({ |
|||
...acc, |
|||
[next.name]: next.default, |
|||
}), |
|||
{} |
|||
), |
|||
datasourceId: datasource._id, |
|||
}) |
|||
const json = await response.json() |
|||
|
|||
if (response.status !== 200) throw new Error(json.message) |
|||
|
|||
data = json || [] |
|||
|
|||
if (data.length === 0) { |
|||
notifier.info( |
|||
"Query results empty. Please execute a query with results to create your schema." |
|||
) |
|||
return |
|||
} |
|||
|
|||
notifier.success("Query executed successfully.") |
|||
|
|||
// Assume all the fields are strings and create a basic schema |
|||
// from the first record returned by the query |
|||
fields = Object.keys(json[0]).map(field => ({ |
|||
name: field, |
|||
type: "STRING", |
|||
})) |
|||
} catch (err) { |
|||
notifier.danger(`Query Error: ${err.message}`) |
|||
console.error(err) |
|||
} |
|||
} |
|||
|
|||
async function saveQuery() { |
|||
try { |
|||
const { _id } = await backendUiStore.actions.queries.save( |
|||
query.datasourceId, |
|||
query |
|||
) |
|||
notifier.success(`Query saved successfully.`) |
|||
$goto(`../../${_id}`) |
|||
} catch (err) { |
|||
console.error(err) |
|||
notifier.danger(`Error creating query. ${err.message}`) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<header> |
|||
<Heading small>{query.name}</Heading> |
|||
{#if config} |
|||
<div class="queryVerbs"> |
|||
{#each Object.keys(config) as queryVerb} |
|||
<div |
|||
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} |
|||
</Select> |
|||
{/if} |
|||
<Spacer medium /> |
|||
<Button primary href={docsLink} target="_blank"> |
|||
<i class="ri-book-2-line" /> |
|||
</Button> |
|||
{/if} |
|||
</header> |
|||
|
|||
<Spacer large /> |
|||
|
|||
{#if shouldShowQueryConfig} |
|||
<section> |
|||
<div class="config"> |
|||
<Label extraSmall grey>Query Name</Label> |
|||
<Input thin bind:value={query.name} /> |
|||
|
|||
<Spacer medium /> |
|||
|
|||
<IntegrationQueryEditor |
|||
{query} |
|||
schema={config[query.queryVerb][query.queryType]} |
|||
bind:parameters /> |
|||
|
|||
<Spacer medium /> |
|||
|
|||
<div class="viewer-controls"> |
|||
<Button |
|||
wide |
|||
thin |
|||
blue |
|||
disabled={data.length === 0} |
|||
on:click={saveQuery}> |
|||
Save |
|||
</Button> |
|||
<Button wide thin primary on:click={previewQuery}>Run</Button> |
|||
</div> |
|||
|
|||
<section class="viewer"> |
|||
{#if data} |
|||
<Switcher headings={PREVIEW_HEADINGS} bind:value={tab}> |
|||
{#if tab === 'JSON'} |
|||
<pre class="preview">{JSON.stringify(data[0], undefined, 2)}</pre> |
|||
{:else if tab === 'PREVIEW'} |
|||
<ExternalDataSourceTable {query} {data} /> |
|||
{:else if tab === 'SCHEMA'} |
|||
{#each fields as field, idx} |
|||
<div class="field"> |
|||
<Input thin type={'text'} bind:value={field.name} /> |
|||
<Select secondary thin bind:value={field.type}> |
|||
<option value={''}>Select an option</option> |
|||
<option value={'STRING'}>Text</option> |
|||
<option value={'NUMBER'}>Number</option> |
|||
<option value={'BOOLEAN'}>Boolean</option> |
|||
<option value={'DATETIME'}>Datetime</option> |
|||
</Select> |
|||
<i |
|||
class="ri-close-circle-line delete" |
|||
on:click={() => deleteField(idx)} /> |
|||
</div> |
|||
{/each} |
|||
<Button thin secondary on:click={newField}>Add Field</Button> |
|||
{/if} |
|||
</Switcher> |
|||
{/if} |
|||
</section> |
|||
</div> |
|||
</section> |
|||
{/if} |
|||
|
|||
<style> |
|||
.field { |
|||
display: grid; |
|||
grid-gap: 10px; |
|||
grid-template-columns: 1fr 1fr 50px; |
|||
margin-bottom: var(--spacing-m); |
|||
} |
|||
|
|||
a { |
|||
font-size: var(--font-size-s); |
|||
} |
|||
|
|||
.config { |
|||
margin-bottom: var(--spacing-s); |
|||
} |
|||
|
|||
.delete { |
|||
align-self: center; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.preview { |
|||
width: 800px; |
|||
height: 100%; |
|||
overflow-y: auto; |
|||
overflow-wrap: break-word; |
|||
white-space: pre-wrap; |
|||
} |
|||
|
|||
header { |
|||
display: flex; |
|||
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 { |
|||
display: grid; |
|||
grid-gap: var(--spacing-m); |
|||
grid-auto-flow: column; |
|||
direction: rtl; |
|||
grid-template-columns: 10% 10% 1fr; |
|||
margin-bottom: var(--spacing-m); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,10 @@ |
|||
import CodeMirror from "codemirror" |
|||
import "codemirror/lib/codemirror.css" |
|||
import "codemirror/theme/tomorrow-night-eighties.css" |
|||
import "codemirror/theme/neo.css" |
|||
import "codemirror/mode/sql/sql" |
|||
import "codemirror/mode/css/css" |
|||
import "codemirror/mode/handlebars/handlebars" |
|||
import "codemirror/mode/javascript/javascript" |
|||
|
|||
export default CodeMirror |
|||
@ -0,0 +1,52 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { TextArea, Label, Input, Heading, Spacer } from "@budibase/bbui" |
|||
import Editor from "./QueryEditor.svelte" |
|||
import ParameterBuilder from "./QueryParameterBuilder.svelte" |
|||
import FieldsBuilder from "./QueryFieldsBuilder.svelte" |
|||
|
|||
const QueryTypes = { |
|||
SQL: "sql", |
|||
JSON: "json", |
|||
FIELDS: "fields", |
|||
} |
|||
|
|||
export let query |
|||
export let schema |
|||
export let editable = true |
|||
|
|||
function updateQuery({ detail }) { |
|||
query.fields[schema.type] = detail.value |
|||
} |
|||
</script> |
|||
|
|||
{#if editable} |
|||
<ParameterBuilder bind:parameters={query.parameters} bindable={false} /> |
|||
<Spacer large /> |
|||
{/if} |
|||
|
|||
<Heading extraSmall black>Query</Heading> |
|||
<Spacer medium /> |
|||
|
|||
{#if schema} |
|||
{#key query._id} |
|||
{#if schema.type === QueryTypes.SQL} |
|||
<Editor |
|||
label="Query" |
|||
mode="sql" |
|||
on:change={updateQuery} |
|||
readOnly={!editable} |
|||
value={query.fields.sql} /> |
|||
{:else if schema.type === QueryTypes.JSON} |
|||
<Spacer large /> |
|||
<Editor |
|||
label="Query" |
|||
mode="json" |
|||
on:change={updateQuery} |
|||
readOnly={!editable} |
|||
value={query.fields.json} /> |
|||
{:else if schema.type === QueryTypes.FIELDS} |
|||
<FieldsBuilder bind:fields={query.fields} {schema} {editable} /> |
|||
{/if} |
|||
{/key} |
|||
{/if} |
|||
@ -0,0 +1,168 @@ |
|||
<script> |
|||
import { |
|||
Button, |
|||
TextButton, |
|||
Body, |
|||
DropdownMenu, |
|||
ModalContent, |
|||
} from "@budibase/bbui" |
|||
import { AddIcon, ArrowDownIcon } from "components/common/Icons/" |
|||
import actionTypes from "./actions" |
|||
import { createEventDispatcher } from "svelte" |
|||
import { automationStore } from "builderStore" |
|||
|
|||
const EVENT_TYPE_KEY = "##eventHandlerType" |
|||
|
|||
export let event |
|||
|
|||
let addActionButton |
|||
let addActionDropdown |
|||
let selectedAction |
|||
|
|||
$: actions = event || [] |
|||
$: selectedActionComponent = |
|||
selectedAction && |
|||
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_KEY]).component |
|||
|
|||
const deleteAction = index => { |
|||
actions.splice(index, 1) |
|||
actions = actions |
|||
} |
|||
|
|||
const addAction = actionType => () => { |
|||
const newAction = { |
|||
parameters: {}, |
|||
[EVENT_TYPE_KEY]: actionType.name, |
|||
} |
|||
actions.push(newAction) |
|||
selectedAction = newAction |
|||
actions = actions |
|||
addActionDropdown.hide() |
|||
} |
|||
|
|||
const selectAction = action => () => { |
|||
selectedAction = action |
|||
} |
|||
</script> |
|||
|
|||
<div class="actions-container"> |
|||
<div class="actions-list"> |
|||
<div> |
|||
<div bind:this={addActionButton}> |
|||
<TextButton text small blue on:click={addActionDropdown.show}> |
|||
<div style="height: 20px; width: 20px;"> |
|||
<AddIcon /> |
|||
</div> |
|||
Add Action |
|||
</TextButton> |
|||
</div> |
|||
<DropdownMenu |
|||
bind:this={addActionDropdown} |
|||
anchor={addActionButton} |
|||
align="right"> |
|||
<div class="available-actions-container"> |
|||
{#each actionTypes as actionType} |
|||
<div class="available-action" on:click={addAction(actionType)}> |
|||
<span>{actionType.name}</span> |
|||
</div> |
|||
{/each} |
|||
</div> |
|||
</DropdownMenu> |
|||
</div> |
|||
|
|||
{#if actions && actions.length > 0} |
|||
{#each actions as action, index} |
|||
<div class="action-container"> |
|||
<div class="action-header" on:click={selectAction(action)}> |
|||
<span class:selected={action === selectedAction}> |
|||
{index + 1}. |
|||
{action[EVENT_TYPE_KEY]} |
|||
</span> |
|||
</div> |
|||
<i |
|||
class="ri-close-fill" |
|||
style="margin-left: var(--spacing-m);" |
|||
on:click={() => deleteAction(index)} /> |
|||
</div> |
|||
{/each} |
|||
{/if} |
|||
</div> |
|||
<div class="action-config"> |
|||
{#if selectedAction} |
|||
<div class="selected-action-container"> |
|||
<svelte:component |
|||
this={selectedActionComponent} |
|||
parameters={selectedAction.parameters} /> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.action-header { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
margin-top: var(--spacing-m); |
|||
} |
|||
|
|||
.action-header > span { |
|||
margin-bottom: var(--spacing-m); |
|||
font-size: var(--font-size-s); |
|||
} |
|||
|
|||
.action-header > span:hover, |
|||
.selected { |
|||
cursor: pointer; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.actions-list { |
|||
border: var(--border-light); |
|||
padding: var(--spacing-s); |
|||
} |
|||
|
|||
.available-action { |
|||
padding: var(--spacing-s); |
|||
font-size: var(--font-size-m); |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.available-action:hover { |
|||
background: var(--grey-2); |
|||
} |
|||
|
|||
.actions-container { |
|||
height: 40vh; |
|||
display: grid; |
|||
grid-gap: var(--spacing-m); |
|||
grid-template-columns: 15% 1fr; |
|||
grid-auto-flow: column; |
|||
min-height: 0; |
|||
padding-top: 0; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.action-container { |
|||
border: var(--border-light); |
|||
border-width: 1px 0 0 0; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.selected-action-container { |
|||
padding-bottom: var(--spacing-s); |
|||
padding-top: var(--spacing-s); |
|||
} |
|||
|
|||
a { |
|||
flex: 1; |
|||
color: var(--grey-5); |
|||
font-size: var(--font-size-s); |
|||
text-decoration: none; |
|||
} |
|||
|
|||
a:hover { |
|||
color: var(--blue); |
|||
} |
|||
</style> |
|||
@ -1,17 +1,76 @@ |
|||
<script> |
|||
import { Button, Modal } from "@budibase/bbui" |
|||
import EventEditorModal from "./EventEditorModal.svelte" |
|||
import { createEventDispatcher } from "svelte" |
|||
import { store } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import EventEditor from "./EventEditor.svelte" |
|||
import BottomDrawer from "components/common/BottomDrawer.svelte" |
|||
import { automationStore } from "builderStore" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let value |
|||
export let name |
|||
|
|||
let modal |
|||
let drawerVisible |
|||
|
|||
function showDrawer() { |
|||
drawerVisible = true |
|||
} |
|||
|
|||
const saveEventData = async () => { |
|||
// any automations that need created from event triggers |
|||
const automationsToCreate = value.filter( |
|||
action => action["##eventHandlerType"] === "Trigger Automation" |
|||
) |
|||
automationsToCreate.forEach(action => createAutomation(action.parameters)) |
|||
|
|||
dispatch("change", value) |
|||
notifier.success("Component actions saved.") |
|||
} |
|||
|
|||
// called by the parent modal when actions are saved |
|||
const createAutomation = async parameters => { |
|||
if (parameters.automationId || !parameters.newAutomationName) return |
|||
|
|||
await automationStore.actions.create({ name: parameters.newAutomationName }) |
|||
|
|||
const appActionDefinition = $automationStore.blockDefinitions.TRIGGER.APP |
|||
|
|||
const newBlock = $automationStore.selectedAutomation.constructBlock( |
|||
"TRIGGER", |
|||
"APP", |
|||
appActionDefinition |
|||
) |
|||
|
|||
newBlock.inputs = { |
|||
fields: Object.entries(parameters.fields).reduce( |
|||
(fields, [key, value]) => { |
|||
fields[key] = value.type |
|||
return fields |
|||
}, |
|||
{} |
|||
), |
|||
} |
|||
|
|||
automationStore.actions.addBlockToAutomation(newBlock) |
|||
|
|||
await automationStore.actions.save($automationStore.selectedAutomation) |
|||
|
|||
parameters.automationId = $automationStore.selectedAutomation.automation._id |
|||
delete parameters.newAutomationName |
|||
} |
|||
</script> |
|||
|
|||
<Button secondary small on:click={modal.show}>Define Actions</Button> |
|||
<Button secondary small on:click={showDrawer}>Define Actions</Button> |
|||
|
|||
<Modal bind:this={modal} width="600px"> |
|||
<EventEditorModal event={value} eventType={name} on:change /> |
|||
</Modal> |
|||
{#if drawerVisible} |
|||
<BottomDrawer title={'Actions'} onClose={() => (drawerVisible = false)}> |
|||
<heading slot="buttons"> |
|||
<Button thin blue on:click={saveEventData}>Save</Button> |
|||
</heading> |
|||
<div slot="body"> |
|||
<EventEditor event={value} eventType={name} /> |
|||
</div> |
|||
</BottomDrawer> |
|||
{/if} |
|||
|
|||
@ -0,0 +1,69 @@ |
|||
<script> |
|||
import { Select, Label, Spacer } from "@budibase/bbui" |
|||
import { store, backendUiStore, currentAsset } from "builderStore" |
|||
import fetchBindableProperties from "builderStore/fetchBindableProperties" |
|||
import ParameterBuilder from "../../../integration/QueryParameterBuilder.svelte" |
|||
|
|||
export let parameters |
|||
|
|||
$: datasource = $backendUiStore.datasources.find( |
|||
ds => ds._id === parameters.datasourceId |
|||
) |
|||
// TODO: binding needs to be centralised |
|||
$: bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.selectedComponentId, |
|||
components: $store.components, |
|||
screen: $currentAsset, |
|||
tables: $backendUiStore.tables, |
|||
queries: $backendUiStore.queries, |
|||
}).map(property => ({ |
|||
...property, |
|||
category: property.type === "instance" ? "Component" : "Table", |
|||
label: property.readableBinding, |
|||
path: property.readableBinding, |
|||
})) |
|||
|
|||
$: query = |
|||
parameters.queryId && |
|||
$backendUiStore.queries.find(query => query._id === parameters.queryId) |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<Label size="m" color="dark">Datasource</Label> |
|||
<Select thin secondary bind:value={parameters.datasourceId}> |
|||
<option value="" /> |
|||
{#each $backendUiStore.datasources as datasource} |
|||
<option value={datasource._id}>{datasource.name}</option> |
|||
{/each} |
|||
</Select> |
|||
|
|||
<Spacer medium /> |
|||
|
|||
{#if parameters.datasourceId} |
|||
<Label size="m" color="dark">Query</Label> |
|||
<Select thin secondary bind:value={parameters.queryId}> |
|||
<option value="" /> |
|||
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query} |
|||
<option value={query._id}>{query.name}</option> |
|||
{/each} |
|||
</Select> |
|||
{/if} |
|||
|
|||
<Spacer medium /> |
|||
|
|||
{#if query?.parameters?.length > 0} |
|||
<ParameterBuilder |
|||
bind:customParams={parameters.queryParams} |
|||
parameters={query.parameters} |
|||
bindings={bindableProperties} /> |
|||
{#if query.fields.sql} |
|||
<pre>{query.fields.queryString}</pre> |
|||
{/if} |
|||
{/if} |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
padding: var(--spacing-m); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,42 @@ |
|||
<script> |
|||
import { params } from "@sveltech/routify" |
|||
import { backendUiStore } from "builderStore" |
|||
import { Switcher } from "@budibase/bbui" |
|||
import QueryInterface from "components/integration/QueryViewer.svelte" |
|||
|
|||
let query |
|||
|
|||
async function fetchQueryConfig() { |
|||
try { |
|||
const response = await api.get(`/api/integrations/${datasource.source}`) |
|||
const json = await response.json() |
|||
config = json.query |
|||
} catch (err) { |
|||
notifier.danger("Error fetching datasource configuration options.") |
|||
console.error(err) |
|||
} |
|||
} |
|||
|
|||
$: selectedQuery = $backendUiStore.queries.find( |
|||
query => query._id === $backendUiStore.selectedQueryId |
|||
) || { |
|||
datasourceId: $params.selectedDatasource, |
|||
name: "New Query", |
|||
parameters: [], |
|||
fields: {}, |
|||
} |
|||
</script> |
|||
|
|||
<section> |
|||
{#if $backendUiStore.selectedDatabase._id && selectedQuery} |
|||
<QueryInterface query={selectedQuery} /> |
|||
{/if} |
|||
</section> |
|||
|
|||
<style> |
|||
section { |
|||
background: var(--background); |
|||
padding: var(--spacing-xl); |
|||
border-radius: var(--border-radius-m); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,15 @@ |
|||
<script> |
|||
import { params } from "@sveltech/routify" |
|||
import { backendUiStore } from "builderStore" |
|||
|
|||
if ($params.selectedDatasourceId) { |
|||
const datasource = $backendUiStore.datasources.find( |
|||
m => m._id === $params.selectedDatasource |
|||
) |
|||
if (datasource) { |
|||
backendUiStore.actions.datasources.select(datasource) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<slot /> |
|||
@ -0,0 +1,44 @@ |
|||
<script> |
|||
import { goto } from "@sveltech/routify" |
|||
import { Button, Spacer, Icon, TextButton } from "@budibase/bbui" |
|||
import { backendUiStore } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte" |
|||
|
|||
$: datasource = $backendUiStore.datasources.find( |
|||
ds => ds._id === $backendUiStore.selectedDatasourceId |
|||
) |
|||
|
|||
async function saveDatasource() { |
|||
// Create datasource |
|||
await backendUiStore.actions.datasources.save(datasource) |
|||
notifier.success(`Datasource ${name} saved successfully.`) |
|||
} |
|||
</script> |
|||
|
|||
{#if datasource} |
|||
<TextButton text small on:click={() => $goto('../new')}> |
|||
<Icon name="filter" /> |
|||
Create Query |
|||
</TextButton> |
|||
<section> |
|||
<h4>{datasource.name}: Configuration</h4> |
|||
<IntegrationConfigForm integration={datasource.config} /> |
|||
<Spacer medium /> |
|||
<footer> |
|||
<Button blue wide on:click={saveDatasource}>Save</Button> |
|||
</footer> |
|||
</section> |
|||
{/if} |
|||
|
|||
<style> |
|||
h4 { |
|||
margin-top: var(--spacing-xl); |
|||
margin-bottom: var(--spacing-s); |
|||
} |
|||
section { |
|||
background: var(--background); |
|||
border-radius: var(--border-radius-m); |
|||
padding: var(--spacing-xl); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,18 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { goto, leftover } from "@sveltech/routify" |
|||
import { onMount } from "svelte" |
|||
|
|||
onMount(async () => { |
|||
// navigate to first datasource in list, if not already selected |
|||
if ( |
|||
!$leftover && |
|||
$backendUiStore.datasources.length > 0 && |
|||
!$backendUiStore.selectedDatasourceId |
|||
) { |
|||
$goto(`./${$backendUiStore.datasources[0]._id}`) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<slot /> |
|||
@ -0,0 +1,14 @@ |
|||
import API from "./api" |
|||
|
|||
/** |
|||
* Executes a query against an external data connector. |
|||
*/ |
|||
export const executeQuery = async ({ queryId, parameters }) => { |
|||
const response = await API.post({ |
|||
url: `/api/queries/${queryId}`, |
|||
body: { |
|||
parameters, |
|||
}, |
|||
}) |
|||
return response |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
const CouchDB = require("../../db") |
|||
const bcrypt = require("../../utilities/bcrypt") |
|||
const { |
|||
generateDatasourceID, |
|||
getDatasourceParams, |
|||
getQueryParams, |
|||
} = require("../../db/utils") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
const database = new CouchDB(ctx.user.appId) |
|||
const datasources = ( |
|||
await database.allDocs( |
|||
getDatasourceParams(null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
).rows.map(row => row.doc) |
|||
ctx.body = datasources |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
|
|||
const datasource = { |
|||
_id: generateDatasourceID(), |
|||
type: "datasource", |
|||
...ctx.request.body, |
|||
} |
|||
|
|||
try { |
|||
const response = await db.post(datasource) |
|||
datasource._rev = response.rev |
|||
|
|||
ctx.status = 200 |
|||
ctx.message = "Datasource saved successfully." |
|||
ctx.body = datasource |
|||
} catch (err) { |
|||
ctx.throw(err.status, err) |
|||
} |
|||
} |
|||
|
|||
exports.update = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
const user = ctx.request.body |
|||
const dbUser = await db.get(ctx.request.body._id) |
|||
if (user.password) { |
|||
user.password = await bcrypt.hash(user.password) |
|||
} else { |
|||
delete user.password |
|||
} |
|||
const newData = { ...dbUser, ...user } |
|||
|
|||
const response = await db.put(newData) |
|||
user._rev = response.rev |
|||
|
|||
ctx.status = 200 |
|||
ctx.message = `User ${ctx.request.body.email} updated successfully.` |
|||
ctx.body = response |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
|
|||
// Delete all queries for the datasource
|
|||
const rows = await db.allDocs(getQueryParams(ctx.params.datasourceId, null)) |
|||
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true }))) |
|||
|
|||
// delete the datasource
|
|||
await db.remove(ctx.params.datasourceId, ctx.params.revId) |
|||
|
|||
ctx.message = `Datasource deleted.` |
|||
ctx.status = 200 |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
const database = new CouchDB(ctx.user.appId) |
|||
const datasource = await database.get(ctx.params.datasourceId) |
|||
ctx.body = datasource |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
const { definitions } = require("../../integrations") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
// TODO: fetch these from a github repo etc
|
|||
console.log(definitions) |
|||
ctx.status = 200 |
|||
ctx.body = definitions |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
ctx.status = 200 |
|||
ctx.body = definitions[ctx.params.type] |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
const handlebars = require("handlebars") |
|||
const CouchDB = require("../../db") |
|||
const { generateQueryID, getQueryParams } = require("../../db/utils") |
|||
const { integrations } = require("../../integrations") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
|
|||
const body = await db.allDocs( |
|||
getQueryParams(null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
ctx.body = body.rows.map(row => row.doc) |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
const query = ctx.request.body |
|||
|
|||
if (!query._id) { |
|||
query._id = generateQueryID(query.datasourceId) |
|||
} |
|||
|
|||
const response = await db.put(query) |
|||
query._rev = response.rev |
|||
|
|||
ctx.body = query |
|||
ctx.message = `Query ${query.name} saved successfully.` |
|||
} |
|||
|
|||
function enrichQueryFields(fields, parameters) { |
|||
const enrichedQuery = {} |
|||
|
|||
// enrich the fields with dynamic parameters
|
|||
for (let key in fields) { |
|||
const template = handlebars.compile(fields[key]) |
|||
enrichedQuery[key] = template(parameters) |
|||
} |
|||
|
|||
if (enrichedQuery.json || enrichedQuery.customData) { |
|||
enrichedQuery.json = JSON.parse( |
|||
enrichedQuery.json || enrichedQuery.customData |
|||
) |
|||
delete enrichedQuery.customData |
|||
} |
|||
|
|||
return enrichedQuery |
|||
} |
|||
|
|||
exports.preview = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
|
|||
const datasource = await db.get(ctx.request.body.datasourceId) |
|||
|
|||
const Integration = integrations[datasource.source] |
|||
|
|||
if (!Integration) { |
|||
ctx.throw(400, "Integration type does not exist.") |
|||
return |
|||
} |
|||
|
|||
const { fields, parameters, queryVerb } = ctx.request.body |
|||
|
|||
const enrichedQuery = enrichQueryFields(fields, parameters) |
|||
|
|||
ctx.body = await new Integration(datasource.config)[queryVerb](enrichedQuery) |
|||
} |
|||
|
|||
exports.execute = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
|
|||
const query = await db.get(ctx.params.queryId) |
|||
const datasource = await db.get(query.datasourceId) |
|||
|
|||
const Integration = integrations[datasource.source] |
|||
|
|||
if (!Integration) { |
|||
ctx.throw(400, "Integration type does not exist.") |
|||
return |
|||
} |
|||
|
|||
const enrichedQuery = enrichQueryFields( |
|||
query.fields, |
|||
ctx.request.body.parameters |
|||
) |
|||
|
|||
// call the relevant CRUD method on the integration class
|
|||
const response = await new Integration(datasource.config)[query.queryVerb]( |
|||
enrichedQuery |
|||
) |
|||
|
|||
ctx.body = response |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
await db.remove(ctx.params.queryId, ctx.params.revId) |
|||
ctx.message = `Query deleted.` |
|||
ctx.status = 200 |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
const Router = require("@koa/router") |
|||
const datasourceController = require("../controllers/datasource") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { |
|||
BUILDER, |
|||
PermissionLevels, |
|||
PermissionTypes, |
|||
} = require("../../utilities/security/permissions") |
|||
|
|||
const router = Router() |
|||
|
|||
router |
|||
.get("/api/datasources", authorized(BUILDER), datasourceController.fetch) |
|||
.get( |
|||
"/api/datasources/:id", |
|||
authorized(PermissionTypes.TABLE, PermissionLevels.READ), |
|||
datasourceController.find |
|||
) |
|||
.post("/api/datasources", authorized(BUILDER), datasourceController.save) |
|||
.delete( |
|||
"/api/datasources/:datasourceId/:revId", |
|||
authorized(BUILDER), |
|||
datasourceController.destroy |
|||
) |
|||
|
|||
module.exports = router |
|||
@ -0,0 +1,12 @@ |
|||
const Router = require("@koa/router") |
|||
const controller = require("../controllers/integration") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { BUILDER } = require("../../utilities/security/permissions") |
|||
|
|||
const router = Router() |
|||
|
|||
router |
|||
.get("/api/integrations", authorized(BUILDER), controller.fetch) |
|||
.get("/api/integrations/:type", authorized(BUILDER), controller.find) |
|||
|
|||
module.exports = router |
|||
@ -0,0 +1,74 @@ |
|||
const Router = require("@koa/router") |
|||
const queryController = require("../controllers/query") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { BUILDER } = require("../../utilities/security/permissions") |
|||
const Joi = require("joi") |
|||
const { |
|||
PermissionLevels, |
|||
PermissionTypes, |
|||
} = require("../../utilities/security/permissions") |
|||
const joiValidator = require("../../middleware/joi-validator") |
|||
|
|||
const router = Router() |
|||
|
|||
const QueryVerb = { |
|||
Create: "create", |
|||
Read: "read", |
|||
Update: "update", |
|||
Delete: "delete", |
|||
} |
|||
|
|||
function generateQueryValidation() { |
|||
// prettier-ignore
|
|||
return joiValidator.body(Joi.object({ |
|||
_id: Joi.string(), |
|||
_rev: Joi.string(), |
|||
name: Joi.string().required(), |
|||
fields: Joi.object().required(), |
|||
datasourceId: Joi.string().required(), |
|||
parameters: Joi.array().items(Joi.object({ |
|||
name: Joi.string(), |
|||
default: Joi.string() |
|||
})), |
|||
queryVerb: Joi.string().allow(...Object.values(QueryVerb)).required(), |
|||
queryType: Joi.string().required(), |
|||
schema: Joi.object({}).required().unknown(true) |
|||
})) |
|||
} |
|||
|
|||
function generateQueryPreviewValidation() { |
|||
// prettier-ignore
|
|||
return joiValidator.body(Joi.object({ |
|||
fields: Joi.object().required(), |
|||
queryVerb: Joi.string().allow(...Object.values(QueryVerb)).required(), |
|||
datasourceId: Joi.string().required(), |
|||
parameters: Joi.object({}).required().unknown(true) |
|||
})) |
|||
} |
|||
|
|||
router |
|||
.get("/api/queries", authorized(BUILDER), queryController.fetch) |
|||
.post( |
|||
"/api/queries", |
|||
authorized(BUILDER), |
|||
generateQueryValidation(), |
|||
queryController.save |
|||
) |
|||
.post( |
|||
"/api/queries/preview", |
|||
authorized(BUILDER), |
|||
generateQueryPreviewValidation(), |
|||
queryController.preview |
|||
) |
|||
.post( |
|||
"/api/queries/:queryId", |
|||
authorized(PermissionTypes.QUERY, PermissionLevels.WRITE), |
|||
queryController.execute |
|||
) |
|||
.delete( |
|||
"/api/queries/:queryId/:revId", |
|||
authorized(BUILDER), |
|||
queryController.destroy |
|||
) |
|||
|
|||
module.exports = router |
|||
@ -0,0 +1,149 @@ |
|||
const { |
|||
supertest, |
|||
createApplication, |
|||
defaultHeaders, |
|||
builderEndpointShouldBlockNormalUsers, |
|||
getDocument, |
|||
insertDocument |
|||
} = require("./couchTestUtils") |
|||
let { generateDatasourceID, generateQueryID } = require("../../../db/utils") |
|||
|
|||
const DATASOURCE_ID = generateDatasourceID() |
|||
const TEST_DATASOURCE = { |
|||
_id: DATASOURCE_ID, |
|||
type: "datasource", |
|||
name: "Test", |
|||
source: "POSTGRES", |
|||
config: {}, |
|||
type: "datasource", |
|||
} |
|||
|
|||
const TEST_QUERY = { |
|||
_id: generateQueryID(DATASOURCE_ID), |
|||
datasourceId: DATASOURCE_ID, |
|||
name:"New Query", |
|||
parameters:[], |
|||
fields:{}, |
|||
schema:{}, |
|||
queryVerb:"read", |
|||
queryType:"Table", |
|||
} |
|||
|
|||
describe("/datasources", () => { |
|||
let request |
|||
let server |
|||
let app |
|||
let appId |
|||
let datasource |
|||
|
|||
beforeAll(async () => { |
|||
({ request, server } = await supertest()) |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
server.close() |
|||
}) |
|||
|
|||
beforeEach(async () => { |
|||
app = await createApplication(request) |
|||
appId = app.instance._id |
|||
}); |
|||
|
|||
async function createDatasource() { |
|||
return await insertDocument(appId, TEST_DATASOURCE) |
|||
} |
|||
|
|||
async function createQuery() { |
|||
return await insertDocument(appId, TEST_QUERY) |
|||
} |
|||
|
|||
describe("create", () => { |
|||
it("should create a new datasource", async () => { |
|||
const res = await request |
|||
.post(`/api/datasources`) |
|||
.send(TEST_DATASOURCE) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.res.statusMessage).toEqual("Datasource saved successfully."); |
|||
expect(res.body.name).toEqual("Test"); |
|||
}) |
|||
}); |
|||
|
|||
describe("fetch", () => { |
|||
let datasource |
|||
|
|||
beforeEach(async () => { |
|||
datasource = await createDatasource() |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
delete datasource._rev |
|||
}); |
|||
|
|||
it("returns all the datasources from the server", async () => { |
|||
const res = await request |
|||
.get(`/api/datasources`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
const datasources = res.body; |
|||
expect(datasources).toEqual([ |
|||
{ |
|||
"_rev": datasources[0]._rev, |
|||
...TEST_DATASOURCE |
|||
} |
|||
]); |
|||
}) |
|||
|
|||
it("should apply authorization to endpoint", async () => { |
|||
await builderEndpointShouldBlockNormalUsers({ |
|||
request, |
|||
method: "GET", |
|||
url: `/api/datasources`, |
|||
appId: appId, |
|||
}) |
|||
}) |
|||
}); |
|||
|
|||
describe("destroy", () => { |
|||
let datasource; |
|||
|
|||
beforeEach(async () => { |
|||
datasource = await createDatasource() |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
delete datasource._rev |
|||
}); |
|||
|
|||
it("deletes queries for the datasource after deletion and returns a success message", async () => { |
|||
await createQuery(datasource.id) |
|||
|
|||
await request |
|||
.delete(`/api/datasources/${datasource.id}/${datasource.rev}`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect(200) |
|||
|
|||
const res = await request |
|||
.get(`/api/datasources`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.body).toEqual([]) |
|||
}) |
|||
|
|||
it("should apply authorization to endpoint", async () => { |
|||
await builderEndpointShouldBlockNormalUsers({ |
|||
request, |
|||
method: "DELETE", |
|||
url: `/api/datasources/${datasource._id}/${datasource._rev}`, |
|||
appId: appId, |
|||
}) |
|||
}) |
|||
|
|||
}); |
|||
}); |
|||
@ -0,0 +1,153 @@ |
|||
const { |
|||
supertest, |
|||
createApplication, |
|||
defaultHeaders, |
|||
builderEndpointShouldBlockNormalUsers, |
|||
getDocument, |
|||
insertDocument |
|||
} = require("./couchTestUtils") |
|||
let { generateDatasourceID, generateQueryID } = require("../../../db/utils") |
|||
|
|||
const DATASOURCE_ID = generateDatasourceID() |
|||
const TEST_DATASOURCE = { |
|||
_id: DATASOURCE_ID, |
|||
type: "datasource", |
|||
name: "Test", |
|||
source: "POSTGRES", |
|||
config: {}, |
|||
type: "datasource", |
|||
} |
|||
|
|||
const TEST_QUERY = { |
|||
_id: generateQueryID(DATASOURCE_ID), |
|||
datasourceId: DATASOURCE_ID, |
|||
name:"New Query", |
|||
parameters:[], |
|||
fields:{}, |
|||
schema:{}, |
|||
queryVerb:"read", |
|||
queryType:"Table", |
|||
} |
|||
|
|||
describe("/queries", () => { |
|||
let request |
|||
let server |
|||
let app |
|||
let appId |
|||
let datasource |
|||
let query |
|||
|
|||
beforeAll(async () => { |
|||
({ request, server } = await supertest()) |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
server.close() |
|||
}) |
|||
|
|||
beforeEach(async () => { |
|||
app = await createApplication(request) |
|||
appId = app.instance._id |
|||
}); |
|||
|
|||
async function createDatasource() { |
|||
return await insertDocument(appId, TEST_DATASOURCE) |
|||
} |
|||
|
|||
async function createQuery() { |
|||
return await insertDocument(appId, TEST_QUERY) |
|||
} |
|||
|
|||
describe("create", () => { |
|||
it("should create a new query", async () => { |
|||
const res = await request |
|||
.post(`/api/queries`) |
|||
.send(TEST_QUERY) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.res.statusMessage).toEqual(`Query ${TEST_QUERY.name} saved successfully.`); |
|||
expect(res.body).toEqual({ |
|||
_rev: res.body._rev, |
|||
...TEST_QUERY, |
|||
}); |
|||
}) |
|||
}); |
|||
|
|||
describe("fetch", () => { |
|||
let datasource |
|||
|
|||
beforeEach(async () => { |
|||
datasource = await createDatasource() |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
delete datasource._rev |
|||
}); |
|||
|
|||
it("returns all the queries from the server", async () => { |
|||
const query = await createQuery() |
|||
const res = await request |
|||
.get(`/api/queries`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
const queries = res.body; |
|||
expect(queries).toEqual([ |
|||
{ |
|||
"_rev": query.rev, |
|||
...TEST_QUERY |
|||
} |
|||
]); |
|||
}) |
|||
|
|||
it("should apply authorization to endpoint", async () => { |
|||
await builderEndpointShouldBlockNormalUsers({ |
|||
request, |
|||
method: "GET", |
|||
url: `/api/datasources`, |
|||
appId: appId, |
|||
}) |
|||
}) |
|||
}); |
|||
|
|||
describe("destroy", () => { |
|||
let datasource; |
|||
|
|||
beforeEach(async () => { |
|||
datasource = await createDatasource() |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
delete datasource._rev |
|||
}); |
|||
|
|||
it("deletes a query and returns a success message", async () => { |
|||
const query = await createQuery() |
|||
|
|||
await request |
|||
.delete(`/api/queries/${query.id}/${query.rev}`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect(200) |
|||
|
|||
const res = await request |
|||
.get(`/api/queries`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.body).toEqual([]) |
|||
}) |
|||
|
|||
it("should apply authorization to endpoint", async () => { |
|||
await builderEndpointShouldBlockNormalUsers({ |
|||
request, |
|||
method: "DELETE", |
|||
url: `/api/datasources/${datasource._id}/${datasource._rev}`, |
|||
appId: appId, |
|||
}) |
|||
}) |
|||
}); |
|||
}); |
|||
@ -0,0 +1,12 @@ |
|||
exports.QUERY_TYPES = { |
|||
SQL: "sql", |
|||
JSON: "json", |
|||
FIELDS: "fields", |
|||
} |
|||
|
|||
exports.FIELD_TYPES = { |
|||
STRING: "string", |
|||
NUMBER: "number", |
|||
PASSWORD: "password", |
|||
LIST: "list", |
|||
} |
|||
@ -0,0 +1,131 @@ |
|||
const Airtable = require("airtable") |
|||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|||
|
|||
const SCHEMA = { |
|||
docs: "https://airtable.com/api", |
|||
datasource: { |
|||
apiKey: { |
|||
type: FIELD_TYPES.STRING, |
|||
default: "enter api key", |
|||
required: true, |
|||
}, |
|||
base: { |
|||
type: FIELD_TYPES.STRING, |
|||
default: "mybase", |
|||
required: true, |
|||
}, |
|||
}, |
|||
query: { |
|||
create: { |
|||
"Airtable Record": { |
|||
type: QUERY_TYPES.FIELDS, |
|||
customisable: true, |
|||
fields: { |
|||
table: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
read: { |
|||
Table: { |
|||
type: QUERY_TYPES.FIELDS, |
|||
fields: { |
|||
table: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
view: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
update: { |
|||
Fields: { |
|||
type: QUERY_TYPES.FIELDS, |
|||
customisable: true, |
|||
fields: { |
|||
id: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
delete: { |
|||
"Airtable Ids": { |
|||
type: FIELD_TYPES.JSON, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class AirtableIntegration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.client = new Airtable(config).base(config.base) |
|||
} |
|||
|
|||
async create(query) { |
|||
const { table, json } = query |
|||
|
|||
try { |
|||
const records = await this.client(table).create([ |
|||
{ |
|||
fields: json, |
|||
}, |
|||
]) |
|||
return records |
|||
} catch (err) { |
|||
console.error("Error writing to airtable", err) |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
async read(query) { |
|||
try { |
|||
const records = await this.client(query.table) |
|||
.select({ maxRecords: query.numRecords || 10, view: query.view }) |
|||
.firstPage() |
|||
return records.map(({ fields }) => fields) |
|||
} catch (err) { |
|||
console.error("Error writing to airtable", err) |
|||
return [] |
|||
} |
|||
} |
|||
|
|||
async update(query) { |
|||
const { table, id, json } = query |
|||
|
|||
try { |
|||
const records = await this.client(table).update([ |
|||
{ |
|||
id, |
|||
fields: json, |
|||
}, |
|||
]) |
|||
return records |
|||
} catch (err) { |
|||
console.error("Error writing to airtable", err) |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
async delete(query) { |
|||
try { |
|||
const records = await this.client(query.table).destroy(query.ids) |
|||
return records |
|||
} catch (err) { |
|||
console.error("Error writing to airtable", err) |
|||
throw err |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: AirtableIntegration, |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
const PouchDB = require("pouchdb") |
|||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|||
|
|||
const SCHEMA = { |
|||
docs: "https://docs.couchdb.org/en/stable/", |
|||
datasource: { |
|||
url: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
default: "http://localhost:5984", |
|||
}, |
|||
database: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
query: { |
|||
create: { |
|||
"CouchDB DSL": { |
|||
type: QUERY_TYPES.JSON, |
|||
}, |
|||
}, |
|||
read: { |
|||
"CouchDB DSL": { |
|||
type: QUERY_TYPES.JSON, |
|||
}, |
|||
}, |
|||
update: { |
|||
"CouchDB Document": { |
|||
type: QUERY_TYPES.JSON, |
|||
}, |
|||
}, |
|||
delete: { |
|||
"Document ID": { |
|||
type: QUERY_TYPES.FIELDS, |
|||
fields: { |
|||
id: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class CouchDBIntegration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.client = new PouchDB(`${config.url}/${config.database}`) |
|||
} |
|||
|
|||
async create(query) { |
|||
try { |
|||
const result = await this.client.post(query.json) |
|||
return result |
|||
} catch (err) { |
|||
console.error("Error writing to couchDB", err) |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
async read(query) { |
|||
try { |
|||
const result = await this.client.allDocs({ |
|||
include_docs: true, |
|||
...query.json, |
|||
}) |
|||
return result.rows.map(row => row.doc) |
|||
} catch (err) { |
|||
console.error("Error querying couchDB", err) |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
async update(query) { |
|||
try { |
|||
const result = await this.client.put(query.json) |
|||
return result |
|||
} catch (err) { |
|||
console.error("Error updating couchDB document", err) |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
async delete(query) { |
|||
try { |
|||
const result = await this.client.remove(query.id) |
|||
return result |
|||
} catch (err) { |
|||
console.error("Error deleting couchDB document", err) |
|||
throw err |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: CouchDBIntegration, |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
const AWS = require("aws-sdk") |
|||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|||
|
|||
const SCHEMA = { |
|||
docs: "https://github.com/dabit3/dynamodb-documentclient-cheat-sheet", |
|||
datasource: { |
|||
region: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
default: "us-east-1", |
|||
}, |
|||
accessKeyId: { |
|||
type: FIELD_TYPES.PASSWORD, |
|||
required: true, |
|||
}, |
|||
secretKey: { |
|||
type: FIELD_TYPES.PASSWORD, |
|||
required: true, |
|||
}, |
|||
}, |
|||
query: { |
|||
create: { |
|||
DynamoConfig: { |
|||
type: QUERY_TYPES.FIELDS, |
|||
fields: { |
|||
table: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
customisable: true, |
|||
}, |
|||
}, |
|||
}, |
|||
read: { |
|||
DynamoConfig: { |
|||
type: QUERY_TYPES.FIELDS, |
|||
fields: { |
|||
table: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
index: { |
|||
type: FIELD_TYPES.STRING, |
|||
}, |
|||
customisable: true, |
|||
}, |
|||
}, |
|||
}, |
|||
update: { |
|||
DynamoConfig: { |
|||
type: QUERY_TYPES.FIELDS, |
|||
fields: { |
|||
table: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
customisable: true, |
|||
}, |
|||
}, |
|||
}, |
|||
delete: { |
|||
"Dynamo Partition Key": { |
|||
type: QUERY_TYPES.FIELDS, |
|||
fields: { |
|||
table: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
key: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class DynamoDBIntegration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.connect() |
|||
this.client = new AWS.DynamoDB.DocumentClient() |
|||
} |
|||
|
|||
async connect() { |
|||
AWS.config.update(this.config) |
|||
} |
|||
|
|||
async create(query) { |
|||
const response = await this.client.query({ |
|||
TableName: query.table, |
|||
Item: query.json, |
|||
}) |
|||
return response |
|||
} |
|||
|
|||
async read(query) { |
|||
const response = await this.client.query({ |
|||
TableName: query.Table, |
|||
...query.json, |
|||
}) |
|||
return response |
|||
} |
|||
|
|||
async update(query) { |
|||
const response = await this.client.query({ |
|||
TableName: query.Table, |
|||
...query.json, |
|||
}) |
|||
return response |
|||
} |
|||
|
|||
async delete(query) { |
|||
const response = await this.client.query({ |
|||
TableName: query.Table, |
|||
Key: { |
|||
id: query.key, |
|||
}, |
|||
}) |
|||
return response |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: DynamoDBIntegration, |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
const { Client } = require("@elastic/elasticsearch") |
|||
const { QUERY_TYPES, FIELD_TYPES } = require("./Integration") |
|||
|
|||
const SCHEMA = { |
|||
docs: |
|||
"https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html", |
|||
datasource: { |
|||
url: { |
|||
type: "string", |
|||
required: true, |
|||
default: "http://localhost:9200", |
|||
}, |
|||
}, |
|||
query: { |
|||
create: { |
|||
"ES Query DSL": { |
|||
type: QUERY_TYPES.FIELDS, |
|||
customisable: true, |
|||
fields: { |
|||
index: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
read: { |
|||
"ES Query DSL": { |
|||
type: QUERY_TYPES.FIELDS, |
|||
customisable: true, |
|||
fields: { |
|||
index: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
update: { |
|||
"ES Query DSL": { |
|||
type: QUERY_TYPES.FIELDS, |
|||
customisable: true, |
|||
fields: { |
|||
id: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
index: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
delete: { |
|||
"Document ID": { |
|||
type: QUERY_TYPES.FIELDS, |
|||
fields: { |
|||
index: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
id: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class ElasticSearchIntegration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.client = new Client({ node: config.url }) |
|||
} |
|||
|
|||
async create(query) { |
|||
const { index, json } = query |
|||
|
|||
try { |
|||
const result = await this.client.index({ |
|||
index, |
|||
body: json, |
|||
}) |
|||
return result.body |
|||
} catch (err) { |
|||
console.error("Error writing to elasticsearch", err) |
|||
throw err |
|||
} finally { |
|||
await this.client.close() |
|||
} |
|||
} |
|||
|
|||
async read(query) { |
|||
const { index, json } = query |
|||
try { |
|||
const result = await this.client.search({ |
|||
index: index, |
|||
body: json, |
|||
}) |
|||
return result.body.hits.hits.map(({ _source }) => _source) |
|||
} catch (err) { |
|||
console.error("Error querying elasticsearch", err) |
|||
throw err |
|||
} finally { |
|||
await this.client.close() |
|||
} |
|||
} |
|||
|
|||
async update(query) { |
|||
const { id, index, json } = query |
|||
try { |
|||
const result = await this.client.update({ |
|||
id, |
|||
index, |
|||
body: json, |
|||
}) |
|||
return result.body |
|||
} catch (err) { |
|||
console.error("Error querying elasticsearch", err) |
|||
throw err |
|||
} finally { |
|||
await this.client.close() |
|||
} |
|||
} |
|||
|
|||
async delete(query) { |
|||
try { |
|||
const result = await this.client.delete(query) |
|||
return result.body |
|||
} catch (err) { |
|||
console.error("Error deleting from elasticsearch", err) |
|||
throw err |
|||
} finally { |
|||
await this.client.close() |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: ElasticSearchIntegration, |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
const postgres = require("./postgres") |
|||
const dynamodb = require("./dynamodb") |
|||
const mongodb = require("./mongodb") |
|||
const elasticsearch = require("./elasticsearch") |
|||
const couchdb = require("./couchdb") |
|||
// const redis = require("./redis")
|
|||
const sqlServer = require("./microsoftSqlServer") |
|||
const s3 = require("./s3") |
|||
const airtable = require("./airtable") |
|||
|
|||
const DEFINITIONS = { |
|||
POSTGRES: postgres.schema, |
|||
DYNAMODB: dynamodb.schema, |
|||
MONGODB: mongodb.schema, |
|||
ELASTICSEARCH: elasticsearch.schema, |
|||
COUCHDB: couchdb.schema, |
|||
SQL_SERVER: sqlServer.schema, |
|||
S3: s3.schema, |
|||
AIRTABLE: airtable.schema, |
|||
} |
|||
|
|||
const INTEGRATIONS = { |
|||
POSTGRES: postgres.integration, |
|||
DYNAMODB: dynamodb.integration, |
|||
MONGODB: mongodb.integration, |
|||
ELASTICSEARCH: elasticsearch.integration, |
|||
COUCHDB: couchdb.integration, |
|||
S3: s3.integration, |
|||
SQL_SERVER: sqlServer.integration, |
|||
AIRTABLE: airtable.integration, |
|||
} |
|||
|
|||
module.exports = { |
|||
definitions: DEFINITIONS, |
|||
integrations: INTEGRATIONS, |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
const sqlServer = require("mssql") |
|||
const { FIELD_TYPES } = require("./Integration") |
|||
|
|||
const SCHEMA = { |
|||
docs: "https://github.com/tediousjs/node-mssql", |
|||
datasource: { |
|||
user: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
default: "localhost", |
|||
}, |
|||
password: { |
|||
type: FIELD_TYPES.PASSWORD, |
|||
required: true, |
|||
}, |
|||
server: { |
|||
type: FIELD_TYPES.STRING, |
|||
default: "localhost", |
|||
}, |
|||
database: { |
|||
type: FIELD_TYPES.STRING, |
|||
default: "root", |
|||
}, |
|||
}, |
|||
query: { |
|||
create: { |
|||
SQL: { |
|||
type: "sql", |
|||
}, |
|||
}, |
|||
read: { |
|||
SQL: { |
|||
type: "sql", |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class SqlServerIntegration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.client = sqlServer |
|||
} |
|||
|
|||
async connect() { |
|||
return await this.client.connect(this.config) |
|||
} |
|||
|
|||
async read(query) { |
|||
try { |
|||
await this.connect() |
|||
const response = await this.client.query(query.sql) |
|||
return response.recordset |
|||
} catch (err) { |
|||
console.error("Error querying MS SQL Server", err) |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
async create(query) { |
|||
try { |
|||
await this.connect() |
|||
const response = await this.client.query(query.sql) |
|||
return response.recordset |
|||
} catch (err) { |
|||
console.error("Error querying MS SQL Server", err) |
|||
throw err |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: SqlServerIntegration, |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
const { MongoClient } = require("mongodb") |
|||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|||
|
|||
const SCHEMA = { |
|||
docs: "https://github.com/mongodb/node-mongodb-native", |
|||
datasource: { |
|||
connectionString: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
default: "mongodb://localhost:27017", |
|||
}, |
|||
db: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
collection: { |
|||
type: FIELD_TYPES.STRING, |
|||
required: true, |
|||
}, |
|||
}, |
|||
query: { |
|||
create: { |
|||
JSON: { |
|||
type: QUERY_TYPES.JSON, |
|||
}, |
|||
}, |
|||
read: { |
|||
JSON: { |
|||
type: QUERY_TYPES.JSON, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class MongoIntegration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.client = new MongoClient(config.connectionString) |
|||
} |
|||
|
|||
async connect() { |
|||
return this.client.connect() |
|||
} |
|||
|
|||
async create(query) { |
|||
try { |
|||
await this.connect() |
|||
const db = this.client.db(this.config.db) |
|||
const collection = db.collection(this.config.collection) |
|||
const result = await collection.insertOne(query.json) |
|||
return result |
|||
} catch (err) { |
|||
console.error("Error writing to mongodb", err) |
|||
throw err |
|||
} finally { |
|||
await this.client.close() |
|||
} |
|||
} |
|||
|
|||
async read(query) { |
|||
try { |
|||
await this.connect() |
|||
const db = this.client.db(this.config.db) |
|||
const collection = db.collection(this.config.collection) |
|||
const result = await collection.find(query.json).toArray() |
|||
return result |
|||
} catch (err) { |
|||
console.error("Error querying mongodb", err) |
|||
throw err |
|||
} finally { |
|||
await this.client.close() |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: MongoIntegration, |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
const { Client } = require("pg") |
|||
|
|||
const SCHEMA = { |
|||
docs: "https://node-postgres.com", |
|||
datasource: { |
|||
host: { |
|||
type: "string", |
|||
default: "localhost", |
|||
required: true, |
|||
}, |
|||
port: { |
|||
type: "number", |
|||
required: true, |
|||
default: 5432, |
|||
}, |
|||
database: { |
|||
type: "string", |
|||
default: "postgres", |
|||
required: true, |
|||
}, |
|||
username: { |
|||
type: "string", |
|||
default: "root", |
|||
required: true, |
|||
}, |
|||
password: { |
|||
type: "password", |
|||
default: "root", |
|||
required: true, |
|||
}, |
|||
}, |
|||
query: { |
|||
create: { |
|||
SQL: { |
|||
type: "sql", |
|||
}, |
|||
}, |
|||
read: { |
|||
SQL: { |
|||
type: "sql", |
|||
}, |
|||
}, |
|||
update: { |
|||
SQL: { |
|||
type: "sql", |
|||
}, |
|||
}, |
|||
delete: { |
|||
SQL: { |
|||
type: "sql", |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class PostgresIntegration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.client = new Client(config) |
|||
this.connect() |
|||
} |
|||
|
|||
async connect() { |
|||
return this.client.connect() |
|||
} |
|||
|
|||
async create({ sql }) { |
|||
const response = await this.client.query(sql) |
|||
return response.rows |
|||
} |
|||
|
|||
async read({ sql }) { |
|||
const response = await this.client.query(sql) |
|||
return response.rows |
|||
} |
|||
|
|||
async update({ sql }) { |
|||
const response = await this.client.query(sql) |
|||
return response.rows |
|||
} |
|||
|
|||
async delete({ sql }) { |
|||
const response = await this.client.query(sql) |
|||
return response.rows |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: PostgresIntegration, |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
const AWS = require("aws-sdk") |
|||
|
|||
const SCHEMA = { |
|||
docs: "https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html", |
|||
datasource: { |
|||
region: { |
|||
type: "string", |
|||
required: true, |
|||
default: "us-east-1", |
|||
}, |
|||
accessKeyId: { |
|||
type: "password", |
|||
required: true, |
|||
}, |
|||
secretAccessKey: { |
|||
type: "password", |
|||
required: true, |
|||
}, |
|||
}, |
|||
query: { |
|||
read: { |
|||
Bucket: { |
|||
type: "fields", |
|||
fields: { |
|||
bucket: { |
|||
type: "string", |
|||
required: true, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
class S3Integration { |
|||
constructor(config) { |
|||
this.config = config |
|||
this.connect() |
|||
this.client = new AWS.S3() |
|||
} |
|||
|
|||
async connect() { |
|||
AWS.config.update(this.config) |
|||
} |
|||
|
|||
async read(query) { |
|||
const response = await this.client |
|||
.listObjects({ |
|||
Bucket: query.bucket, |
|||
}) |
|||
.promise() |
|||
return response.Contents |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
schema: SCHEMA, |
|||
integration: S3Integration, |
|||
} |
|||
Loading…
Reference in new issue