Merge branch 'master' of github.com:Budibase/budibase
This commit is contained in:
commit
79e6b89733
|
@ -103,6 +103,33 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "victoriasloan",
|
||||
"name": "victoriasloan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9913651?v=4",
|
||||
"profile": "https://github.com/victoriasloan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "yashank09",
|
||||
"name": "yashank09",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37672190?v=4",
|
||||
"profile": "https://github.com/yashank09",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SOVLOOKUP",
|
||||
"name": "SOVLOOKUP",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53158137?v=4",
|
||||
"profile": "https://github.com/SOVLOOKUP",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
|
|
@ -34,3 +34,14 @@ jobs:
|
|||
CI: true
|
||||
name: Budibase CI
|
||||
- run: yarn test:e2e:ci
|
||||
|
||||
- name: Build and Push Staging Docker Image
|
||||
# Only run on push
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
run: |
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
||||
yarn build:docker:staging
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||
|
||||
|
|
10
README.md
10
README.md
|
@ -86,6 +86,10 @@ Watch "releases" of this repo to get notified of major updates, and give the sta
|
|||
<img src="https://i.imgur.com/cJpgqm8.png">
|
||||
</p>
|
||||
|
||||
### Stargazers over time
|
||||
|
||||
[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
|
||||
|
||||
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment.
|
||||
|
||||
|
||||
|
@ -107,6 +111,8 @@ Budibase wants to make sure anyone can use the tools we develop and we know a lo
|
|||
|
||||
Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting).
|
||||
|
||||
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/droplets/new?onboarding_origin=marketplace&i=09038e&fleetUuid=bb04f9c8-1de8-4687-b2ae-1d5177a0535b&appId=77729671&type=applications&size=s-4vcpu-8gb®ion=nyc1&refcode=0caaa6085a82&image=budibase-20-04)
|
||||
|
||||
|
||||
## 🎓 Learning Budibase
|
||||
|
||||
|
@ -154,6 +160,7 @@ If you have a question or would like to talk with other Budibase users, please h
|
|||
|
||||
![Discord Shield](https://discordapp.com/api/guilds/733030666647765003/widget.png?style=shield)
|
||||
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
@ -174,6 +181,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
<tr>
|
||||
<td align="center"><a href="https://github.com/pngwn"><img src="https://avatars1.githubusercontent.com/u/12937446?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pngwn</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/HugoLd"><img src="https://avatars0.githubusercontent.com/u/26521848?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HugoLd</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=HugoLd" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/victoriasloan"><img src="https://avatars.githubusercontent.com/u/9913651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>victoriasloan</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=victoriasloan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/yashank09"><img src="https://avatars.githubusercontent.com/u/37672190?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yashank09</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=yashank09" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/SOVLOOKUP"><img src="https://avatars.githubusercontent.com/u/53158137?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SOVLOOKUP</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=SOVLOOKUP" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
#!/bin/bash
|
||||
|
||||
tag=$1
|
||||
tag=${tag:-latest}
|
||||
|
||||
pushd ../../build
|
||||
docker-compose build --force app-service
|
||||
docker-compose build --force worker-service
|
||||
docker tag build_app-service budibase/budibase-apps:latest
|
||||
|
||||
docker tag build_app-service budibase/budibase-apps:$tag
|
||||
docker tag build_worker-service budibase/budibase-worker:$tag
|
||||
|
||||
docker push budibase/budibase-apps
|
||||
docker tag build_worker-service budibase/budibase-worker:latest
|
||||
docker push budibase/budibase-worker
|
||||
popd
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
docker-compose --env-file hosting.properties pull && ./start.sh
|
|
@ -33,6 +33,7 @@
|
|||
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"",
|
||||
"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 -"
|
||||
"build:docker": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
|
||||
"build:docker:staging": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh staging && cd -"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
"codemirror": "^5.59.0",
|
||||
"d3-selection": "^1.4.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"downloadjs": "^1.4.7",
|
||||
"fast-sort": "^2.2.0",
|
||||
"lodash": "^4.17.13",
|
||||
"posthog-js": "1.4.5",
|
||||
|
|
|
@ -106,6 +106,16 @@ function highlightFeedbackIcon() {
|
|||
return isFeedbackTimeElapsed(firstRunStr)
|
||||
}
|
||||
|
||||
// Opt In/Out
|
||||
const ifAnalyticsEnabled = func => () => {
|
||||
if (analyticsEnabled && process.env.POSTHOG_TOKEN) {
|
||||
return func()
|
||||
}
|
||||
}
|
||||
const disabled = () => posthog.has_opted_out_capturing()
|
||||
const optIn = () => posthog.opt_in_capturing()
|
||||
const optOut = () => posthog.opt_out_capturing()
|
||||
|
||||
export default {
|
||||
activate,
|
||||
identify,
|
||||
|
@ -115,4 +125,7 @@ export default {
|
|||
requestFeedbackOnDeploy,
|
||||
submitFeedback,
|
||||
highlightFeedbackIcon,
|
||||
disabled: ifAnalyticsEnabled(disabled),
|
||||
optIn: ifAnalyticsEnabled(optIn),
|
||||
optOut: ifAnalyticsEnabled(optOut),
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { cloneDeep } from "lodash/fp"
|
|||
import { get } from "svelte/store"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
|
||||
import { TableNames } from "../constants"
|
||||
|
||||
// Regex to match all instances of template strings
|
||||
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
||||
|
@ -114,6 +115,37 @@ export const getContextBindings = (rootComponent, componentId) => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Add logged in user bindings
|
||||
const tables = get(backendUiStore).tables
|
||||
const userTable = tables.find(table => table._id === TableNames.USERS)
|
||||
const schema = {
|
||||
...userTable.schema,
|
||||
_id: { type: "string" },
|
||||
_rev: { type: "string" },
|
||||
}
|
||||
const keys = Object.keys(schema).sort()
|
||||
keys.forEach(key => {
|
||||
const fieldSchema = schema[key]
|
||||
// Replace certain bindings with a new property to help display components
|
||||
let runtimeBoundKey = key
|
||||
if (fieldSchema.type === "link") {
|
||||
runtimeBoundKey = `${key}_count`
|
||||
} else if (fieldSchema.type === "attachment") {
|
||||
runtimeBoundKey = `${key}_first`
|
||||
}
|
||||
|
||||
contextBindings.push({
|
||||
type: "context",
|
||||
runtimeBinding: `user.${runtimeBoundKey}`,
|
||||
readableBinding: `Current User.${key}`,
|
||||
fieldSchema,
|
||||
providerId: "user",
|
||||
tableId: TableNames.USERS,
|
||||
field: key,
|
||||
})
|
||||
})
|
||||
|
||||
return contextBindings
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
<Input
|
||||
value={field.name}
|
||||
secondary
|
||||
placeholder="Enter field name"
|
||||
on:change={fieldNameChanged(field.name)} />
|
||||
<Select
|
||||
secondary
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
let permissions = []
|
||||
let selectedRole = {}
|
||||
let errors = []
|
||||
let builtInRoles = ['Admin', 'Power', 'Basic', 'Public']
|
||||
$: selectedRoleId = selectedRole._id
|
||||
$: otherRoles = $backendUiStore.roles.filter(
|
||||
role => role._id !== selectedRoleId
|
||||
|
@ -102,7 +103,7 @@
|
|||
{/each}
|
||||
</Select>
|
||||
{#if selectedRole}
|
||||
<Input label="Name" bind:value={selectedRole.name} thin />
|
||||
<Input label="Name" bind:value={selectedRole.name} thin disabled={builtInRoles.includes(selectedRole.name)}/>
|
||||
<Select
|
||||
thin
|
||||
secondary
|
||||
|
|
|
@ -3,20 +3,39 @@
|
|||
export let height = "100"
|
||||
</script>
|
||||
|
||||
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" {width} {height} viewBox="0 0 2900 2000" >
|
||||
<g id="layer101">
|
||||
<path fill="#5b350f" d="M735 1983 c-137 -19 -322 -95 -431 -175 -138 -103 -250 -315 -284 -542 -24 -161 22 -298 129 -382 123 -97 318 -180 577 -243 l130 -32 29 -67 c41 -94 135 -231 199 -289 292 -267 732 -319 1094 -128 148 79 224 151 418 400 137 176 173 242 228 414 41 130 70 301 70 416 0 236 -146 421 -382 484 -206 56 -392 19 -733 -144 -96 -47 -181 -85 -189 -85 -7 0 -61 46 -119 103 -163 159 -283 231 -433 261 -71 15 -229 19 -303 9z"/>
|
||||
</g>
|
||||
<g id="layer102">
|
||||
<path fill="#406f23" d="M735 1983 c-137 -19 -322 -95 -431 -175 -138 -103 -250 -315 -284 -542 -24 -161 22 -298 129 -382 123 -97 318 -180 577 -243 l130 -32 29 -67 c41 -94 135 -231 199 -289 292 -267 732 -319 1094 -128 148 79 224 151 418 400 137 176 173 242 228 414 41 130 70 301 70 416 0 236 -146 421 -382 484 -206 56 -392 19 -733 -144 -96 -47 -181 -85 -189 -85 -7 0 -61 46 -119 103 -163 159 -283 231 -433 261 -71 15 -229 19 -303 9z m460 -643 c163 -51 296 -150 329 -246 23 -69 20 -93 -20 -174 -51 -100 -126 -173 -238 -230 l-89 -45 -131 0 c-151 0 -222 18 -323 84 -138 89 -243 264 -243 406 0 62 12 76 120 134 160 85 229 102 405 96 87 -2 139 -9 190 -25z"/>
|
||||
</g>
|
||||
<g id="layer103">
|
||||
<path fill="#c6d821" d="M2215 1789 c-27 -4 -68 -15 -90 -23 -42 -15 -264 -116 -397 -180 l-77 -38 78 -102 c102 -134 144 -208 175 -305 37 -116 49 -200 44 -309 -7 -157 -46 -218 -175 -278 -94 -44 -178 -56 -313 -43 -108 10 -401 53 -479 70 -24 5 -45 8 -47 6 -2 -2 20 -44 49 -92 67 -115 176 -225 281 -284 87 -49 235 -103 335 -121 91 -16 253 -14 346 5 175 37 269 95 419 264 307 347 422 563 457 859 18 148 9 247 -29 325 -88 179 -340 286 -577 246z"/>
|
||||
<path fill="#c6d821" d="M549 1680 c-272 -34 -426 -142 -495 -346 -22 -68 -26 -91 -22 -164 6 -115 32 -173 112 -248 70 -65 236 -153 370 -196 241 -79 722 -172 985 -192 180 -14 343 55 404 170 34 64 30 168 -11 271 -88 223 -245 373 -568 542 -222 116 -379 161 -584 168 -69 2 -155 0 -191 -5z m646 -340 c163 -51 296 -150 329 -246 23 -69 20 -93 -20 -174 -50 -100 -126 -173 -235 -229 -84 -42 -90 -44 -200 -48 -190 -8 -312 37 -434 161 -98 99 -155 221 -155 331 0 62 12 76 120 134 160 85 229 102 405 96 87 -2 139 -9 190 -25z"/>
|
||||
</g>
|
||||
<g id="layer104">
|
||||
<path fill="#f6f654" d="M2190 1721 c-57 -19 -469 -184 -494 -198 -12 -7 -5 -21 34 -72 28 -35 71 -96 97 -137 l46 -74 69 0 c38 0 83 -6 102 -14 52 -22 105 -75 139 -140 28 -55 32 -70 32 -147 0 -84 -2 -90 -48 -183 -96 -187 -234 -296 -376 -296 -44 0 -80 7 -110 20 -28 13 -65 20 -105 20 -72 0 -264 22 -426 49 -63 10 -117 17 -118 15 -10 -8 59 -113 110 -169 106 -115 238 -183 448 -231 155 -35 313 -29 447 17 78 27 210 117 279 191 82 89 277 353 336 456 92 159 137 344 125 513 -9 117 -37 189 -101 258 -109 116 -332 172 -486 122z"/>
|
||||
<path fill="#f6f654" d="M490 1624 c-153 -33 -275 -125 -339 -257 -54 -109 -59 -201 -17 -301 32 -76 164 -189 288 -245 51 -23 260 -91 281 -91 3 0 -21 25 -54 56 -94 91 -150 193 -170 313 -14 90 1 111 123 172 161 81 195 91 343 96 111 5 143 2 215 -16 221 -55 380 -192 380 -327 0 -52 -46 -143 -106 -212 -75 -85 -233 -170 -323 -173 -25 -1 -25 -1 4 -10 140 -43 411 -62 527 -38 142 30 238 125 238 234 0 88 -58 230 -139 337 -105 139 -431 343 -676 423 -161 52 -429 71 -575 39z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
{width}
|
||||
{height}
|
||||
viewBox="0 0 2900 2000">
|
||||
<g id="layer101">
|
||||
<path
|
||||
fill="#5b350f"
|
||||
d="M735 1983 c-137 -19 -322 -95 -431 -175 -138 -103 -250 -315 -284 -542 -24 -161 22 -298 129 -382 123 -97 318 -180 577 -243 l130 -32 29 -67 c41 -94 135 -231 199 -289 292 -267 732 -319 1094 -128 148 79 224 151 418 400 137 176 173 242 228 414 41 130 70 301 70 416 0 236 -146 421 -382 484 -206 56 -392 19 -733 -144 -96 -47 -181 -85 -189 -85 -7 0 -61 46 -119 103 -163 159 -283 231 -433 261 -71 15 -229 19 -303 9z" />
|
||||
</g>
|
||||
<g id="layer102">
|
||||
<path
|
||||
fill="#406f23"
|
||||
d="M735 1983 c-137 -19 -322 -95 -431 -175 -138 -103 -250 -315 -284 -542 -24 -161 22 -298 129 -382 123 -97 318 -180 577 -243 l130 -32 29 -67 c41 -94 135 -231 199 -289 292 -267 732 -319 1094 -128 148 79 224 151 418 400 137 176 173 242 228 414 41 130 70 301 70 416 0 236 -146 421 -382 484 -206 56 -392 19 -733 -144 -96 -47 -181 -85 -189 -85 -7 0 -61 46 -119 103 -163 159 -283 231 -433 261 -71 15 -229 19 -303 9z m460 -643 c163 -51 296 -150 329 -246 23 -69 20 -93 -20 -174 -51 -100 -126 -173 -238 -230 l-89 -45 -131 0 c-151 0 -222 18 -323 84 -138 89 -243 264 -243 406 0 62 12 76 120 134 160 85 229 102 405 96 87 -2 139 -9 190 -25z" />
|
||||
</g>
|
||||
<g id="layer103">
|
||||
<path
|
||||
fill="#c6d821"
|
||||
d="M2215 1789 c-27 -4 -68 -15 -90 -23 -42 -15 -264 -116 -397 -180 l-77 -38 78 -102 c102 -134 144 -208 175 -305 37 -116 49 -200 44 -309 -7 -157 -46 -218 -175 -278 -94 -44 -178 -56 -313 -43 -108 10 -401 53 -479 70 -24 5 -45 8 -47 6 -2 -2 20 -44 49 -92 67 -115 176 -225 281 -284 87 -49 235 -103 335 -121 91 -16 253 -14 346 5 175 37 269 95 419 264 307 347 422 563 457 859 18 148 9 247 -29 325 -88 179 -340 286 -577 246z" />
|
||||
<path
|
||||
fill="#c6d821"
|
||||
d="M549 1680 c-272 -34 -426 -142 -495 -346 -22 -68 -26 -91 -22 -164 6 -115 32 -173 112 -248 70 -65 236 -153 370 -196 241 -79 722 -172 985 -192 180 -14 343 55 404 170 34 64 30 168 -11 271 -88 223 -245 373 -568 542 -222 116 -379 161 -584 168 -69 2 -155 0 -191 -5z m646 -340 c163 -51 296 -150 329 -246 23 -69 20 -93 -20 -174 -50 -100 -126 -173 -235 -229 -84 -42 -90 -44 -200 -48 -190 -8 -312 37 -434 161 -98 99 -155 221 -155 331 0 62 12 76 120 134 160 85 229 102 405 96 87 -2 139 -9 190 -25z" />
|
||||
</g>
|
||||
<g id="layer104">
|
||||
<path
|
||||
fill="#f6f654"
|
||||
d="M2190 1721 c-57 -19 -469 -184 -494 -198 -12 -7 -5 -21 34 -72 28 -35 71 -96 97 -137 l46 -74 69 0 c38 0 83 -6 102 -14 52 -22 105 -75 139 -140 28 -55 32 -70 32 -147 0 -84 -2 -90 -48 -183 -96 -187 -234 -296 -376 -296 -44 0 -80 7 -110 20 -28 13 -65 20 -105 20 -72 0 -264 22 -426 49 -63 10 -117 17 -118 15 -10 -8 59 -113 110 -169 106 -115 238 -183 448 -231 155 -35 313 -29 447 17 78 27 210 117 279 191 82 89 277 353 336 456 92 159 137 344 125 513 -9 117 -37 189 -101 258 -109 116 -332 172 -486 122z" />
|
||||
<path
|
||||
fill="#f6f654"
|
||||
d="M490 1624 c-153 -33 -275 -125 -339 -257 -54 -109 -59 -201 -17 -301 32 -76 164 -189 288 -245 51 -23 260 -91 281 -91 3 0 -21 25 -54 56 -94 91 -150 193 -170 313 -14 90 1 111 123 172 161 81 195 91 343 96 111 5 143 2 215 -16 221 -55 380 -192 380 -327 0 -52 -46 -143 -106 -212 -75 -85 -233 -170 -323 -173 -25 -1 -25 -1 4 -10 140 -43 411 -62 527 -38 142 30 238 125 238 234 0 88 -58 230 -139 337 -105 139 -431 343 -676 423 -161 52 -429 71 -575 39z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { Input, Label } from "@budibase/bbui"
|
||||
import { Input, Label, TextButton } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { backendUiStore } from "builderStore"
|
||||
import analytics from "analytics"
|
||||
|
||||
|
@ -10,7 +11,7 @@
|
|||
if (key === "budibase") {
|
||||
const isValid = await analytics.identifyByApiKey(value)
|
||||
if (!isValid) {
|
||||
// TODO: add validation message
|
||||
notifier.danger("Your API Key is invalid.")
|
||||
keys = { ...keys }
|
||||
return
|
||||
}
|
||||
|
@ -38,7 +39,10 @@
|
|||
thin
|
||||
edit
|
||||
value={keys.budibase}
|
||||
label="Budibase API Key" />
|
||||
label="Budibase Cloud API Key" />
|
||||
<TextButton text medium blue href="https://portal.budi.live">
|
||||
Log in to the Budibase Hosting Portal to get your API Key. →
|
||||
</TextButton>
|
||||
<div>
|
||||
<Label extraSmall grey>Instance ID (Webhooks)</Label>
|
||||
<span>{$backendUiStore.selectedDatabase._id}</span>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
.notOneOf(existingAppUrls),
|
||||
}
|
||||
} else {
|
||||
nameValidation = { name: string.required(nameError) }
|
||||
nameValidation = { name: string().required(nameError) }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -2,7 +2,26 @@
|
|||
import { TextButton } from "@budibase/bbui"
|
||||
import { Heading } from "@budibase/bbui"
|
||||
import { Spacer } from "@budibase/bbui"
|
||||
import api from "builderStore/api"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import download from "downloadjs"
|
||||
|
||||
export let name, _id
|
||||
|
||||
let appExportLoading = false
|
||||
|
||||
async function exportApp() {
|
||||
appExportLoading = true
|
||||
try {
|
||||
download(`/api/backups/export?appId=${_id}`)
|
||||
notifier.success("App Export Complete.")
|
||||
} catch (err) {
|
||||
notifier.danger("App Export Failed.")
|
||||
} finally {
|
||||
appExportLoading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="apps-card">
|
||||
|
@ -14,6 +33,9 @@
|
|||
{name}
|
||||
→
|
||||
</TextButton>
|
||||
{#if appExportLoading}
|
||||
<Spinner size="10" />
|
||||
{:else}<i class="ri-folder-download-line" on:click={exportApp} />{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -31,7 +53,17 @@
|
|||
.card-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: var(--font-size-l);
|
||||
cursor: pointer;
|
||||
transition: 0.2s all;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
color: var(--blue);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
let hostingInfo
|
||||
let selfhosted = false
|
||||
|
||||
$: analyticsDisabled = analytics.disabled()
|
||||
|
||||
async function save() {
|
||||
hostingInfo.type = selfhosted ? HostingTypes.SELF : HostingTypes.CLOUD
|
||||
if (!selfhosted && hostingInfo._rev) {
|
||||
|
@ -35,6 +37,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
function toggleAnalytics() {
|
||||
if (analyticsDisabled) {
|
||||
analytics.optIn()
|
||||
} else {
|
||||
analytics.optOut()
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
hostingInfo = await hostingStore.actions.fetch()
|
||||
selfhosted = hostingInfo.type === "self"
|
||||
|
@ -59,6 +69,16 @@
|
|||
<Input bind:value={hostingInfo.selfHostKey} label="Hosting Key" />
|
||||
<Toggle thin text="HTTPS" bind:checked={hostingInfo.useHttps} />
|
||||
{/if}
|
||||
<h5>Analytics</h5>
|
||||
<p>
|
||||
If you would like to send analytics that help us make budibase better,
|
||||
please let us know below.
|
||||
</p>
|
||||
<Toggle
|
||||
thin
|
||||
text="Send Analytics To Budibase"
|
||||
checked={!analyticsDisabled}
|
||||
on:change={toggleAnalytics} />
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -22,36 +22,11 @@
|
|||
//Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly
|
||||
const createAppStore = writable({ currentStep: 0, values: {} })
|
||||
|
||||
export let hasKey
|
||||
export let template
|
||||
|
||||
let isApiKeyValid
|
||||
let lastApiKey
|
||||
let fetchApiKeyPromise
|
||||
|
||||
const validateApiKey = async apiKey => {
|
||||
if (isApiKeyValid) return true
|
||||
if (!apiKey) return false
|
||||
|
||||
// make sure we only fetch once, unless API Key is changed
|
||||
if (isApiKeyValid === undefined || apiKey !== lastApiKey) {
|
||||
lastApiKey = apiKey
|
||||
// svelte reactivity was causing a requst to get fired mutiple times
|
||||
// so, we make everything await the same promise, if one exists
|
||||
if (!fetchApiKeyPromise) {
|
||||
fetchApiKeyPromise = analytics.identifyByApiKey(apiKey)
|
||||
}
|
||||
isApiKeyValid = await fetchApiKeyPromise
|
||||
fetchApiKeyPromise = undefined
|
||||
}
|
||||
return isApiKeyValid
|
||||
}
|
||||
|
||||
const apiValidation = {
|
||||
apiKey: string()
|
||||
.required("Please enter your API key.")
|
||||
.test("valid-apikey", "This API key is invalid", validateApiKey),
|
||||
}
|
||||
const infoValidation = {
|
||||
applicationName: string().required("Your application must have a name."),
|
||||
}
|
||||
|
@ -66,7 +41,7 @@
|
|||
let submitting = false
|
||||
let errors = {}
|
||||
let validationErrors = {}
|
||||
let validationSchemas = [apiValidation, infoValidation, userValidation]
|
||||
let validationSchemas = [infoValidation, userValidation]
|
||||
|
||||
function buildStep(component) {
|
||||
return {
|
||||
|
@ -76,7 +51,7 @@
|
|||
}
|
||||
|
||||
// steps need to be initialized for cypress from the get go
|
||||
let steps = [buildStep(API), buildStep(Info), buildStep(User)]
|
||||
let steps = [buildStep(Info), buildStep(User)]
|
||||
|
||||
onMount(async () => {
|
||||
let hostingInfo = await hostingStore.actions.fetch()
|
||||
|
@ -87,17 +62,11 @@
|
|||
infoValidation.applicationName = string()
|
||||
.required("Your application must have a name.")
|
||||
.notOneOf(existingAppNames)
|
||||
isApiKeyValid = true
|
||||
steps = [buildStep(Info), buildStep(User)]
|
||||
validationSchemas = [infoValidation, userValidation]
|
||||
}
|
||||
})
|
||||
|
||||
if (hasKey) {
|
||||
validationSchemas.shift()
|
||||
steps.shift()
|
||||
}
|
||||
|
||||
// Handles form navigation
|
||||
const back = () => {
|
||||
if ($createAppStore.currentStep > 0) {
|
||||
|
@ -145,11 +114,6 @@
|
|||
async function createNewApp() {
|
||||
submitting = true
|
||||
try {
|
||||
// Add API key if there is none.
|
||||
if (!hasKey) {
|
||||
await updateKey(["budibase", $createAppStore.values.apiKey])
|
||||
}
|
||||
|
||||
// Create App
|
||||
const appResp = await post("/api/applications", {
|
||||
name: $createAppStore.values.applicationName,
|
||||
|
|
|
@ -1,14 +1,51 @@
|
|||
<script>
|
||||
import { Label, Heading, Input } from "@budibase/bbui"
|
||||
import Dropzone from "components/common/Dropzone.svelte"
|
||||
|
||||
const BYTES_IN_MB = 1000000
|
||||
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
|
||||
|
||||
export let validationErrors
|
||||
export let template
|
||||
|
||||
let blurred = { appName: false }
|
||||
let file
|
||||
|
||||
function handleFile(evt) {
|
||||
const fileArray = Array.from(evt.target.files)
|
||||
if (fileArray.some(file => file.size >= FILE_SIZE_LIMIT)) {
|
||||
notifier.danger(
|
||||
`Files cannot exceed ${FILE_SIZE_LIMIT /
|
||||
BYTES_IN_MB}MB. Please try again with smaller files.`
|
||||
)
|
||||
return
|
||||
}
|
||||
file = fileArray[0]
|
||||
template.fileImportPath = file.path
|
||||
}
|
||||
</script>
|
||||
|
||||
<h2>Create your Web App</h2>
|
||||
{#if template?.fromFile}
|
||||
<h2>Import Your Web App From A File</h2>
|
||||
{:else}
|
||||
<h2>Create your Web App</h2>
|
||||
{/if}
|
||||
<div class="container">
|
||||
{#if template}
|
||||
{#if template?.fromFile}
|
||||
<div class="template">
|
||||
<Label extraSmall grey>Import File</Label>
|
||||
<div class="dropzone">
|
||||
<input
|
||||
id="file-upload"
|
||||
accept=".txt"
|
||||
type="file"
|
||||
on:change={handleFile} />
|
||||
<label for="file-upload" class:uploaded={file}>
|
||||
{#if file}{file.name}{:else}Import{/if}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{:else if template}
|
||||
<div class="template">
|
||||
<Label extraSmall grey>Selected Template</Label>
|
||||
<Heading small>{template.name}</Heading>
|
||||
|
@ -33,4 +70,48 @@
|
|||
/* Fix layout due to LH 0 on heading */
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.uploaded {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius-s);
|
||||
color: var(--ink);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
transition: all 0.2s ease 0s;
|
||||
display: inline-flex;
|
||||
text-rendering: optimizeLegibility;
|
||||
min-width: auto;
|
||||
outline: none;
|
||||
font-feature-settings: "case" 1, "rlig" 1, "calt" 0;
|
||||
-webkit-box-align: center;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background-color: var(--grey-2);
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: normal;
|
||||
border: var(--border-transparent);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,6 +17,18 @@
|
|||
$: appId = $store.appId
|
||||
|
||||
async function deployApp() {
|
||||
// Must have cloud or self host API key to deploy
|
||||
if (!$hostingStore.hostingInfo?.selfHostKey) {
|
||||
const response = await api.get(`/api/keys/`)
|
||||
const userKeys = await response.json()
|
||||
if (!userKeys.budibase) {
|
||||
notifier.danger(
|
||||
"No budibase API Keys configured. You must set either a self hosted or cloud API key to deploy your budibase app."
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const DEPLOY_URL = `/api/deploy`
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<script>
|
||||
import { store } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import AppList from "components/start/AppList.svelte"
|
||||
import { get } from "builderStore/api"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
import { Button, Heading, Modal } from "@budibase/bbui"
|
||||
import { Button, Heading, Modal, Spacer } from "@budibase/bbui"
|
||||
import TemplateList from "components/start/TemplateList.svelte"
|
||||
import analytics from "analytics"
|
||||
|
||||
|
@ -35,10 +36,6 @@
|
|||
hasKey = true
|
||||
analytics.identify(keys.userId)
|
||||
}
|
||||
|
||||
if (!keys.budibase) {
|
||||
modal.show()
|
||||
}
|
||||
}
|
||||
|
||||
function selectTemplate(newTemplate) {
|
||||
|
@ -46,13 +43,22 @@
|
|||
modal.show()
|
||||
}
|
||||
|
||||
function initiateAppImport() {
|
||||
template = { fromFile: true }
|
||||
modal.show()
|
||||
}
|
||||
|
||||
checkIfKeysAndApps()
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<Heading medium black>Welcome to the Budibase Beta</Heading>
|
||||
<Button primary on:click={modal.show}>Create New Web App</Button>
|
||||
<div class="button-group">
|
||||
<Button secondary on:click={initiateAppImport}>Import Web App</Button>
|
||||
<Spacer medium />
|
||||
<Button primary on:click={modal.show}>Create New Web App</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="banner">
|
||||
|
@ -106,4 +112,9 @@
|
|||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,9 +20,10 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
|
|||
const requestBody = json ? JSON.stringify(body) : body
|
||||
let headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
...(json && { "Content-Type": "application/json" }),
|
||||
"x-budibase-app-id": window["##BUDIBASE_APP_ID##"],
|
||||
}
|
||||
|
||||
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||
headers["x-budibase-type"] = "client"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import API from "./api"
|
||||
import { enrichRows } from "./rows"
|
||||
import { TableNames } from "../constants"
|
||||
|
||||
/**
|
||||
* Performs a log in request.
|
||||
|
@ -15,3 +17,15 @@ export const logIn = async ({ email, password }) => {
|
|||
body: { email, password },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the currently logged in user object
|
||||
*/
|
||||
export const fetchSelf = async () => {
|
||||
const user = await API.get({ url: "/api/self" })
|
||||
if (user?._id) {
|
||||
return (await enrichRows([user], TableNames.USERS))[0]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,7 @@
|
|||
import Component from "./Component.svelte"
|
||||
import NotificationDisplay from "./NotificationDisplay.svelte"
|
||||
import SDK from "../sdk"
|
||||
import {
|
||||
createDataStore,
|
||||
initialise,
|
||||
screenStore,
|
||||
notificationStore,
|
||||
} from "../store"
|
||||
import { createDataStore, initialise, screenStore, authStore } from "../store"
|
||||
|
||||
// Provide contexts
|
||||
setContext("sdk", SDK)
|
||||
|
@ -22,6 +17,7 @@
|
|||
// Load app config
|
||||
onMount(async () => {
|
||||
await initialise()
|
||||
await authStore.actions.fetchUser()
|
||||
loaded = true
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import * as ComponentLibrary from "@budibase/standard-components"
|
||||
import Router from "./Router.svelte"
|
||||
import { enrichProps, propsAreSame } from "../utils/componentProps"
|
||||
import { bindingStore, builderStore } from "../store"
|
||||
import { authStore, bindingStore, builderStore } from "../store"
|
||||
|
||||
export let definition = {}
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
|||
$: constructor = getComponentConstructor(definition._component)
|
||||
$: children = definition._children || []
|
||||
$: id = definition._id
|
||||
$: enrichComponentProps(definition, $dataContext, $bindingStore)
|
||||
$: enrichComponentProps(definition, $dataContext, $bindingStore, $authStore)
|
||||
$: updateProps(enrichedProps)
|
||||
$: styles = definition._styles
|
||||
|
||||
|
@ -67,8 +67,8 @@
|
|||
}
|
||||
|
||||
// Enriches any string component props using handlebars
|
||||
const enrichComponentProps = async (definition, context, bindingStore) => {
|
||||
enrichedProps = await enrichProps(definition, context, bindingStore)
|
||||
const enrichComponentProps = async (definition, context, bindings, user) => {
|
||||
enrichedProps = await enrichProps(definition, context, bindings, user)
|
||||
}
|
||||
|
||||
// Returns a unique key to let svelte know when to remount components.
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const TableNames = {
|
||||
USERS: "ta_users",
|
||||
}
|
|
@ -3,9 +3,10 @@ import { writable, get } from "svelte/store"
|
|||
import { initialise } from "./initialise"
|
||||
import { routeStore } from "./routes"
|
||||
import { builderStore } from "./builder"
|
||||
import { TableNames } from "../constants"
|
||||
|
||||
const createAuthStore = () => {
|
||||
const store = writable("")
|
||||
const store = writable(null)
|
||||
|
||||
const goToDefaultRoute = () => {
|
||||
// Setting the active route forces an update of the active screen ID,
|
||||
|
@ -15,16 +16,20 @@ const createAuthStore = () => {
|
|||
// Navigating updates the URL to reflect this route
|
||||
routeStore.actions.navigate("/")
|
||||
}
|
||||
|
||||
// Logs a user in
|
||||
const logIn = async ({ email, password }) => {
|
||||
const user = await API.logIn({ email, password })
|
||||
if (!user.error) {
|
||||
store.set(user.token)
|
||||
store.set(user)
|
||||
await initialise()
|
||||
goToDefaultRoute()
|
||||
}
|
||||
}
|
||||
|
||||
// Logs a user out
|
||||
const logOut = async () => {
|
||||
store.set("")
|
||||
store.set(null)
|
||||
const appId = get(builderStore).appId
|
||||
if (appId) {
|
||||
for (let environment of ["local", "cloud"]) {
|
||||
|
@ -35,9 +40,26 @@ const createAuthStore = () => {
|
|||
goToDefaultRoute()
|
||||
}
|
||||
|
||||
// Fetches the user object if someone is logged in and has reloaded the page
|
||||
const fetchUser = async () => {
|
||||
// Fetch the first user if inside the builder
|
||||
if (get(builderStore).inBuilder) {
|
||||
const users = await API.fetchTableData(TableNames.USERS)
|
||||
if (!users.error && users[0] != null) {
|
||||
store.set(users[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Or fetch the current user from localstorage in a real app
|
||||
else {
|
||||
const user = await API.fetchSelf()
|
||||
store.set(user)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: { logIn, logOut },
|
||||
actions: { logIn, logOut, fetchUser },
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => {
|
|||
* Enriches component props.
|
||||
* Data bindings are enriched, and button actions are enriched.
|
||||
*/
|
||||
export const enrichProps = async (props, dataContexts, dataBindings) => {
|
||||
export const enrichProps = async (props, dataContexts, dataBindings, user) => {
|
||||
// Exclude all private props that start with an underscore
|
||||
let validProps = {}
|
||||
Object.entries(props)
|
||||
|
@ -35,6 +35,7 @@ export const enrichProps = async (props, dataContexts, dataBindings) => {
|
|||
const context = {
|
||||
...dataContexts,
|
||||
...dataBindings,
|
||||
user,
|
||||
data: dataContexts[dataContexts.closestComponentId],
|
||||
data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
"maintainer": "Budibase",
|
||||
"icon": "./build/icons/",
|
||||
"target": [
|
||||
"deb"
|
||||
"deb",
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
|
|
|
@ -105,10 +105,16 @@ async function createInstance(template) {
|
|||
|
||||
// replicate the template data to the instance DB
|
||||
if (template) {
|
||||
const templatePath = await downloadTemplate(...template.key.split("/"))
|
||||
const dbDumpReadStream = fs.createReadStream(
|
||||
join(templatePath, "db", "dump.txt")
|
||||
)
|
||||
let dbDumpReadStream
|
||||
|
||||
if (template.fileImportPath) {
|
||||
dbDumpReadStream = fs.createReadStream(template.fileImportPath)
|
||||
} else {
|
||||
const templatePath = await downloadTemplate(...template.key.split("/"))
|
||||
dbDumpReadStream = fs.createReadStream(
|
||||
join(templatePath, "db", "dump.txt")
|
||||
)
|
||||
}
|
||||
const { ok } = await db.load(dbDumpReadStream)
|
||||
if (!ok) {
|
||||
throw "Error loading database dump from template."
|
||||
|
|
|
@ -57,3 +57,17 @@ exports.authenticate = async ctx => {
|
|||
ctx.throw(401, "Invalid credentials.")
|
||||
}
|
||||
}
|
||||
|
||||
exports.fetchSelf = async ctx => {
|
||||
const { userId, appId } = ctx.user
|
||||
if (!userId || !appId) {
|
||||
ctx.body = {}
|
||||
} else {
|
||||
const database = new CouchDB(appId)
|
||||
const user = await database.get(userId)
|
||||
if (user) {
|
||||
delete user.password
|
||||
}
|
||||
ctx.body = user
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
const { performDump } = require("../../utilities/templates")
|
||||
const path = require("path")
|
||||
const os = require("os")
|
||||
const fs = require("fs-extra")
|
||||
|
||||
exports.exportAppDump = async function(ctx) {
|
||||
const { appId } = ctx.query
|
||||
|
||||
const backupsDir = path.join(os.homedir(), ".budibase", "backups")
|
||||
fs.ensureDirSync(backupsDir)
|
||||
|
||||
const backupIdentifier = `${appId} Backup: ${new Date()}.txt`
|
||||
|
||||
await performDump({
|
||||
dir: backupsDir,
|
||||
appId,
|
||||
name: backupIdentifier,
|
||||
})
|
||||
|
||||
ctx.status = 200
|
||||
|
||||
const backupFile = path.join(backupsDir, backupIdentifier)
|
||||
|
||||
ctx.attachment(backupIdentifier)
|
||||
ctx.body = fs.createReadStream(backupFile)
|
||||
// ctx.body = {
|
||||
// url: `/api/backups/download/${backupIdentifier}`,
|
||||
// }
|
||||
}
|
||||
|
||||
// exports.downloadAppDump = async function(ctx) {
|
||||
// const fileName = ctx.params.fileName
|
||||
|
||||
// const backupsDir = path.join(os.homedir(), ".budibase", "backups")
|
||||
// fs.ensureDirSync(backupsDir)
|
||||
|
||||
// const backupFile = path.join(backupsDir, fileName)
|
||||
|
||||
// ctx.attachment(fileName)
|
||||
// ctx.body = fs.createReadStream(backupFile)
|
||||
// }
|
|
@ -177,7 +177,6 @@ exports.serveApp = async function(ctx) {
|
|||
})
|
||||
|
||||
const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8")
|
||||
console.log(appHbs)
|
||||
ctx.body = await processString(appHbs, {
|
||||
head,
|
||||
body: html,
|
||||
|
|
|
@ -83,7 +83,7 @@ const controller = {
|
|||
ctx.message = `View ${ctx.params.viewName} saved successfully.`
|
||||
},
|
||||
exportView: async ctx => {
|
||||
const view = ctx.request.body
|
||||
const view = ctx.query.view
|
||||
const format = ctx.query.format
|
||||
|
||||
// Fetch view rows
|
||||
|
@ -102,14 +102,6 @@ const controller = {
|
|||
const filename = `${view.name}.${format}`
|
||||
fs.writeFileSync(join(os.tmpdir(), filename), exportedFile)
|
||||
|
||||
ctx.body = {
|
||||
url: `/api/views/export/download/${filename}`,
|
||||
name: view.name,
|
||||
}
|
||||
},
|
||||
downloadExport: async ctx => {
|
||||
const filename = ctx.params.fileName
|
||||
|
||||
ctx.attachment(filename)
|
||||
ctx.body = fs.createReadStream(join(os.tmpdir(), filename))
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@ const zlib = require("zlib")
|
|||
const { budibaseAppsDir } = require("../utilities/budibaseDir")
|
||||
const { isDev } = require("../utilities")
|
||||
const { mainRoutes, authRoutes, staticRoutes } = require("./routes")
|
||||
const pkg = require("../../package.json")
|
||||
|
||||
const router = new Router()
|
||||
const env = require("../environment")
|
||||
|
@ -32,6 +33,7 @@ router
|
|||
await next()
|
||||
})
|
||||
.use("/health", ctx => (ctx.status = 200))
|
||||
.use("/version", ctx => (ctx.body = pkg.version))
|
||||
.use(authenticated)
|
||||
|
||||
// error handling middleware
|
||||
|
|
|
@ -4,5 +4,6 @@ const controller = require("../controllers/auth")
|
|||
const router = Router()
|
||||
|
||||
router.post("/api/authenticate", controller.authenticate)
|
||||
router.get("/api/self", controller.fetchSelf)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../controllers/backup")
|
||||
const authorized = require("../../middleware/authorized")
|
||||
const { BUILDER } = require("../../utilities/security/permissions")
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get("/api/backups/export", authorized(BUILDER), controller.exportAppDump)
|
||||
// .get(
|
||||
// "/api/backups/download/:fileName",
|
||||
// authorized(BUILDER),
|
||||
// controller.downloadAppDump
|
||||
// )
|
||||
|
||||
module.exports = router
|
|
@ -21,6 +21,7 @@ const permissionRoutes = require("./permission")
|
|||
const datasourceRoutes = require("./datasource")
|
||||
const queryRoutes = require("./query")
|
||||
const hostingRoutes = require("./hosting")
|
||||
const backupRoutes = require("./backup")
|
||||
|
||||
exports.mainRoutes = [
|
||||
deployRoutes,
|
||||
|
@ -42,6 +43,7 @@ exports.mainRoutes = [
|
|||
datasourceRoutes,
|
||||
queryRoutes,
|
||||
hostingRoutes,
|
||||
backupRoutes,
|
||||
// these need to be handled last as they still use /api/:tableId
|
||||
// this could be breaking as koa may recognise other routes as this
|
||||
tableRoutes,
|
||||
|
|
|
@ -26,10 +26,5 @@ router
|
|||
)
|
||||
.post("/api/views", authorized(BUILDER), usage, viewController.save)
|
||||
.post("/api/views/export", authorized(BUILDER), viewController.exportView)
|
||||
.get(
|
||||
"/api/views/export/download/:fileName",
|
||||
authorized(BUILDER),
|
||||
viewController.downloadExport
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -10,7 +10,6 @@ const streamPipeline = promisify(stream.pipeline)
|
|||
const { budibaseAppsDir } = require("./budibaseDir")
|
||||
const env = require("../environment")
|
||||
const CouchDB = require("../db")
|
||||
const { DocumentTypes } = require("../db/utils")
|
||||
|
||||
const DEFAULT_TEMPLATES_BUCKET =
|
||||
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
||||
|
@ -56,6 +55,15 @@ exports.downloadTemplate = async function(type, name) {
|
|||
return dirName
|
||||
}
|
||||
|
||||
async function performDump({ dir, appId, name = "dump.txt" }) {
|
||||
const writeStream = fs.createWriteStream(join(dir, name))
|
||||
// perform couch dump
|
||||
const instanceDb = new CouchDB(appId)
|
||||
await instanceDb.dump(writeStream, {})
|
||||
}
|
||||
|
||||
exports.performDump = performDump
|
||||
|
||||
exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
||||
// Copy frontend files
|
||||
const templatesDir = join(
|
||||
|
@ -67,13 +75,6 @@ exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
|||
"db"
|
||||
)
|
||||
fs.ensureDirSync(templatesDir)
|
||||
const writeStream = fs.createWriteStream(join(templatesDir, "dump.txt"))
|
||||
// perform couch dump
|
||||
const instanceDb = new CouchDB(appId)
|
||||
await instanceDb.dump(writeStream, {
|
||||
filter: doc => {
|
||||
return !doc._id.startsWith(DocumentTypes.USER)
|
||||
},
|
||||
})
|
||||
await performDump({ dir: templatesDir, appId })
|
||||
return templatesDir
|
||||
}
|
||||
|
|
|
@ -947,6 +947,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.8.tgz#fe2012f2355e4ce08bca44aeb3abbb21cf88d33f"
|
||||
integrity sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==
|
||||
|
||||
"@types/node@>=13.13.4":
|
||||
version "14.14.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18"
|
||||
integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==
|
||||
|
||||
"@types/node@>=8.0.0 <15":
|
||||
version "14.14.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340"
|
||||
|
@ -1278,6 +1283,17 @@ app-builder-lib@22.9.1:
|
|||
semver "^7.3.2"
|
||||
temp-file "^3.3.7"
|
||||
|
||||
arangojs@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/arangojs/-/arangojs-7.2.0.tgz#e576926b4b3469c5a130cceba45fada8b5f015d1"
|
||||
integrity sha512-9mQRCcttaG0lckapNF9TA71ZU7H2ATXK2a1w+0fj+Y4TlTP1bNDMIz3ZN+EnaSgEtwVu0rb6N6Ac97Yd56GmkQ==
|
||||
dependencies:
|
||||
"@types/node" ">=13.13.4"
|
||||
es6-error "^4.0.1"
|
||||
multi-part "^3.0.0"
|
||||
x3-linkedlist "1.2.0"
|
||||
xhr "^2.4.1"
|
||||
|
||||
archive-type@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/archive-type/-/archive-type-4.0.0.tgz#f92e72233056dfc6969472749c267bdb046b1d70"
|
||||
|
@ -2759,7 +2775,7 @@ es3ify@^0.2.2:
|
|||
jstransform "~11.0.0"
|
||||
through "~2.3.4"
|
||||
|
||||
es6-error@^4.1.1:
|
||||
es6-error@^4.0.1, es6-error@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
||||
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
|
||||
|
@ -3173,6 +3189,11 @@ file-type@^11.1.0:
|
|||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8"
|
||||
integrity sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==
|
||||
|
||||
file-type@^12.1.0:
|
||||
version "12.4.2"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9"
|
||||
integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==
|
||||
|
||||
file-type@^3.8.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||
|
@ -3524,6 +3545,14 @@ global@~4.3.0:
|
|||
min-document "^2.19.0"
|
||||
process "~0.5.1"
|
||||
|
||||
global@~4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
|
||||
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
|
||||
dependencies:
|
||||
min-document "^2.19.0"
|
||||
process "^0.11.10"
|
||||
|
||||
globals@^11.1.0:
|
||||
version "11.12.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
|
@ -5441,11 +5470,19 @@ mime-db@1.44.0:
|
|||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
|
||||
integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
|
||||
|
||||
"mime-db@>= 1.43.0 < 2", mime-db@^1.28.0:
|
||||
mime-db@1.45.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0:
|
||||
version "1.45.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
|
||||
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
|
||||
|
||||
mime-kind@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-kind/-/mime-kind-3.0.0.tgz#23bb3aba03ed6a1ea8c4f6093a9c7ab7121a9cb2"
|
||||
integrity sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==
|
||||
dependencies:
|
||||
file-type "^12.1.0"
|
||||
mime-types "^2.1.24"
|
||||
|
||||
mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.27"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
|
||||
|
@ -5453,6 +5490,13 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24:
|
|||
dependencies:
|
||||
mime-db "1.44.0"
|
||||
|
||||
mime-types@^2.1.24:
|
||||
version "2.1.28"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"
|
||||
integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==
|
||||
dependencies:
|
||||
mime-db "1.45.0"
|
||||
|
||||
mime@^1.3.4, mime@^1.4.1:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
|
@ -5549,6 +5593,19 @@ mssql@^6.2.3:
|
|||
tarn "^1.1.5"
|
||||
tedious "^6.6.2"
|
||||
|
||||
multi-part-lite@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/multi-part-lite/-/multi-part-lite-1.0.0.tgz#7b86baf8ff83ef20ca13f1269a0f35aec42b9000"
|
||||
integrity sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==
|
||||
|
||||
multi-part@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/multi-part/-/multi-part-3.0.0.tgz#2bde386e8c1dcc9f15a2277267a7f5ed13aa0cc0"
|
||||
integrity sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==
|
||||
dependencies:
|
||||
mime-kind "^3.0.0"
|
||||
multi-part-lite "^1.0.0"
|
||||
|
||||
mute-stream@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
|
@ -6452,6 +6509,11 @@ process-nextick-args@~2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
process@^0.11.10:
|
||||
version "0.11.10"
|
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
|
||||
|
||||
process@~0.5.1:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
|
||||
|
@ -8264,6 +8326,11 @@ ws@^5.2.0:
|
|||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
x3-linkedlist@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz#c70467559b7c748595f0f79222af1d709402699e"
|
||||
integrity sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==
|
||||
|
||||
xdg-basedir@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||
|
@ -8279,6 +8346,16 @@ xhr@^2.0.1:
|
|||
parse-headers "^2.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
xhr@^2.4.1:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d"
|
||||
integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==
|
||||
dependencies:
|
||||
global "~4.4.0"
|
||||
is-function "^1.0.1"
|
||||
parse-headers "^2.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"license": "MIT",
|
||||
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491",
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.52.4",
|
||||
"@budibase/bbui": "^1.55.1",
|
||||
"@budibase/svelte-ag-grid": "^0.0.16",
|
||||
"apexcharts": "^3.22.1",
|
||||
"flatpickr": "^4.6.6",
|
||||
|
|
|
@ -135,8 +135,7 @@
|
|||
}
|
||||
|
||||
const updateRow = async row => {
|
||||
const schema = (await API.fetchTableDefinition(row.tableId)).schema
|
||||
await API.updateRow(schema, { data: row })
|
||||
await API.updateRow(row)
|
||||
}
|
||||
|
||||
const deleteRows = async () => {
|
||||
|
|
|
@ -39,10 +39,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.55.1":
|
||||
version "1.55.1"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.55.1.tgz#291fb6fa10479b49f078d3a911ad0ed42c2e6b12"
|
||||
integrity sha512-bxsHBwkOqCtuFz89e0hAXwvwycfS4xPPrEge5PxK1Lh3uqetO4bXoIxYaIDjfi2Ku7CYIzEmOwSloNaQWeTF4g==
|
||||
dependencies:
|
||||
markdown-it "^12.0.2"
|
||||
quill "^1.3.7"
|
||||
|
|
Loading…
Reference in New Issue