Merge branch 'master' of github.com:Budibase/budibase into feature/handlebars-migration
This commit is contained in:
commit
3dd985cfb6
|
@ -81,3 +81,6 @@ typings/
|
|||
|
||||
# Mac files
|
||||
.DS_Store
|
||||
|
||||
# Nova Editor
|
||||
.nova
|
26
README.md
26
README.md
|
@ -8,10 +8,10 @@
|
|||
</h1>
|
||||
|
||||
<h3 align="center">
|
||||
Build business apps 50x faster
|
||||
Build internal tools 50x faster on your own infrastructure
|
||||
</h3>
|
||||
<p align="center">
|
||||
Budibase is an open-source low-code platform that helps developers and IT professionals design, build, and ship business apps 50x faster.
|
||||
Budibase is an open-source low-code platform, helping developers and IT professionals build, automate, and ship internal tools 50x faster on their own infrastructure.
|
||||
</p>
|
||||
|
||||
<h3 align="center">
|
||||
|
@ -61,23 +61,31 @@ When other platforms chose the closed source route, we decided to go open source
|
|||
|
||||
- **Open source and extensable.** Budibase is open-source. The builder is licensed AGPL v3, the server is GPL v3, and the client is MPL. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
||||
|
||||
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, whether it’s a CSV, an external database (coming very soon), or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, mySQL, Airtable, Google Sheets, S3, DyanmoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
- **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new components](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
- **Automate processes, integrate with other tools, and connect to webhooks.** Save time by automating manual processes and workflows. From connecting to webhooks, to automating emails, simply tell Budibase what to do and let it work for you. You can easily [create new automations for Budibase here](https://github.com/Budibase/automations) or [request new integrations here](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
- **Cloud hosting (available) and self-hosting (coming soon).** Users will soon have the option to host with Budibase in AWS (available now) or self-host (coming very soon). From the very beginning, we wanted our users to have the option to self-host. We understand the importance of having full control over data. This is why we are working incredibly hard to offer an easy path to self-hosting. If you are interested in self-hosting, please [join the conversation and add your thoughts](https://github.com/Budibase/budibase/discussions/648).
|
||||
- **Cloud hosting and self-hosting.** Users can self-host (see below), or host their apps with Budibase. Currently, our cloud hosting offering is limited to the free tier but we aim to change this in the future. For heavy usage, we advise users to self-host.
|
||||
|
||||
|
||||
## 🤖 Self-hosting
|
||||
|
||||
<p align="center">
|
||||
<img src="https://i.imgur.com/Z52cEvT.png?1" />
|
||||
</p>
|
||||
|
||||
Budibase wants to make sure anyone can use the tools we develop and we know a lot of people need to be able to host the apps they make on their own systems - that is why we've decided to try and make self hosting as easy as possible!
|
||||
|
||||
Currently, you can host your apps using Docker. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting).
|
||||
|
||||
|
||||
## ⌛ Status
|
||||
- [x] Alpha: We are demoing Budibase to users and receiving feedback
|
||||
- [x] Private Beta: We are testing Budibase with a closed set of customers
|
||||
- [x] Public Beta: Anyone can [sign-up and use Budibase](https://portal.budi.live/signup) but it's not production ready. We cannot ensure backwards compatibility
|
||||
- [ ] Official Launch: Production-ready
|
||||
|
||||
|
||||
We are currently in Public Beta. Until our official launch, we cannot ensure backwards compatibility for your Budibase applications between versions. Issues may arise when trying to edit apps created with old versions of the Budibase builder.
|
||||
- [x] Public Beta: Anyone can [sign-up and use Budibase](https://portal.budi.live/signup).
|
||||
- [ ] Official Launch
|
||||
|
||||
Watch "releases" of this repo to get notified of major updates, and give the star button a click whilst you're there.
|
||||
|
||||
|
|
|
@ -6,8 +6,11 @@ services:
|
|||
ports:
|
||||
- "${APP_PORT}:4002"
|
||||
environment:
|
||||
SELF_HOSTED: 1
|
||||
SELF_HOSTED: 1
|
||||
CLOUD: 1
|
||||
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
||||
WORKER_URL: http://worker-service:4003
|
||||
HOSTING_KEY: ${HOSTING_KEY}
|
||||
BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT}
|
||||
PORT: 4002
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
|
@ -20,14 +23,13 @@ services:
|
|||
- "${WORKER_PORT}:4003"
|
||||
environment:
|
||||
SELF_HOSTED: 1,
|
||||
DEPLOYMENT_API_KEY: ${WORKER_API_KEY}
|
||||
PORT: 4003
|
||||
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||
RAW_MINIO_URL: http://minio-service:9000
|
||||
COUCH_DB_USERNAME: ${COUCH_DB_USER}
|
||||
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
|
||||
RAW_COUCH_DB_URL: http://couchdb-service:5984
|
||||
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
||||
SELF_HOST_KEY: ${HOSTING_KEY}
|
||||
depends_on:
|
||||
- minio-service
|
||||
|
|
|
@ -12,7 +12,6 @@ MINIO_ACCESS_KEY=budibase
|
|||
MINIO_SECRET_KEY=budibase
|
||||
COUCH_DB_PASSWORD=budibase
|
||||
COUCH_DB_USER=budibase
|
||||
WORKER_API_KEY=budibase
|
||||
|
||||
# This section contains variables that do not need to be altered under normal circumstances
|
||||
APP_PORT=4002
|
||||
|
|
|
@ -615,6 +615,17 @@
|
|||
"ssri": "^6.0.1",
|
||||
"unique-filename": "^1.1.1",
|
||||
"y18n": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"make-fetch-happen": {
|
||||
|
@ -1004,6 +1015,17 @@
|
|||
"ssri": "^6.0.1",
|
||||
"unique-filename": "^1.1.1",
|
||||
"y18n": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"make-fetch-happen": {
|
||||
|
@ -1087,6 +1109,17 @@
|
|||
"npmlog": "^4.1.2",
|
||||
"path-exists": "^3.0.0",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@lerna/run": {
|
||||
|
@ -1900,6 +1933,17 @@
|
|||
"ssri": "^6.0.1",
|
||||
"unique-filename": "^1.1.1",
|
||||
"y18n": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cache-base": {
|
||||
|
@ -2494,6 +2538,17 @@
|
|||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.5.4",
|
||||
"run-queue": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"copy-descriptor": {
|
||||
|
@ -3394,6 +3449,17 @@
|
|||
"flatted": "^2.0.0",
|
||||
"rimraf": "2.6.3",
|
||||
"write": "1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flatted": {
|
||||
|
@ -3767,6 +3833,12 @@
|
|||
"pump": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"get-them-args": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/get-them-args/-/get-them-args-1.3.2.tgz",
|
||||
"integrity": "sha1-dKILqKSr7OWuGZrQPyvMaP38m6U=",
|
||||
"dev": true
|
||||
},
|
||||
"get-value": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
|
||||
|
@ -4354,9 +4426,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"dev": true
|
||||
},
|
||||
"init-package-json": {
|
||||
|
@ -4829,6 +4901,16 @@
|
|||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"kill-port": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/kill-port/-/kill-port-1.6.1.tgz",
|
||||
"integrity": "sha512-un0Y55cOM7JKGaLnGja28T38tDDop0AQ8N0KlAdyh+B1nmMoX8AnNmqPNZbS3mUMgiST51DCVqmbFT1gNJpVNw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-them-args": "1.3.2",
|
||||
"shell-exec": "1.0.2"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
|
@ -5370,6 +5452,17 @@
|
|||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.5.4",
|
||||
"run-queue": "^1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -5469,6 +5562,15 @@
|
|||
"which": "1"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
|
@ -5966,6 +6068,15 @@
|
|||
"which": "^1.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
|
@ -6192,13 +6303,10 @@
|
|||
}
|
||||
},
|
||||
"prettier-plugin-svelte": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-0.7.0.tgz",
|
||||
"integrity": "sha512-SuZSeMh48rx42kCFEpI/xE1XgjxQcS3r22Yo7jIhBYRhwbAa8laNxiIHsfeWWkX8BdyELkEayaTQp4ricckwTQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-1.4.2.tgz",
|
||||
"integrity": "sha512-O9VsNwII+raTG8QPoQWouk5ABQy/hmLm4dZ2eqJ7DPnbO35A+BxMSjlfqkw0cNP+UcbykHFYU8zNXm93ytWP9g==",
|
||||
"dev": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
|
@ -6603,9 +6711,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
|
@ -6733,6 +6841,12 @@
|
|||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
||||
"dev": true
|
||||
},
|
||||
"shell-exec": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/shell-exec/-/shell-exec-1.0.2.tgz",
|
||||
"integrity": "sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==",
|
||||
"dev": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||
|
@ -7213,9 +7327,9 @@
|
|||
}
|
||||
},
|
||||
"svelte": {
|
||||
"version": "3.29.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.29.0.tgz",
|
||||
"integrity": "sha512-f+A65eyOQ5ujETLy+igNXtlr6AEjAQLYd1yJE1VwNiXMQO5Z/Vmiy3rL+zblV/9jd7rtTTWqO1IcuXsP2Qv0OA==",
|
||||
"version": "3.31.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.31.2.tgz",
|
||||
"integrity": "sha512-TxZGrXzX2ggFH3BIKY5fmbeMdJuZrMIMDYPMX6R9255bueuYIuVaBQSLUeY2oD7W4IdeqRZiAVGCjDw2POKBRA==",
|
||||
"dev": true
|
||||
},
|
||||
"table": {
|
||||
|
|
|
@ -34,8 +34,5 @@
|
|||
"test:e2e": "lerna run cy:test",
|
||||
"test:e2e:ci": "lerna run cy:ci",
|
||||
"build:docker": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome": "^1.1.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,15 +63,15 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.52.4",
|
||||
"@budibase/bbui": "^1.54.0",
|
||||
"@budibase/client": "^0.5.3",
|
||||
"@budibase/colorpicker": "^1.0.1",
|
||||
"@budibase/string-templates": "^0.5.3",
|
||||
"@budibase/svelte-ag-grid": "^0.0.16",
|
||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@svelteschool/svelte-forms": "^0.7.0",
|
||||
"britecharts": "^2.16.0",
|
||||
"codemirror": "^5.59.0",
|
||||
"d3-selection": "^1.4.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fast-sort": "^2.2.0",
|
||||
|
|
|
@ -23,13 +23,20 @@ import { cloneDeep, difference } from "lodash/fp"
|
|||
* @param {fetchBindablePropertiesParameter} param
|
||||
* @returns {Array.<BindableProperty>}
|
||||
*/
|
||||
export default function({ componentInstanceId, screen, components, tables }) {
|
||||
export default function({
|
||||
componentInstanceId,
|
||||
screen,
|
||||
components,
|
||||
tables,
|
||||
queries,
|
||||
}) {
|
||||
const result = walk({
|
||||
// cloning so we are free to mutate props (e.g. by adding _contexts)
|
||||
instance: cloneDeep(screen.props),
|
||||
targetId: componentInstanceId,
|
||||
components,
|
||||
tables,
|
||||
queries,
|
||||
})
|
||||
|
||||
return [
|
||||
|
@ -37,6 +44,9 @@ export default function({ componentInstanceId, screen, components, tables }) {
|
|||
.filter(isInstanceInSharedContext(result))
|
||||
.map(componentInstanceToBindable),
|
||||
...(result.target?._contexts.map(contextToBindables(tables)).flat() ?? []),
|
||||
...(result.target?._contexts
|
||||
.map(context => queriesToBindables(queries, context))
|
||||
.flat() ?? []),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -61,9 +71,36 @@ const componentInstanceToBindable = i => {
|
|||
}
|
||||
}
|
||||
|
||||
const queriesToBindables = (queries, context) => {
|
||||
let queryId = context.table._id
|
||||
|
||||
const query = queries.find(query => query._id === queryId)
|
||||
let schema = query?.schema
|
||||
|
||||
// Avoid crashing whenever no data source has been selected
|
||||
if (!schema) {
|
||||
return []
|
||||
}
|
||||
|
||||
const queryBindings = Object.entries(schema).map(([key, value]) => ({
|
||||
type: "context",
|
||||
fieldSchema: value,
|
||||
instance: context.instance,
|
||||
// how the binding expression persists, and is used in the app at runtime
|
||||
runtimeBinding: `${context.instance._id}.${key}`,
|
||||
// how the binding expressions looks to the user of the builder
|
||||
readableBinding: `${context.instance._instanceName}.${query.name}.${key}`,
|
||||
// table / view info
|
||||
table: context.table,
|
||||
}))
|
||||
|
||||
return queryBindings
|
||||
}
|
||||
|
||||
const contextToBindables = tables => context => {
|
||||
const tableId = context.table?.tableId ?? context.table
|
||||
const table = tables.find(table => table._id === tableId)
|
||||
let tableId = context.table?.tableId ?? context.table
|
||||
|
||||
const table = tables.find(table => table._id === tableId || context.table._id)
|
||||
let schema =
|
||||
context.table?.type === "view"
|
||||
? table?.views?.[context.table.name]?.schema
|
||||
|
@ -152,8 +189,8 @@ const walk = ({ instance, targetId, components, tables, result }) => {
|
|||
|
||||
const currentContexts = [...result.currentContexts]
|
||||
for (let child of instance._children || []) {
|
||||
// attaching _contexts of components, for eas comparison later
|
||||
// these have been deep cloned above, so shouln't modify the
|
||||
// attaching _contexts of components, for easy comparison later
|
||||
// these have been deep cloned above, so shouldn't modify the
|
||||
// original component instances
|
||||
child._contexts = currentContexts
|
||||
walk({ instance: child, targetId, components, tables, result })
|
||||
|
|
|
@ -7,6 +7,9 @@ const INITIAL_BACKEND_UI_STATE = {
|
|||
views: [],
|
||||
users: [],
|
||||
roles: [],
|
||||
datasources: [],
|
||||
queries: [],
|
||||
integrations: {},
|
||||
selectedDatabase: {},
|
||||
selectedTable: {},
|
||||
draftTable: {},
|
||||
|
@ -21,9 +24,19 @@ export const getBackendUiStore = () => {
|
|||
select: async db => {
|
||||
const tablesResponse = await api.get(`/api/tables`)
|
||||
const tables = await tablesResponse.json()
|
||||
const datasourcesResponse = await api.get(`/api/datasources`)
|
||||
const datasources = await datasourcesResponse.json()
|
||||
const queriesResponse = await api.get(`/api/queries`)
|
||||
const queries = await queriesResponse.json()
|
||||
const integrationsResponse = await api.get("/api/integrations")
|
||||
const integrations = await integrationsResponse.json()
|
||||
|
||||
store.update(state => {
|
||||
state.selectedDatabase = db
|
||||
state.tables = tables
|
||||
state.datasources = datasources
|
||||
state.queries = queries
|
||||
state.integrations = integrations
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
@ -45,6 +58,107 @@ export const getBackendUiStore = () => {
|
|||
return state
|
||||
}),
|
||||
},
|
||||
datasources: {
|
||||
fetch: async () => {
|
||||
const response = await api.get(`/api/datasources`)
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
state.datasources = json
|
||||
return state
|
||||
})
|
||||
return json
|
||||
},
|
||||
select: async datasourceId => {
|
||||
store.update(state => {
|
||||
state.selectedDatasourceId = datasourceId
|
||||
state.selectedQueryId = null
|
||||
return state
|
||||
})
|
||||
},
|
||||
save: async datasource => {
|
||||
const response = await api.post("/api/datasources", datasource)
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
const currentIdx = state.datasources.findIndex(
|
||||
ds => ds._id === json._id
|
||||
)
|
||||
|
||||
if (currentIdx >= 0) {
|
||||
state.datasources.splice(currentIdx, 1, json)
|
||||
} else {
|
||||
state.datasources.push(json)
|
||||
}
|
||||
|
||||
state.datasources = state.datasources
|
||||
state.selectedDatasourceId = json._id
|
||||
return state
|
||||
})
|
||||
return json
|
||||
},
|
||||
delete: async datasource => {
|
||||
await api.delete(
|
||||
`/api/datasources/${datasource._id}/${datasource._rev}`
|
||||
)
|
||||
store.update(state => {
|
||||
state.datasources = state.datasources.filter(
|
||||
existing => existing._id !== datasource._id
|
||||
)
|
||||
state.selectedDatasourceId = null
|
||||
return state
|
||||
})
|
||||
},
|
||||
},
|
||||
queries: {
|
||||
fetch: async () => {
|
||||
const response = await api.get(`/api/queries`)
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
state.queries = json
|
||||
return state
|
||||
})
|
||||
return json
|
||||
},
|
||||
save: async (datasourceId, query) => {
|
||||
query.datasourceId = datasourceId
|
||||
const response = await api.post(`/api/queries`, query)
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
const currentIdx = state.queries.findIndex(
|
||||
query => query._id === json._id
|
||||
)
|
||||
|
||||
if (currentIdx >= 0) {
|
||||
state.queries.splice(currentIdx, 1, json)
|
||||
} else {
|
||||
state.queries.push(json)
|
||||
}
|
||||
|
||||
state.queries = state.queries
|
||||
state.selectedQueryId = json._id
|
||||
return state
|
||||
})
|
||||
return json
|
||||
},
|
||||
select: query =>
|
||||
store.update(state => {
|
||||
state.selectedDatasourceId = query.datasourceId
|
||||
state.selectedQueryId = query._id
|
||||
return state
|
||||
}),
|
||||
delete: async query => {
|
||||
await api.delete(`/api/queries/${query._id}/${query._rev}`)
|
||||
store.update(state => {
|
||||
state.queries = state.queries.filter(
|
||||
existing => existing._id !== query._id
|
||||
)
|
||||
if (state.selectedQueryId === query._id) {
|
||||
state.selectedQueryId = null
|
||||
}
|
||||
|
||||
return state
|
||||
})
|
||||
},
|
||||
},
|
||||
tables: {
|
||||
fetch: async () => {
|
||||
const tablesResponse = await api.get(`/api/tables`)
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
const INITIAL_FRONTEND_STATE = {
|
||||
apps: [],
|
||||
name: "",
|
||||
url: "",
|
||||
description: "",
|
||||
layouts: [],
|
||||
screens: [],
|
||||
|
@ -62,6 +63,7 @@ export const getFrontendStore = () => {
|
|||
libraries: pkg.application.componentLibraries,
|
||||
components,
|
||||
name: pkg.application.name,
|
||||
url: pkg.application.url,
|
||||
description: pkg.application.description,
|
||||
appId: pkg.application._id,
|
||||
layouts,
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { writable } from "svelte/store"
|
||||
import api from "../api"
|
||||
import api, { get } from "../api"
|
||||
|
||||
const INITIAL_BACKEND_UI_STATE = {
|
||||
const INITIAL_HOSTING_UI_STATE = {
|
||||
hostingInfo: {},
|
||||
appUrl: "",
|
||||
deployedApps: {},
|
||||
deployedAppNames: [],
|
||||
deployedAppUrls: [],
|
||||
}
|
||||
|
||||
export const getHostingStore = () => {
|
||||
const store = writable({ ...INITIAL_BACKEND_UI_STATE })
|
||||
const store = writable({ ...INITIAL_HOSTING_UI_STATE })
|
||||
store.actions = {
|
||||
fetch: async () => {
|
||||
const responses = await Promise.all([
|
||||
|
@ -33,6 +36,16 @@ export const getHostingStore = () => {
|
|||
return state
|
||||
})
|
||||
},
|
||||
fetchDeployedApps: async () => {
|
||||
let deployments = await (await get("/api/hosting/apps")).json()
|
||||
store.update(state => {
|
||||
state.deployedApps = deployments
|
||||
state.deployedAppNames = Object.values(deployments).map(app => app.name)
|
||||
state.deployedAppUrls = Object.values(deployments).map(app => app.url)
|
||||
return state
|
||||
})
|
||||
return deployments
|
||||
},
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -199,7 +199,6 @@
|
|||
align-items: stretch;
|
||||
}
|
||||
.grid-wrapper :global(> *) {
|
||||
height: auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
:global(.grid-wrapper) {
|
||||
|
@ -236,6 +235,7 @@
|
|||
}
|
||||
|
||||
:global(.ag-filter) {
|
||||
background: var(--background);
|
||||
padding: var(--spacing-s);
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -23,5 +23,10 @@ export async function fetchDataForView(view) {
|
|||
const FETCH_ROWS_URL = `/api/views/${view.name}`
|
||||
|
||||
const response = await api.get(FETCH_ROWS_URL)
|
||||
return await response.json()
|
||||
const json = await response.json()
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(json.message)
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -5,7 +5,7 @@
|
|||
import api from "builderStore/api"
|
||||
|
||||
const BYTES_IN_MB = 1000000
|
||||
const FILE_SIZE_LIMIT = BYTES_IN_MB * 1
|
||||
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
|
||||
|
||||
export let files = []
|
||||
export let dataImport = {
|
||||
|
|
|
@ -2,13 +2,11 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { TableNames } from "constants"
|
||||
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import { Switcher } from "@budibase/bbui"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
|
||||
let modal
|
||||
$: selectedView =
|
||||
$backendUiStore.selectedView && $backendUiStore.selectedView.name
|
||||
|
||||
|
@ -34,10 +32,6 @@
|
|||
</script>
|
||||
|
||||
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
||||
<div class="title">
|
||||
<h1>Tables</h1>
|
||||
<i data-cy="new-table" on:click={modal.show} class="ri-add-circle-fill" />
|
||||
</div>
|
||||
<div class="hierarchy-items-container">
|
||||
{#each $backendUiStore.tables as table, idx}
|
||||
<NavItem
|
||||
|
@ -64,27 +58,3 @@
|
|||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<Modal bind:this={modal}>
|
||||
<CreateTableModal />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.title h1 {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
.title i {
|
||||
font-size: 20px;
|
||||
}
|
||||
.title i:hover {
|
||||
cursor: pointer;
|
||||
color: var(--blue);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,14 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { Input, Label, ModalContent, Toggle } from "@budibase/bbui"
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
ModalContent,
|
||||
Button,
|
||||
Spacer,
|
||||
Toggle,
|
||||
} from "@budibase/bbui"
|
||||
import TableDataImport from "../TableDataImport.svelte"
|
||||
import analytics from "analytics"
|
||||
import screenTemplates from "builderStore/store/screenTemplates"
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
on:click
|
||||
class:big={subtitle != null}
|
||||
{...$$restProps}>
|
||||
{#if icon}<i class={icon} />{/if}
|
||||
{#if icon}
|
||||
<i class={icon} />
|
||||
{/if}
|
||||
<div class="content">
|
||||
<div class="title">{title}</div>
|
||||
{#if subtitle != null}
|
||||
|
@ -56,7 +58,7 @@
|
|||
}
|
||||
|
||||
.title {
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
|
@ -65,6 +67,10 @@
|
|||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--grey-2);
|
||||
font-size: 24px;
|
||||
border-radius: var(--border-radius-s);
|
||||
color: var(--ink);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
<i class="ri-arrow-right-s-line" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<slot name="icon" />
|
||||
{#if icon}
|
||||
<div class="icon"><i class={icon} /></div>
|
||||
{/if}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import api from "builderStore/api"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte"
|
||||
import { hostingStore } from "builderStore"
|
||||
import { store, hostingStore } from "builderStore"
|
||||
|
||||
const DeploymentStatus = {
|
||||
SUCCESS: "SUCCESS",
|
||||
|
@ -36,7 +36,9 @@
|
|||
let errorReason
|
||||
let poll
|
||||
let deployments = []
|
||||
let deploymentUrl = `${$hostingStore.appUrl}/${appId}`
|
||||
let urlComponent =
|
||||
$hostingStore.hostingInfo.type === "self" ? $store.url : `/${appId}`
|
||||
let deploymentUrl = `${$hostingStore.appUrl}${urlComponent}`
|
||||
|
||||
const formatDate = (date, format) =>
|
||||
Intl.DateTimeFormat("en-GB", DATE_OPTIONS[format]).format(date)
|
||||
|
|
|
@ -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}
|
|
@ -1,11 +1,20 @@
|
|||
<script>
|
||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import { Input, TextArea } from "@budibase/bbui"
|
||||
import { store, hostingStore } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import { object, string } from "yup"
|
||||
import { onMount } from "svelte"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
let nameValidation, nameError
|
||||
let urlValidation, urlError
|
||||
|
||||
$: checkName($store.name)
|
||||
$: checkUrl($store.url)
|
||||
|
||||
async function updateApplication(data) {
|
||||
const response = await api.put(`/api/applications/${$store.appId}`, data)
|
||||
const app = await response.json()
|
||||
await response.json()
|
||||
store.update(state => {
|
||||
state = {
|
||||
...state,
|
||||
|
@ -14,6 +23,59 @@
|
|||
return state
|
||||
})
|
||||
}
|
||||
|
||||
async function checkValidation(input, validation) {
|
||||
if (!input || !validation) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await object(validation).validate(input, { abortEarly: false })
|
||||
} catch (error) {
|
||||
if (!error || !error.inner) return ""
|
||||
return error.inner.reduce((acc, err) => {
|
||||
return acc + err.message
|
||||
}, "")
|
||||
}
|
||||
}
|
||||
|
||||
async function checkName(name) {
|
||||
nameError = await checkValidation({ name }, nameValidation)
|
||||
}
|
||||
|
||||
async function checkUrl(url) {
|
||||
urlError = await checkValidation({ url: url.toLowerCase() }, urlValidation)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const nameError = "Your application must have a name.",
|
||||
urlError = "Your application must have a URL."
|
||||
let hostingInfo = await hostingStore.actions.fetch()
|
||||
if (hostingInfo.type === "self") {
|
||||
await hostingStore.actions.fetchDeployedApps()
|
||||
const existingAppNames = get(hostingStore).deployedAppNames
|
||||
const existingAppUrls = get(hostingStore).deployedAppUrls
|
||||
const nameIdx = existingAppNames.indexOf(get(store).name)
|
||||
const urlIdx = existingAppUrls.indexOf(get(store).url)
|
||||
if (nameIdx !== -1) {
|
||||
existingAppNames.splice(nameIdx, 1)
|
||||
}
|
||||
if (urlIdx !== -1) {
|
||||
existingAppUrls.splice(urlIdx, 1)
|
||||
}
|
||||
nameValidation = {
|
||||
name: string()
|
||||
.required(nameError)
|
||||
.notOneOf(existingAppNames),
|
||||
}
|
||||
urlValidation = {
|
||||
url: string()
|
||||
.required(urlError)
|
||||
.notOneOf(existingAppUrls),
|
||||
}
|
||||
} else {
|
||||
nameValidation = { name: string.required(nameError) }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
@ -21,8 +83,18 @@
|
|||
on:save={e => updateApplication({ name: e.detail })}
|
||||
thin
|
||||
edit
|
||||
value={$store.name}
|
||||
bind:value={$store.name}
|
||||
bind:error={nameError}
|
||||
label="App Name" />
|
||||
{#if $hostingStore.hostingInfo.type === 'self'}
|
||||
<Input
|
||||
on:save={e => updateApplication({ url: e.detail })}
|
||||
thin
|
||||
edit
|
||||
bind:value={$store.url}
|
||||
bind:error={urlError}
|
||||
label="App URL" />
|
||||
{/if}
|
||||
<TextArea
|
||||
on:save={e => updateApplication({ description: e.detail })}
|
||||
thin
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { writable } from "svelte/store"
|
||||
import { writable, get as svelteGet } from "svelte/store"
|
||||
import {
|
||||
store,
|
||||
automationStore,
|
||||
|
@ -28,6 +28,7 @@
|
|||
let isApiKeyValid
|
||||
let lastApiKey
|
||||
let fetchApiKeyPromise
|
||||
|
||||
const validateApiKey = async apiKey => {
|
||||
if (isApiKeyValid) return true
|
||||
if (!apiKey) return false
|
||||
|
@ -81,6 +82,11 @@
|
|||
let hostingInfo = await hostingStore.actions.fetch()
|
||||
// re-init the steps based on whether self hosting or cloud hosted
|
||||
if (hostingInfo.type === "self") {
|
||||
await hostingStore.actions.fetchDeployedApps()
|
||||
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
||||
infoValidation.applicationName = string()
|
||||
.required("Your application must have a name.")
|
||||
.notOneOf(existingAppNames)
|
||||
isApiKeyValid = true
|
||||
steps = [buildStep(Info), buildStep(User)]
|
||||
validationSchemas = [infoValidation, userValidation]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
<style>
|
||||
body, html {
|
||||
height: 100% !important;
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import GenericBindingPopover from "./GenericBindingPopover.svelte"
|
||||
import { Input, Icon } from "@budibase/bbui"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let bindings = []
|
||||
export let value
|
||||
|
||||
let anchor
|
||||
let popover = undefined
|
||||
let enrichedValue
|
||||
|
@ -14,6 +18,7 @@
|
|||
let { bindings, ...otherProps } = $$props
|
||||
inputProps = otherProps
|
||||
}
|
||||
$: value && dispatch("change", value)
|
||||
</script>
|
||||
|
||||
<div class="container" bind:this={anchor}>
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
<script>
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import {
|
||||
Button,
|
||||
TextArea,
|
||||
Label,
|
||||
Body,
|
||||
Heading,
|
||||
Spacer,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
export let bindableProperties
|
||||
export let value = ""
|
||||
export let close
|
||||
|
||||
function addToText(readableBinding) {
|
||||
value = value + `{{ ${readableBinding} }}`
|
||||
}
|
||||
let originalValue = value
|
||||
|
||||
$: dispatch("update", value)
|
||||
|
||||
function cancel() {
|
||||
dispatch("update", originalValue)
|
||||
close()
|
||||
}
|
||||
|
||||
$: ({ instance, context } = groupBy("type", bindableProperties))
|
||||
</script>
|
||||
|
||||
<div class="container" data-cy="binding-dropdown-modal">
|
||||
<div class="list">
|
||||
<Heading extraSmall>Objects</Heading>
|
||||
<Spacer medium />
|
||||
{#if context}
|
||||
<Heading extraSmall>Tables</Heading>
|
||||
<ul>
|
||||
{#each context as { readableBinding }}
|
||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if instance}
|
||||
<Heading extraSmall>Components</Heading>
|
||||
<ul>
|
||||
{#each instance as { readableBinding }}
|
||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text">
|
||||
<Heading extraSmall>Data binding</Heading>
|
||||
<Spacer small />
|
||||
<Body extraSmall lh>
|
||||
Binding connects one piece of data to another and makes it dynamic. Click
|
||||
the objects on the left, to add them to the textbox.
|
||||
</Body>
|
||||
<Spacer large />
|
||||
<TextArea
|
||||
thin
|
||||
bind:value
|
||||
placeholder="Add text, or click the objects on the left to add them to the
|
||||
textbox." />
|
||||
<div class="controls">
|
||||
<a href="https://docs.budibase.com/design/binding">
|
||||
<Body small grey>Learn more about binding</Body>
|
||||
</a>
|
||||
<Button on:click={cancel} secondary>Cancel</Button>
|
||||
<Button on:click={close} primary>Done</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
.list,
|
||||
.text {
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
.controls {
|
||||
margin-top: var(--spacing-m);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-gap: var(--spacing-l);
|
||||
grid-template-columns: 1fr auto auto;
|
||||
}
|
||||
.list {
|
||||
width: 150px;
|
||||
border-right: 1.5px solid var(--grey-4);
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
.text {
|
||||
width: 600px;
|
||||
padding: var(--spacing-xl);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.text :global(p) {
|
||||
margin: 0;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--grey-7);
|
||||
padding: var(--spacing-s) 0;
|
||||
margin: auto 0px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
color: var(--ink);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
li:active {
|
||||
color: var(--blue);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,113 @@
|
|||
<script>
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import {
|
||||
Button,
|
||||
TextArea,
|
||||
Drawer,
|
||||
Heading,
|
||||
Spacer,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
export let bindableProperties
|
||||
export let value = ""
|
||||
export let bindingDrawer
|
||||
|
||||
function addToText(readableBinding) {
|
||||
value = value + `{{ ${readableBinding} }}`
|
||||
}
|
||||
let originalValue = value
|
||||
|
||||
$: dispatch("update", value)
|
||||
|
||||
export function cancel() {
|
||||
dispatch("update", originalValue)
|
||||
bindingDrawer.close()
|
||||
}
|
||||
|
||||
$: ({ instance, context } = groupBy("type", bindableProperties))
|
||||
</script>
|
||||
|
||||
<div class="drawer-contents">
|
||||
<div class="container" data-cy="binding-dropdown-modal">
|
||||
<div class="list">
|
||||
<Heading extraSmall>Objects</Heading>
|
||||
<Spacer medium />
|
||||
{#if context}
|
||||
<Heading extraSmall>Tables</Heading>
|
||||
<ul>
|
||||
{#each context as { readableBinding }}
|
||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if instance}
|
||||
<Heading extraSmall>Components</Heading>
|
||||
<ul>
|
||||
{#each instance as { readableBinding }}
|
||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text">
|
||||
<TextArea
|
||||
thin
|
||||
bind:value
|
||||
placeholder="Add text, or click the objects on the left to add them to the
|
||||
textbox." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
.list {
|
||||
width: 150px;
|
||||
border-right: 1.5px solid var(--grey-4);
|
||||
padding: var(--spacing-s);
|
||||
}
|
||||
.text {
|
||||
padding: var(--spacing-s);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.text :global(p) {
|
||||
margin: 0;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--grey-7);
|
||||
padding: var(--spacing-s) 0;
|
||||
margin: auto 0px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
color: var(--ink);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
li:active {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.drawer-contents {
|
||||
height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
|
@ -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,68 @@
|
|||
<script>
|
||||
import { Button, Modal } from "@budibase/bbui"
|
||||
import EventEditorModal from "./EventEditorModal.svelte"
|
||||
import { Button, Drawer } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import EventEditor from "./EventEditor.svelte"
|
||||
import { automationStore } from "builderStore"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value
|
||||
export let name
|
||||
|
||||
let modal
|
||||
let drawer
|
||||
|
||||
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.")
|
||||
drawer.hide()
|
||||
}
|
||||
|
||||
// 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>
|
||||
|
||||
<Modal bind:this={modal} width="600px">
|
||||
<EventEditorModal event={value} eventType={name} on:change />
|
||||
</Modal>
|
||||
<Button secondary small on:click={drawer.show}>Define Actions</Button>
|
||||
<Drawer bind:this={drawer} title={'Actions'}>
|
||||
<heading slot="buttons">
|
||||
<Button thin blue on:click={saveEventData}>Save</Button>
|
||||
</heading>
|
||||
<div slot="body">
|
||||
<EventEditor event={value} eventType={name} />
|
||||
</div>
|
||||
</Drawer>
|
||||
|
|
|
@ -11,11 +11,9 @@
|
|||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
|
||||
// just wraps binding in {{ ... }}
|
||||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
|
||||
|
||||
const tableFields = tableId => {
|
||||
const table = $backendUiStore.tables.find(m => m._id === tableId)
|
||||
|
||||
|
@ -63,12 +61,4 @@
|
|||
grid-column-start: 2;
|
||||
grid-column-end: 6;
|
||||
}
|
||||
|
||||
.cannot-use {
|
||||
color: var(--red);
|
||||
font-size: var(--font-size-s);
|
||||
text-align: center;
|
||||
width: 70%;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
|
||||
$: idFields = bindableProperties.filter(
|
||||
|
@ -42,8 +43,6 @@
|
|||
typeof tableInfo === "string" ? tableInfo : tableInfo.tableId
|
||||
}
|
||||
}
|
||||
|
||||
console.log(parameters)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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>
|
|
@ -44,6 +44,7 @@
|
|||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
|
||||
const addField = () => {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
|
||||
$: {
|
||||
|
|
|
@ -3,9 +3,16 @@
|
|||
import { automationStore } from "builderStore"
|
||||
import SaveFields from "./SaveFields.svelte"
|
||||
|
||||
const AUTOMATION_STATUS = {
|
||||
NEW: "new",
|
||||
EXISTING: "existing",
|
||||
}
|
||||
|
||||
export let parameters = {}
|
||||
|
||||
let newOrExisting = parameters.automationId ? "existing" : "new"
|
||||
let automationStatus = parameters.automationId
|
||||
? AUTOMATION_STATUS.EXISTING
|
||||
: AUTOMATION_STATUS.NEW
|
||||
|
||||
$: automations = $automationStore.automations
|
||||
.filter(a => a.definition.trigger?.stepId === "APP")
|
||||
|
@ -33,12 +40,12 @@
|
|||
}
|
||||
|
||||
const setNew = () => {
|
||||
newOrExisting = "new"
|
||||
automationStatus = AUTOMATION_STATUS.NEW
|
||||
parameters.automationId = undefined
|
||||
}
|
||||
|
||||
const setExisting = () => {
|
||||
newOrExisting = "existing"
|
||||
automationStatus = AUTOMATION_STATUS.EXISTING
|
||||
parameters.newAutomationName = ""
|
||||
}
|
||||
</script>
|
||||
|
@ -47,8 +54,8 @@
|
|||
<div class="radio-container" on:click={setNew}>
|
||||
<input
|
||||
type="radio"
|
||||
value="new"
|
||||
bind:group={newOrExisting}
|
||||
value={AUTOMATION_STATUS.NEW}
|
||||
bind:group={automationStatus}
|
||||
disabled={!hasAutomations} />
|
||||
|
||||
<Label disabled={!hasAutomations}>Create a new automation</Label>
|
||||
|
@ -57,8 +64,8 @@
|
|||
<div class="radio-container" on:click={setExisting}>
|
||||
<input
|
||||
type="radio"
|
||||
value="existing"
|
||||
bind:group={newOrExisting}
|
||||
value={AUTOMATION_STATUS.EXISTING}
|
||||
bind:group={automationStatus}
|
||||
disabled={!hasAutomations} />
|
||||
|
||||
<Label disabled={!hasAutomations}>Use an existing automation</Label>
|
||||
|
@ -66,7 +73,7 @@
|
|||
|
||||
<Label size="m" color="dark">Automation</Label>
|
||||
|
||||
{#if newOrExisting === 'existing'}
|
||||
{#if automationStatus === AUTOMATION_STATUS.EXISTING}
|
||||
<Select
|
||||
secondary
|
||||
bind:value={parameters.automationId}
|
||||
|
@ -85,7 +92,7 @@
|
|||
|
||||
<SaveFields
|
||||
parameterFields={parameters.fields}
|
||||
schemaFields={newOrExisting === 'existing' && selectedAutomation && selectedAutomation.schema}
|
||||
schemaFields={automationStatus === AUTOMATION_STATUS.EXISTING && selectedAutomation && selectedAutomation.schema}
|
||||
fieldLabel="Field"
|
||||
on:fieldschanged={onFieldsChanged} />
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
|
||||
let idFields
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import NavigateTo from "./NavigateTo.svelte"
|
||||
import SaveRow from "./SaveRow.svelte"
|
||||
import DeleteRow from "./DeleteRow.svelte"
|
||||
import ExecuteQuery from "./ExecuteQuery.svelte"
|
||||
import TriggerAutomation from "./TriggerAutomation.svelte"
|
||||
|
||||
// defines what actions are available, when adding a new one
|
||||
|
@ -21,6 +22,10 @@ export default [
|
|||
name: "Navigate To",
|
||||
component: NavigateTo,
|
||||
},
|
||||
{
|
||||
name: "Execute Query",
|
||||
component: ExecuteQuery,
|
||||
},
|
||||
{
|
||||
name: "Trigger Automation",
|
||||
component: TriggerAutomation,
|
||||
|
|
|
@ -50,7 +50,9 @@
|
|||
<span class="binding__label">{binding.label}</span>
|
||||
<span class="binding__type">{binding.type}</span>
|
||||
<br />
|
||||
<div class="binding__description">{binding.description}</div>
|
||||
<div class="binding__description">
|
||||
{binding.description || ''}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
<script context="module">
|
||||
import iconData from "./icons.js"
|
||||
|
||||
const categories = Object.keys(iconData)
|
||||
const icons = Object.keys(iconData).reduce((acc, cat) => [...acc, ...Object.keys(iconData[cat])], [])
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import { createEventDispatcher, tick } from "svelte"
|
||||
|
||||
import icons from "./icons.js"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
|
@ -45,12 +51,13 @@
|
|||
"Y",
|
||||
"Z",
|
||||
]
|
||||
|
||||
let buttonAnchor, dropdown
|
||||
let loading = false
|
||||
|
||||
function findIconByTerm(term) {
|
||||
const r = new RegExp(`\^${term}`, "i")
|
||||
return icons.filter(i => r.test(i.label))
|
||||
return icons.filter(i => r.test(i))
|
||||
}
|
||||
|
||||
async function switchLetter(letter) {
|
||||
|
@ -62,7 +69,6 @@
|
|||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function findIconOnPage() {
|
||||
loading = true
|
||||
const iconIdx = filteredIcons.findIndex(i => i.value === value)
|
||||
|
@ -100,7 +106,7 @@
|
|||
loading = false
|
||||
}
|
||||
|
||||
$: displayValue = value ? value.substring(7) : "Pick Icon"
|
||||
$: displayValue = value ? value.substring(3) : "Pick Icon"
|
||||
|
||||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage)
|
||||
$: pageEndIdx = maxIconsPerPage * currentPage
|
||||
|
@ -138,11 +144,11 @@
|
|||
<div class="page-area">
|
||||
<div class="pager">
|
||||
<span on:click={() => pageClick(false)}>
|
||||
<i class="page-btn fas fa-chevron-left" />
|
||||
<i class="page-btn ri-arrow-left-line ri-sm" />
|
||||
</span>
|
||||
<span>{pagerText}</span>
|
||||
<span on:click={() => pageClick(true)}>
|
||||
<i class="page-btn fas fa-chevron-right" />
|
||||
<i class="page-btn ri-arrow-right-line ri-sm" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -153,12 +159,21 @@
|
|||
{#each pagedIcons as icon}
|
||||
<div
|
||||
class="icon-container"
|
||||
class:selected={value === icon.value}
|
||||
on:click={() => (value = icon.value)}>
|
||||
class:selected={value === `ri-${icon}-fill`}
|
||||
on:click={() => (value = `ri-${icon}-fill`)}>
|
||||
<div class="icon-preview">
|
||||
<i class={`${icon.value} fa-3x`} />
|
||||
<i class={`ri-${icon}-fill ri-xl`} />
|
||||
</div>
|
||||
<div class="icon-label">{icon.label}</div>
|
||||
<div class="icon-label">{icon}-fill</div>
|
||||
</div>
|
||||
<div
|
||||
class="icon-container"
|
||||
class:selected={value === `ri-${icon}-line`}
|
||||
on:click={() => (value = `ri-${icon}-line`)}>
|
||||
<div class="icon-preview">
|
||||
<i class={`ri-${icon}-line ri-xl`} />
|
||||
</div>
|
||||
<div class="icon-label">{icon}-line</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
@ -182,13 +197,11 @@
|
|||
padding: 10px 0px 10px 15px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.search-area {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.icon-area {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
|
@ -199,13 +212,11 @@
|
|||
overflow-x: hidden;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.no-icons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alphabet-area {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
@ -213,44 +224,36 @@
|
|||
padding-right: 15px;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
width: 510px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.page-area {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.letter {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.letter:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.letter-selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
|
@ -258,34 +261,28 @@
|
|||
flex-direction: column;
|
||||
border: var(--border-dark);
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
cursor: pointer;
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: var(--grey-3);
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
flex: 0 0 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.page-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1 @@
|
|||
import "@fortawesome/fontawesome-free/js/all.js"
|
||||
|
||||
export { default as IconSelect } from "./IconSelect.svelte"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { Button, Icon, Drawer, Body } from "@budibase/bbui"
|
||||
import Input from "./PropertyPanelControls/Input.svelte"
|
||||
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
|
@ -7,8 +7,7 @@
|
|||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/replaceBindings"
|
||||
import { DropdownMenu } from "@budibase/bbui"
|
||||
import BindingDropdown from "components/userInterface/BindingDropdown.svelte"
|
||||
import BindingPanel from "components/userInterface/BindingPanel.svelte"
|
||||
|
||||
export let label = ""
|
||||
export let bindable = true
|
||||
|
@ -19,13 +18,15 @@
|
|||
export let props = {}
|
||||
export let onChange = () => {}
|
||||
|
||||
let bindingDrawer
|
||||
|
||||
let temporaryBindableValue = value
|
||||
let bindableProperties = []
|
||||
let anchor
|
||||
let dropdown
|
||||
|
||||
function handleClose() {
|
||||
handleChange(key, temporaryBindableValue)
|
||||
bindingDrawer.hide()
|
||||
}
|
||||
|
||||
function getBindableProperties() {
|
||||
|
@ -35,6 +36,7 @@
|
|||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -94,32 +96,34 @@
|
|||
<div
|
||||
class="icon"
|
||||
data-cy={`${key}-binding-button`}
|
||||
on:click={dropdown.show}>
|
||||
on:click={bindingDrawer.show}>
|
||||
<Icon name="edit" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if control == Input}
|
||||
<DropdownMenu
|
||||
on:close={handleClose}
|
||||
bind:this={dropdown}
|
||||
{anchor}
|
||||
align="right">
|
||||
<BindingDropdown
|
||||
{...handlevalueKey(value)}
|
||||
close={dropdown.hide}
|
||||
on:update={e => (temporaryBindableValue = e.detail)}
|
||||
{bindableProperties} />
|
||||
</DropdownMenu>
|
||||
{/if}
|
||||
|
||||
<Drawer bind:this={bindingDrawer} title="Bindings">
|
||||
<div slot="description"><Body extraSmall grey>Add the objects on the left to enrich your text.</Body></div>
|
||||
<heading slot="buttons">
|
||||
<Button thin blue on:click={handleClose}>Save</Button>
|
||||
</heading>
|
||||
<div slot="body">
|
||||
<BindingPanel {...handlevalueKey(value)}
|
||||
close={handleClose}
|
||||
on:update={e => (temporaryBindableValue = e.detail)}
|
||||
{bindableProperties} />
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<style>
|
||||
|
||||
.property-control {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
|
||||
const detailScreens = $allScreens.filter(screen =>
|
||||
|
|
|
@ -9,17 +9,20 @@
|
|||
export let multiselect = false
|
||||
|
||||
const tables = $backendUiStore.tables
|
||||
const queries = $backendUiStore.queries
|
||||
|
||||
let options = []
|
||||
|
||||
$: table = componentInstance.datasource
|
||||
? tables.find(m => m._id === componentInstance.datasource.tableId)
|
||||
: null
|
||||
$: table =
|
||||
componentInstance.datasource?.type === "table"
|
||||
? tables.find(m => m._id === componentInstance.datasource.tableId)
|
||||
: queries.find(query => query._id === componentInstance.datasource._id)
|
||||
|
||||
$: type = componentInstance.datasource.type
|
||||
|
||||
$: if (table) {
|
||||
options =
|
||||
type === "table" || type === "link"
|
||||
type === "table" || type === "link" || type === "query"
|
||||
? Object.keys(table.schema)
|
||||
: Object.keys(table.views[componentInstance.datasource.name].schema)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
<script>
|
||||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
||||
import { Button, Icon, DropdownMenu, Spacer, Heading, Drawer } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
let drawer
|
||||
|
||||
export let value = {}
|
||||
|
||||
function handleSelected(selected) {
|
||||
dispatch("change", selected)
|
||||
dropdownRight.hide()
|
||||
}
|
||||
|
||||
$: tables = $backendUiStore.tables.map(m => ({
|
||||
label: m.name,
|
||||
name: `all_${m._id}`,
|
||||
|
@ -31,13 +30,30 @@
|
|||
return [...acc, ...viewsArr]
|
||||
}, [])
|
||||
|
||||
$: queries = $backendUiStore.queries.map(query => ({
|
||||
label: query.name,
|
||||
name: query.name,
|
||||
...query,
|
||||
schema: query.schema,
|
||||
parameters: query.parameters,
|
||||
type: "query",
|
||||
}))
|
||||
|
||||
$: bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.selectedComponentId,
|
||||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
queries: $backendUiStore.queries,
|
||||
})
|
||||
|
||||
$: queryBindableProperties = bindableProperties.map(property => ({
|
||||
...property,
|
||||
category: property.type === "instance" ? "Component" : "Table",
|
||||
label: property.readableBinding,
|
||||
path: property.readableBinding,
|
||||
}))
|
||||
|
||||
$: links = bindableProperties
|
||||
.filter(x => x.fieldSchema?.type === "link")
|
||||
.map(property => {
|
||||
|
@ -50,15 +66,59 @@
|
|||
type: "link",
|
||||
}
|
||||
})
|
||||
|
||||
function handleSelected(selected) {
|
||||
dispatch("change", selected)
|
||||
dropdownRight.hide()
|
||||
}
|
||||
|
||||
function fetchDatasourceSchema(query) {
|
||||
const source = $backendUiStore.datasources.find(
|
||||
ds => ds._id === query.datasourceId
|
||||
).source
|
||||
return $backendUiStore.integrations[source].query[query.queryVerb][
|
||||
query.queryType
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value.label ? value.label : 'Table / View'}</span>
|
||||
<span>{value.label ? value.label : 'Table / View / Query'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
</div>
|
||||
{#if value.type === 'query'}
|
||||
<i class="ri-settings-5-line" on:click={drawer.show} />
|
||||
<Drawer title={'Query'}>
|
||||
<div slot="buttons">
|
||||
<Button
|
||||
blue
|
||||
thin
|
||||
on:click={() => {
|
||||
notifier.success('Query parameters saved.')
|
||||
handleSelected(value)
|
||||
drawer.hide()
|
||||
}}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<div class="drawer-contents" slot="body">
|
||||
<IntegrationQueryEditor
|
||||
query={value}
|
||||
schema={fetchDatasourceSchema(value)}
|
||||
editable={false} />
|
||||
<Spacer large />
|
||||
{#if value.parameters.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find(query => query._id === value._id).parameters}
|
||||
bindings={queryBindableProperties} />
|
||||
{/if}
|
||||
</div>
|
||||
</Drawer>
|
||||
{/if}
|
||||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||
<div class="dropdown">
|
||||
<div class="title">
|
||||
|
@ -99,6 +159,20 @@
|
|||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Queries</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each queries as query}
|
||||
<li
|
||||
class:selected={value === query}
|
||||
on:click={() => handleSelected(query)}>
|
||||
{query.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
|
@ -163,4 +237,23 @@
|
|||
li:hover {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
|
||||
.drawer-contents {
|
||||
padding: var(--spacing-xl);
|
||||
height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-left: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
transform: scale(1.1);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1153,16 +1153,23 @@ export default {
|
|||
label: "Size",
|
||||
key: "size",
|
||||
control: OptionSelect,
|
||||
defaultValue: "fa-lg",
|
||||
defaultValue: "md",
|
||||
options: [
|
||||
{ value: "fa-xs", label: "xs" },
|
||||
{ value: "fa-sm", label: "sm" },
|
||||
{ value: "fa-lg", label: "lg" },
|
||||
{ value: "fa-2x", label: "2x" },
|
||||
{ value: "fa-3x", label: "3x" },
|
||||
{ value: "fa-5x", label: "5x" },
|
||||
{ value: "fa-7x", label: "7x" },
|
||||
{ value: "fa-10x", label: "10x" },
|
||||
{ value: "ri-xxs", label: "xxs" },
|
||||
{ value: "ri-xs", label: "xs" },
|
||||
{ value: "ri-sm", label: "sm" },
|
||||
{ value: "ri-1x", label: "md" },
|
||||
{ value: "ri-lg", label: "lg" },
|
||||
{ value: "ri-xl", label: "xl" },
|
||||
{ value: "ri-2x", label: "2x" },
|
||||
{ value: "ri-3x", label: "3x" },
|
||||
{ value: "ri-4x", label: "4x" },
|
||||
{ value: "ri-5x", label: "5x" },
|
||||
{ value: "ri-6x", label: "6x" },
|
||||
{ value: "ri-7x", label: "7x" },
|
||||
{ value: "ri-8x", label: "8x" },
|
||||
{ value: "ri-9x", label: "9x" },
|
||||
{ value: "ri-10x", label: "10x" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,11 +1,49 @@
|
|||
<script>
|
||||
import { params } from "@sveltech/routify"
|
||||
import { Switcher, Modal } from "@budibase/bbui"
|
||||
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
||||
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
|
||||
import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
|
||||
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: "Tables",
|
||||
key: "table",
|
||||
},
|
||||
{
|
||||
title: "Data Sources",
|
||||
key: "datasource",
|
||||
},
|
||||
]
|
||||
|
||||
let tab = $params.selectedDatasource ? "datasource" : "table"
|
||||
|
||||
let modal
|
||||
</script>
|
||||
|
||||
<!-- routify:options index=0 -->
|
||||
<div class="root">
|
||||
<div class="nav">
|
||||
<TableNavigator />
|
||||
<Switcher headings={tabs} bind:value={tab}>
|
||||
<div class="title">
|
||||
<i
|
||||
data-cy={`new-${tab}`}
|
||||
class="ri-add-circle-fill"
|
||||
on:click={modal.show} />
|
||||
</div>
|
||||
{#if tab === 'table'}
|
||||
<TableNavigator />
|
||||
<Modal bind:this={modal}>
|
||||
<CreateTableModal />
|
||||
</Modal>
|
||||
{:else if tab === 'datasource'}
|
||||
<DatasourceNavigator />
|
||||
<Modal bind:this={modal}>
|
||||
<CreateDatasourceModal />
|
||||
</Modal>
|
||||
{/if}
|
||||
</Switcher>
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot />
|
||||
|
@ -19,6 +57,7 @@
|
|||
grid-template-columns: 260px minmax(0, 1fr);
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
padding: var(--spacing-l) 40px;
|
||||
|
@ -29,6 +68,7 @@
|
|||
align-items: stretch;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
|
||||
.nav {
|
||||
overflow-y: auto;
|
||||
background: var(--background);
|
||||
|
@ -38,5 +78,18 @@
|
|||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-l);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
position: absolute;
|
||||
top: var(--spacing-l);
|
||||
right: var(--spacing-xl);
|
||||
}
|
||||
|
||||
i:hover {
|
||||
cursor: pointer;
|
||||
color: var(--blue);
|
||||
}
|
||||
</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 />
|
|
@ -103,6 +103,15 @@
|
|||
padding: var(--spacing-l) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.binding-drawer-container {
|
||||
height: 50vh;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: var(--background);
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.nav-group-header > div:nth-child(1) {
|
||||
padding: 0rem 0.5rem 0rem 0rem;
|
||||
vertical-align: bottom;
|
||||
|
|
|
@ -224,6 +224,8 @@ const testData = () => {
|
|||
},
|
||||
]
|
||||
|
||||
const queries = []
|
||||
|
||||
const components = {
|
||||
"@budibase/standard-components/container": {
|
||||
props: {},
|
||||
|
@ -247,5 +249,5 @@ const testData = () => {
|
|||
},
|
||||
}
|
||||
|
||||
return { screen, tables, components }
|
||||
return { screen, tables, components, queries }
|
||||
}
|
||||
|
|
|
@ -842,10 +842,10 @@
|
|||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@budibase/bbui@^1.52.4":
|
||||
version "1.52.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.52.4.tgz#ae3c17e1f49f14e65831703958bcddc6e64afd24"
|
||||
integrity sha512-/wiv5dSyvXLgy2/zGEslnCsjwE8qqng1D8k5ScSOPEyMab8tzzd1XxfZAN9rp84zIMgAXeH6s5a4j4riR+jVkg==
|
||||
"@budibase/bbui@^1.54.0":
|
||||
version "1.54.0"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.54.0.tgz#60e6c0faa3d8f1781c503e74f8b8990f75ba2c40"
|
||||
integrity sha512-98koXkueqda6oQT6q0NPNvdL878ETRevtmmm34aSz9C6B4Oz68VVCsiFzRWuHvP/7wiNaAxMgY1nsEsCwP3LpQ==
|
||||
dependencies:
|
||||
markdown-it "^12.0.2"
|
||||
quill "^1.3.7"
|
||||
|
@ -918,11 +918,6 @@
|
|||
debug "^3.1.0"
|
||||
lodash.once "^4.1.1"
|
||||
|
||||
"@fortawesome/fontawesome-free@^5.14.0":
|
||||
version "5.15.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.1.tgz#ccfef6ddbe59f8fe8f694783e1d3eb88902dc5eb"
|
||||
integrity sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ==
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6"
|
||||
|
@ -2233,6 +2228,11 @@ code-point-at@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
||||
|
||||
codemirror@^5.59.0:
|
||||
version "5.59.0"
|
||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.59.0.tgz#6d8132055459aabf21d04cae5cf5c430e5c57bb9"
|
||||
integrity sha512-UGzSkCacY9z0rSpQ3wnTWRN2nvRE6foDXnJltWW8pazInR/R+3gXHrao4IFQMv/bSBvFBxt8/HPpkpKAS54x5Q==
|
||||
|
||||
collection-visit@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
|
||||
|
@ -3911,9 +3911,9 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1,
|
|||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@^1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { get } from "svelte/store"
|
||||
import { fetchTableData } from "./tables"
|
||||
import { fetchViewData } from "./views"
|
||||
import { fetchRelationshipData } from "./relationships"
|
||||
import { executeQuery } from "./queries"
|
||||
import { enrichRows } from "./rows"
|
||||
import { enrichDataBindings } from "../utils/enrichDataBinding"
|
||||
import { bindingStore } from "../store/binding"
|
||||
|
||||
/**
|
||||
* Fetches all rows for a particular Budibase data source.
|
||||
|
@ -18,6 +22,21 @@ export const fetchDatasource = async (datasource, dataContext) => {
|
|||
rows = await fetchTableData(tableId)
|
||||
} else if (type === "view") {
|
||||
rows = await fetchViewData(datasource)
|
||||
} else if (type === "query") {
|
||||
const bindings = get(bindingStore)
|
||||
|
||||
// Set the default query params
|
||||
let queryParams = datasource.queryParams || {}
|
||||
for (let param of datasource.parameters) {
|
||||
if (!queryParams[param.name]) {
|
||||
queryParams[param.name] = param.default
|
||||
}
|
||||
}
|
||||
const parameters = enrichDataBindings(queryParams, {
|
||||
...bindings,
|
||||
...dataContext,
|
||||
})
|
||||
return await executeQuery({ queryId: datasource._id, parameters })
|
||||
} else if (type === "link") {
|
||||
const row = dataContext[datasource.providerId]
|
||||
rows = await fetchRelationshipData({
|
||||
|
@ -26,7 +45,6 @@ export const fetchDatasource = async (datasource, dataContext) => {
|
|||
fieldName,
|
||||
})
|
||||
}
|
||||
|
||||
// Enrich rows
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ export * from "./attachments"
|
|||
export * from "./views"
|
||||
export * from "./relationships"
|
||||
export * from "./routes"
|
||||
export * from "./queries"
|
||||
export * from "./app"
|
||||
export * from "./automations"
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { enrichDataBinding } from "./enrichDataBinding"
|
||||
import { enrichDataBinding, enrichDataBindings } from "./enrichDataBinding"
|
||||
import { routeStore } from "../store"
|
||||
import { saveRow, deleteRow, triggerAutomation } from "../api"
|
||||
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
|
||||
|
||||
const saveRowHandler = async (action, context) => {
|
||||
let draft = context[`${action.parameters.contextPath}_draft`]
|
||||
|
@ -41,10 +41,23 @@ const navigationHandler = action => {
|
|||
routeStore.actions.navigate(action.parameters.url)
|
||||
}
|
||||
|
||||
const queryExecutionHandler = async (action, context) => {
|
||||
const { datasourceId, queryId, queryParams } = action.parameters
|
||||
|
||||
const enrichedQueryParameters = enrichDataBindings(queryParams, context)
|
||||
|
||||
await executeQuery({
|
||||
datasourceId,
|
||||
queryId,
|
||||
parameters: enrichedQueryParameters,
|
||||
})
|
||||
}
|
||||
|
||||
const handlerMap = {
|
||||
["Save Row"]: saveRowHandler,
|
||||
["Delete Row"]: deleteRowHandler,
|
||||
["Navigate To"]: navigationHandler,
|
||||
["Execute Query"]: queryExecutionHandler,
|
||||
["Trigger Automation"]: triggerAutomationHandler,
|
||||
}
|
||||
|
||||
|
|
|
@ -49,11 +49,13 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@elastic/elasticsearch": "^7.10.0",
|
||||
"@budibase/client": "^0.5.3",
|
||||
"@budibase/string-templates": "^0.5.3",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@sendgrid/mail": "^7.1.1",
|
||||
"@sentry/node": "^5.19.2",
|
||||
"airtable": "^0.10.1",
|
||||
"aws-sdk": "^2.767.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chmodr": "^1.2.0",
|
||||
|
@ -78,8 +80,11 @@
|
|||
"koa-session": "^5.12.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"lodash": "^4.17.13",
|
||||
"mongodb": "^3.6.3",
|
||||
"mssql": "^6.2.3",
|
||||
"node-fetch": "^2.6.0",
|
||||
"open": "^7.3.0",
|
||||
"pg": "^8.5.1",
|
||||
"pino-pretty": "^4.0.0",
|
||||
"pouchdb": "^7.2.1",
|
||||
"pouchdb-all-dbs": "^1.0.2",
|
||||
|
|
|
@ -3,7 +3,7 @@ const { exportTemplateFromApp } = require("../src/utilities/templates")
|
|||
const yargs = require("yargs")
|
||||
|
||||
// Script to export a chosen budibase app into a package
|
||||
// Usage: ./scripts/exportAppTemplate.js export --name=Funky --appId=someInstanceId --appId=appId
|
||||
// Usage: ./scripts/exportAppTemplate.js export --name=Funky --appId=appId
|
||||
|
||||
yargs
|
||||
.command(
|
||||
|
|
|
@ -12,8 +12,6 @@ const { createRoutingView } = require("../../utilities/routing")
|
|||
const { downloadTemplate } = require("../../utilities/templates")
|
||||
const {
|
||||
generateAppID,
|
||||
DocumentTypes,
|
||||
SEPARATOR,
|
||||
getLayoutParams,
|
||||
getScreenParams,
|
||||
generateScreenID,
|
||||
|
@ -32,9 +30,15 @@ const {
|
|||
} = require("../../constants/screens")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
const { processObject } = require("@budibase/string-templates")
|
||||
const { getAllApps } = require("../../utilities")
|
||||
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
||||
const {
|
||||
getDeployedApps,
|
||||
getHostingInfo,
|
||||
HostingTypes,
|
||||
} = require("../../utilities/builder/hosting")
|
||||
|
||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||
const URL_REGEX_SLASH = /\/|\\/g
|
||||
|
||||
// utility function, need to do away with this
|
||||
async function getLayouts(db) {
|
||||
|
@ -63,6 +67,28 @@ function getUserRoleId(ctx) {
|
|||
: ctx.user.role._id
|
||||
}
|
||||
|
||||
async function getAppUrlIfNotInUse(ctx) {
|
||||
let url
|
||||
if (ctx.request.body.url) {
|
||||
url = encodeURI(ctx.request.body.url)
|
||||
} else {
|
||||
url = encodeURI(`${ctx.request.body.name}`)
|
||||
}
|
||||
url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
|
||||
const hostingInfo = await getHostingInfo()
|
||||
if (hostingInfo.type === HostingTypes.CLOUD) {
|
||||
return url
|
||||
}
|
||||
const deployedApps = await getDeployedApps()
|
||||
if (
|
||||
deployedApps[url] != null &&
|
||||
deployedApps[url].appId !== ctx.params.appId
|
||||
) {
|
||||
ctx.throw(400, "App name/URL is already in use.")
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
async function createInstance(template) {
|
||||
const appId = generateAppID()
|
||||
|
||||
|
@ -96,17 +122,7 @@ async function createInstance(template) {
|
|||
}
|
||||
|
||||
exports.fetch = async function(ctx) {
|
||||
let allDbs = await CouchDB.allDbs()
|
||||
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
|
||||
const apps = appDbNames.map(db => new CouchDB(db).get(db))
|
||||
if (apps.length === 0) {
|
||||
ctx.body = []
|
||||
} else {
|
||||
const response = await Promise.allSettled(apps)
|
||||
ctx.body = response
|
||||
.filter(result => result.status === "fulfilled")
|
||||
.map(({ value }) => value)
|
||||
}
|
||||
ctx.body = await getAllApps()
|
||||
}
|
||||
|
||||
exports.fetchAppDefinition = async function(ctx) {
|
||||
|
@ -139,6 +155,7 @@ exports.fetchAppPackage = async function(ctx) {
|
|||
}
|
||||
|
||||
exports.create = async function(ctx) {
|
||||
const url = await getAppUrlIfNotInUse(ctx)
|
||||
const instance = await createInstance(ctx.request.body.template)
|
||||
const appId = instance._id
|
||||
const version = packageJson.version
|
||||
|
@ -148,6 +165,7 @@ exports.create = async function(ctx) {
|
|||
version: packageJson.version,
|
||||
componentLibraries: ["@budibase/standard-components"],
|
||||
name: ctx.request.body.name,
|
||||
url: url,
|
||||
template: ctx.request.body.template,
|
||||
instance: instance,
|
||||
deployment: {
|
||||
|
@ -169,11 +187,12 @@ exports.create = async function(ctx) {
|
|||
}
|
||||
|
||||
exports.update = async function(ctx) {
|
||||
const url = await getAppUrlIfNotInUse(ctx)
|
||||
const db = new CouchDB(ctx.params.appId)
|
||||
const application = await db.get(ctx.params.appId)
|
||||
|
||||
const data = ctx.request.body
|
||||
const newData = { ...application, ...data }
|
||||
const newData = { ...application, ...data, url }
|
||||
|
||||
const response = await db.put(newData)
|
||||
data._rev = response.rev
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -2,6 +2,7 @@ const CouchDB = require("../../db")
|
|||
const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants")
|
||||
const {
|
||||
getHostingInfo,
|
||||
getDeployedApps,
|
||||
HostingTypes,
|
||||
getAppUrl,
|
||||
} = require("../../utilities/builder/hosting")
|
||||
|
@ -37,3 +38,7 @@ exports.fetchUrls = async ctx => {
|
|||
app: await getAppUrl(ctx.appId),
|
||||
}
|
||||
}
|
||||
|
||||
exports.getDeployedApps = async ctx => {
|
||||
ctx.body = await getDeployedApps()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -175,7 +175,7 @@ exports.fetchView = async function(ctx) {
|
|||
const viewName = ctx.params.viewName
|
||||
|
||||
// if this is a table view being looked for just transfer to that
|
||||
if (viewName.indexOf(TABLE_VIEW_BEGINS_WITH) === 0) {
|
||||
if (viewName.startsWith(TABLE_VIEW_BEGINS_WITH)) {
|
||||
ctx.params.tableId = viewName.substring(4)
|
||||
await exports.fetchTableRows(ctx)
|
||||
return
|
||||
|
@ -217,6 +217,7 @@ exports.fetchView = async function(ctx) {
|
|||
|
||||
exports.fetchTableRows = async function(ctx) {
|
||||
const appId = ctx.user.appId
|
||||
|
||||
// special case for users, fetch through the user controller
|
||||
let rows
|
||||
if (ctx.params.tableId === ViewNames.USERS) {
|
||||
|
|
|
@ -12,6 +12,7 @@ const {
|
|||
budibaseAppsDir,
|
||||
budibaseTempDir,
|
||||
} = require("../../../utilities/budibaseDir")
|
||||
const { getDeployedApps } = require("../../../utilities/builder/hosting")
|
||||
const CouchDB = require("../../../db")
|
||||
const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
|
||||
const fileProcessor = require("../../../utilities/fileProcessor")
|
||||
|
@ -26,6 +27,17 @@ function objectStoreUrl() {
|
|||
}
|
||||
}
|
||||
|
||||
async function checkForSelfHostedURL(ctx) {
|
||||
// the "appId" component of the URL may actually be a specific self hosted URL
|
||||
let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}`
|
||||
const apps = await getDeployedApps()
|
||||
if (apps[possibleAppUrl] && apps[possibleAppUrl].appId) {
|
||||
return apps[possibleAppUrl].appId
|
||||
} else {
|
||||
return ctx.params.appId
|
||||
}
|
||||
}
|
||||
|
||||
// this was the version before we started versioning the component library
|
||||
const COMP_LIB_BASE_APP_VERSION = "0.2.5"
|
||||
|
||||
|
@ -149,14 +161,18 @@ exports.performLocalFileProcessing = async function(ctx) {
|
|||
}
|
||||
|
||||
exports.serveApp = async function(ctx) {
|
||||
let appId = ctx.params.appId
|
||||
if (env.SELF_HOSTED) {
|
||||
appId = await checkForSelfHostedURL(ctx)
|
||||
}
|
||||
const App = require("./templates/BudibaseApp.svelte").default
|
||||
const db = new CouchDB(ctx.params.appId)
|
||||
const appInfo = await db.get(ctx.params.appId)
|
||||
const db = new CouchDB(appId, { skip_setup: true })
|
||||
const appInfo = await db.get(appId)
|
||||
|
||||
const { head, html, css } = App.render({
|
||||
title: appInfo.name,
|
||||
production: env.CLOUD,
|
||||
appId: ctx.params.appId,
|
||||
appId,
|
||||
objectStoreUrl: objectStoreUrl(),
|
||||
})
|
||||
|
||||
|
@ -165,7 +181,7 @@ exports.serveApp = async function(ctx) {
|
|||
head,
|
||||
body: html,
|
||||
style: css.code,
|
||||
appId: ctx.params.appId,
|
||||
appId,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -173,8 +189,7 @@ exports.serveAttachment = async function(ctx) {
|
|||
const appId = ctx.user.appId
|
||||
const attachmentsPath = resolve(budibaseAppsDir(), appId, "attachments")
|
||||
|
||||
// Serve from CloudFront
|
||||
// TODO: need to replace this with link to self hosted object store
|
||||
// Serve from object store
|
||||
if (env.CLOUD) {
|
||||
const S3_URL = join(objectStoreUrl(), appId, "attachments", ctx.file)
|
||||
const response = await fetch(S3_URL)
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<title>{title}</title>
|
||||
<link rel="icon" type="image/png" href={favicon} />
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
|
|
|
@ -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
|
|
@ -1,6 +1,7 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../controllers/hosting")
|
||||
const authorized = require("../../middleware/authorized")
|
||||
const selfhost = require("../../middleware/selfhost")
|
||||
const { BUILDER } = require("../../utilities/security/permissions")
|
||||
|
||||
const router = Router()
|
||||
|
@ -10,5 +11,11 @@ router
|
|||
.get("/api/hosting/urls", authorized(BUILDER), controller.fetchUrls)
|
||||
.get("/api/hosting", authorized(BUILDER), controller.fetch)
|
||||
.post("/api/hosting", authorized(BUILDER), controller.save)
|
||||
.get(
|
||||
"/api/hosting/apps",
|
||||
authorized(BUILDER),
|
||||
selfhost,
|
||||
controller.getDeployedApps
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -16,7 +16,10 @@ const apiKeysRoutes = require("./apikeys")
|
|||
const templatesRoutes = require("./templates")
|
||||
const analyticsRoutes = require("./analytics")
|
||||
const routingRoutes = require("./routing")
|
||||
const integrationRoutes = require("./integration")
|
||||
const permissionRoutes = require("./permission")
|
||||
const datasourceRoutes = require("./datasource")
|
||||
const queryRoutes = require("./query")
|
||||
const hostingRoutes = require("./hosting")
|
||||
|
||||
exports.mainRoutes = [
|
||||
|
@ -34,7 +37,10 @@ exports.mainRoutes = [
|
|||
analyticsRoutes,
|
||||
webhookRoutes,
|
||||
routingRoutes,
|
||||
integrationRoutes,
|
||||
permissionRoutes,
|
||||
datasourceRoutes,
|
||||
queryRoutes,
|
||||
hostingRoutes,
|
||||
// these need to be handled last as they still use /api/:tableId
|
||||
// this could be breaking as koa may recognise other routes as this
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
|
@ -12,6 +12,7 @@ PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
|
|||
|
||||
let POUCH_DB_DEFAULTS = {
|
||||
prefix: COUCH_DB_URL,
|
||||
skip_setup: !!env.CLOUD,
|
||||
}
|
||||
|
||||
if (isInMemory) {
|
||||
|
|
|
@ -15,6 +15,8 @@ const DocumentTypes = {
|
|||
INSTANCE: "inst",
|
||||
LAYOUT: "layout",
|
||||
SCREEN: "screen",
|
||||
DATASOURCE: "datasource",
|
||||
QUERY: "query",
|
||||
}
|
||||
|
||||
const ViewNames = {
|
||||
|
@ -223,3 +225,43 @@ exports.generateWebhookID = () => {
|
|||
exports.getWebhookParams = (webhookId = null, otherProps = {}) => {
|
||||
return getDocParams(DocumentTypes.WEBHOOK, webhookId, otherProps)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new datasource ID.
|
||||
* @returns {string} The new datasource ID which the webhook doc can be stored under.
|
||||
*/
|
||||
exports.generateDatasourceID = () => {
|
||||
return `${DocumentTypes.DATASOURCE}${SEPARATOR}${newid()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving a datasource, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getDatasourceParams = (datasourceId = null, otherProps = {}) => {
|
||||
return getDocParams(DocumentTypes.DATASOURCE, datasourceId, otherProps)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new query ID.
|
||||
* @returns {string} The new query ID which the query doc can be stored under.
|
||||
*/
|
||||
exports.generateQueryID = datasourceId => {
|
||||
return `${
|
||||
DocumentTypes.QUERY
|
||||
}${SEPARATOR}${datasourceId}${SEPARATOR}${newid()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving a query, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getQueryParams = (datasourceId = null, otherProps = {}) => {
|
||||
if (datasourceId == null) {
|
||||
return getDocParams(DocumentTypes.QUERY, null, otherProps)
|
||||
}
|
||||
|
||||
return getDocParams(
|
||||
DocumentTypes.QUERY,
|
||||
`${datasourceId}${SEPARATOR}`,
|
||||
otherProps
|
||||
)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ module.exports = {
|
|||
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
|
||||
CLOUD: process.env.CLOUD,
|
||||
SELF_HOSTED: process.env.SELF_HOSTED,
|
||||
WORKER_URL: process.env.WORKER_URL,
|
||||
HOSTING_KEY: process.env.HOSTING_KEY,
|
||||
DYNAMO_ENDPOINT: process.env.DYNAMO_ENDPOINT,
|
||||
AWS_REGION: process.env.AWS_REGION,
|
||||
DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue