Merge branch 'master' of github.com:Budibase/budibase into feature/handlebars-helpers
This commit is contained in:
commit
de1a497508
|
@ -103,6 +103,33 @@
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"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,
|
"contributorsPerLine": 7,
|
||||||
|
|
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">
|
<img src="https://i.imgur.com/cJpgqm8.png">
|
||||||
</p>
|
</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.
|
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).
|
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
|
## 🎓 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)
|
![Discord Shield](https://discordapp.com/api/guilds/733030666647765003/widget.png?style=shield)
|
||||||
|
|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
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>
|
<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/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/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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
docker-compose --env-file hosting.properties pull && ./start.sh
|
|
@ -74,6 +74,7 @@
|
||||||
"codemirror": "^5.59.0",
|
"codemirror": "^5.59.0",
|
||||||
"d3-selection": "^1.4.1",
|
"d3-selection": "^1.4.1",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
|
"downloadjs": "^1.4.7",
|
||||||
"fast-sort": "^2.2.0",
|
"fast-sort": "^2.2.0",
|
||||||
"lodash": "^4.17.13",
|
"lodash": "^4.17.13",
|
||||||
"posthog-js": "1.4.5",
|
"posthog-js": "1.4.5",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { get } from "svelte/store"
|
||||||
import { backendUiStore, store } from "builderStore"
|
import { backendUiStore, store } from "builderStore"
|
||||||
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
|
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
|
||||||
import { makePropSafe } from "@budibase/string-templates"
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
// Regex to match all instances of template strings
|
// Regex to match all instances of template strings
|
||||||
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
||||||
|
@ -118,6 +119,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
|
return contextBindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
let permissions = []
|
let permissions = []
|
||||||
let selectedRole = {}
|
let selectedRole = {}
|
||||||
let errors = []
|
let errors = []
|
||||||
|
let builtInRoles = ['Admin', 'Power', 'Basic', 'Public']
|
||||||
$: selectedRoleId = selectedRole._id
|
$: selectedRoleId = selectedRole._id
|
||||||
$: otherRoles = $backendUiStore.roles.filter(
|
$: otherRoles = $backendUiStore.roles.filter(
|
||||||
role => role._id !== selectedRoleId
|
role => role._id !== selectedRoleId
|
||||||
|
@ -102,7 +103,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
{#if selectedRole}
|
{#if selectedRole}
|
||||||
<Input label="Name" bind:value={selectedRole.name} thin />
|
<Input label="Name" bind:value={selectedRole.name} thin disabled={builtInRoles.includes(selectedRole.name)}/>
|
||||||
<Select
|
<Select
|
||||||
thin
|
thin
|
||||||
secondary
|
secondary
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
.notOneOf(existingAppUrls),
|
.notOneOf(existingAppUrls),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nameValidation = { name: string.required(nameError) }
|
nameValidation = { name: string().required(nameError) }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,7 +2,26 @@
|
||||||
import { TextButton } from "@budibase/bbui"
|
import { TextButton } from "@budibase/bbui"
|
||||||
import { Heading } from "@budibase/bbui"
|
import { Heading } from "@budibase/bbui"
|
||||||
import { Spacer } 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
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="apps-card">
|
<div class="apps-card">
|
||||||
|
@ -14,6 +33,9 @@
|
||||||
{name}
|
{name}
|
||||||
→
|
→
|
||||||
</TextButton>
|
</TextButton>
|
||||||
|
{#if appExportLoading}
|
||||||
|
<Spinner size="10" />
|
||||||
|
{:else}<i class="ri-folder-download-line" on:click={exportApp} />{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -31,7 +53,17 @@
|
||||||
.card-footer {
|
.card-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: var(--font-size-l);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s all;
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,14 +1,51 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, Heading, Input } from "@budibase/bbui"
|
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 validationErrors
|
||||||
export let template
|
export let template
|
||||||
|
|
||||||
let blurred = { appName: false }
|
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>
|
</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">
|
<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">
|
<div class="template">
|
||||||
<Label extraSmall grey>Selected Template</Label>
|
<Label extraSmall grey>Selected Template</Label>
|
||||||
<Heading small>{template.name}</Heading>
|
<Heading small>{template.name}</Heading>
|
||||||
|
@ -33,4 +70,48 @@
|
||||||
/* Fix layout due to LH 0 on heading */
|
/* Fix layout due to LH 0 on heading */
|
||||||
margin-bottom: 16px;
|
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>
|
</style>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import AppList from "components/start/AppList.svelte"
|
import AppList from "components/start/AppList.svelte"
|
||||||
import { get } from "builderStore/api"
|
import { get } from "builderStore/api"
|
||||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
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 TemplateList from "components/start/TemplateList.svelte"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
|
@ -43,13 +43,22 @@
|
||||||
modal.show()
|
modal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initiateAppImport() {
|
||||||
|
template = { fromFile: true }
|
||||||
|
modal.show()
|
||||||
|
}
|
||||||
|
|
||||||
checkIfKeysAndApps()
|
checkIfKeysAndApps()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<Heading medium black>Welcome to the Budibase Beta</Heading>
|
<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>
|
||||||
|
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
|
@ -103,4 +112,9 @@
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
</style>
|
</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
|
const requestBody = json ? JSON.stringify(body) : body
|
||||||
let headers = {
|
let headers = {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json",
|
...(json && { "Content-Type": "application/json" }),
|
||||||
"x-budibase-app-id": window["##BUDIBASE_APP_ID##"],
|
"x-budibase-app-id": window["##BUDIBASE_APP_ID##"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||||
headers["x-budibase-type"] = "client"
|
headers["x-budibase-type"] = "client"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
|
import { enrichRows } from "./rows"
|
||||||
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a log in request.
|
* Performs a log in request.
|
||||||
|
@ -15,3 +17,15 @@ export const logIn = async ({ email, password }) => {
|
||||||
body: { 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 Component from "./Component.svelte"
|
||||||
import NotificationDisplay from "./NotificationDisplay.svelte"
|
import NotificationDisplay from "./NotificationDisplay.svelte"
|
||||||
import SDK from "../sdk"
|
import SDK from "../sdk"
|
||||||
import {
|
import { createDataStore, initialise, screenStore, authStore } from "../store"
|
||||||
createDataStore,
|
|
||||||
initialise,
|
|
||||||
screenStore,
|
|
||||||
notificationStore,
|
|
||||||
} from "../store"
|
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
setContext("sdk", SDK)
|
setContext("sdk", SDK)
|
||||||
|
@ -22,6 +17,7 @@
|
||||||
// Load app config
|
// Load app config
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await initialise()
|
await initialise()
|
||||||
|
await authStore.actions.fetchUser()
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import * as ComponentLibrary from "@budibase/standard-components"
|
import * as ComponentLibrary from "@budibase/standard-components"
|
||||||
import Router from "./Router.svelte"
|
import Router from "./Router.svelte"
|
||||||
import { enrichProps, propsAreSame } from "../utils/componentProps"
|
import { enrichProps, propsAreSame } from "../utils/componentProps"
|
||||||
import { bindingStore, builderStore } from "../store"
|
import { authStore, bindingStore, builderStore } from "../store"
|
||||||
|
|
||||||
export let definition = {}
|
export let definition = {}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
$: constructor = getComponentConstructor(definition._component)
|
$: constructor = getComponentConstructor(definition._component)
|
||||||
$: children = definition._children || []
|
$: children = definition._children || []
|
||||||
$: id = definition._id
|
$: id = definition._id
|
||||||
$: enrichComponentProps(definition, $dataContext, $bindingStore)
|
$: enrichComponentProps(definition, $dataContext, $bindingStore, $authStore)
|
||||||
$: updateProps(enrichedProps)
|
$: updateProps(enrichedProps)
|
||||||
$: styles = definition._styles
|
$: styles = definition._styles
|
||||||
|
|
||||||
|
@ -67,8 +67,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enriches any string component props using handlebars
|
// Enriches any string component props using handlebars
|
||||||
const enrichComponentProps = async (definition, context, bindingStore) => {
|
const enrichComponentProps = async (definition, context, bindings, user) => {
|
||||||
enrichedProps = await enrichProps(definition, context, bindingStore)
|
enrichedProps = await enrichProps(definition, context, bindings, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a unique key to let svelte know when to remount components.
|
// 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 { initialise } from "./initialise"
|
||||||
import { routeStore } from "./routes"
|
import { routeStore } from "./routes"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
const createAuthStore = () => {
|
const createAuthStore = () => {
|
||||||
const store = writable("")
|
const store = writable(null)
|
||||||
|
|
||||||
const goToDefaultRoute = () => {
|
const goToDefaultRoute = () => {
|
||||||
// Setting the active route forces an update of the active screen ID,
|
// 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
|
// Navigating updates the URL to reflect this route
|
||||||
routeStore.actions.navigate("/")
|
routeStore.actions.navigate("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logs a user in
|
||||||
const logIn = async ({ email, password }) => {
|
const logIn = async ({ email, password }) => {
|
||||||
const user = await API.logIn({ email, password })
|
const user = await API.logIn({ email, password })
|
||||||
if (!user.error) {
|
if (!user.error) {
|
||||||
store.set(user.token)
|
store.set(user)
|
||||||
await initialise()
|
await initialise()
|
||||||
goToDefaultRoute()
|
goToDefaultRoute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logs a user out
|
||||||
const logOut = async () => {
|
const logOut = async () => {
|
||||||
store.set("")
|
store.set(null)
|
||||||
const appId = get(builderStore).appId
|
const appId = get(builderStore).appId
|
||||||
if (appId) {
|
if (appId) {
|
||||||
for (let environment of ["local", "cloud"]) {
|
for (let environment of ["local", "cloud"]) {
|
||||||
|
@ -35,9 +40,26 @@ const createAuthStore = () => {
|
||||||
goToDefaultRoute()
|
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 {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
actions: { logIn, logOut },
|
actions: { logIn, logOut, fetchUser },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => {
|
||||||
* Enriches component props.
|
* Enriches component props.
|
||||||
* Data bindings are enriched, and button actions are enriched.
|
* 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
|
// Exclude all private props that start with an underscore
|
||||||
let validProps = {}
|
let validProps = {}
|
||||||
Object.entries(props)
|
Object.entries(props)
|
||||||
|
@ -35,6 +35,7 @@ export const enrichProps = async (props, dataContexts, dataBindings) => {
|
||||||
const context = {
|
const context = {
|
||||||
...dataContexts,
|
...dataContexts,
|
||||||
...dataBindings,
|
...dataBindings,
|
||||||
|
user,
|
||||||
data: dataContexts[dataContexts.closestComponentId],
|
data: dataContexts[dataContexts.closestComponentId],
|
||||||
data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
|
data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
"maintainer": "Budibase",
|
"maintainer": "Budibase",
|
||||||
"icon": "./build/icons/",
|
"icon": "./build/icons/",
|
||||||
"target": [
|
"target": [
|
||||||
"deb"
|
"deb",
|
||||||
|
"AppImage"
|
||||||
],
|
],
|
||||||
"category": "Development"
|
"category": "Development"
|
||||||
},
|
},
|
||||||
|
|
|
@ -105,10 +105,16 @@ async function createInstance(template) {
|
||||||
|
|
||||||
// replicate the template data to the instance DB
|
// replicate the template data to the instance DB
|
||||||
if (template) {
|
if (template) {
|
||||||
const templatePath = await downloadTemplate(...template.key.split("/"))
|
let dbDumpReadStream
|
||||||
const dbDumpReadStream = fs.createReadStream(
|
|
||||||
join(templatePath, "db", "dump.txt")
|
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)
|
const { ok } = await db.load(dbDumpReadStream)
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
throw "Error loading database dump from template."
|
throw "Error loading database dump from template."
|
||||||
|
|
|
@ -57,3 +57,17 @@ exports.authenticate = async ctx => {
|
||||||
ctx.throw(401, "Invalid credentials.")
|
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")
|
const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8")
|
||||||
console.log(appHbs)
|
|
||||||
ctx.body = await processString(appHbs, {
|
ctx.body = await processString(appHbs, {
|
||||||
head,
|
head,
|
||||||
body: html,
|
body: html,
|
||||||
|
|
|
@ -83,7 +83,7 @@ const controller = {
|
||||||
ctx.message = `View ${ctx.params.viewName} saved successfully.`
|
ctx.message = `View ${ctx.params.viewName} saved successfully.`
|
||||||
},
|
},
|
||||||
exportView: async ctx => {
|
exportView: async ctx => {
|
||||||
const view = ctx.request.body
|
const view = ctx.query.view
|
||||||
const format = ctx.query.format
|
const format = ctx.query.format
|
||||||
|
|
||||||
// Fetch view rows
|
// Fetch view rows
|
||||||
|
@ -102,14 +102,6 @@ const controller = {
|
||||||
const filename = `${view.name}.${format}`
|
const filename = `${view.name}.${format}`
|
||||||
fs.writeFileSync(join(os.tmpdir(), filename), exportedFile)
|
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.attachment(filename)
|
||||||
ctx.body = fs.createReadStream(join(os.tmpdir(), filename))
|
ctx.body = fs.createReadStream(join(os.tmpdir(), filename))
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@ const zlib = require("zlib")
|
||||||
const { budibaseAppsDir } = require("../utilities/budibaseDir")
|
const { budibaseAppsDir } = require("../utilities/budibaseDir")
|
||||||
const { isDev } = require("../utilities")
|
const { isDev } = require("../utilities")
|
||||||
const { mainRoutes, authRoutes, staticRoutes } = require("./routes")
|
const { mainRoutes, authRoutes, staticRoutes } = require("./routes")
|
||||||
|
const pkg = require("../../package.json")
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
@ -32,6 +33,7 @@ router
|
||||||
await next()
|
await next()
|
||||||
})
|
})
|
||||||
.use("/health", ctx => (ctx.status = 200))
|
.use("/health", ctx => (ctx.status = 200))
|
||||||
|
.use("/version", ctx => (ctx.body = pkg.version))
|
||||||
.use(authenticated)
|
.use(authenticated)
|
||||||
|
|
||||||
// error handling middleware
|
// error handling middleware
|
||||||
|
|
|
@ -4,5 +4,6 @@ const controller = require("../controllers/auth")
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router.post("/api/authenticate", controller.authenticate)
|
router.post("/api/authenticate", controller.authenticate)
|
||||||
|
router.get("/api/self", controller.fetchSelf)
|
||||||
|
|
||||||
module.exports = router
|
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 datasourceRoutes = require("./datasource")
|
||||||
const queryRoutes = require("./query")
|
const queryRoutes = require("./query")
|
||||||
const hostingRoutes = require("./hosting")
|
const hostingRoutes = require("./hosting")
|
||||||
|
const backupRoutes = require("./backup")
|
||||||
|
|
||||||
exports.mainRoutes = [
|
exports.mainRoutes = [
|
||||||
deployRoutes,
|
deployRoutes,
|
||||||
|
@ -42,6 +43,7 @@ exports.mainRoutes = [
|
||||||
datasourceRoutes,
|
datasourceRoutes,
|
||||||
queryRoutes,
|
queryRoutes,
|
||||||
hostingRoutes,
|
hostingRoutes,
|
||||||
|
backupRoutes,
|
||||||
// these need to be handled last as they still use /api/:tableId
|
// these need to be handled last as they still use /api/:tableId
|
||||||
// this could be breaking as koa may recognise other routes as this
|
// this could be breaking as koa may recognise other routes as this
|
||||||
tableRoutes,
|
tableRoutes,
|
||||||
|
|
|
@ -26,10 +26,5 @@ router
|
||||||
)
|
)
|
||||||
.post("/api/views", authorized(BUILDER), usage, viewController.save)
|
.post("/api/views", authorized(BUILDER), usage, viewController.save)
|
||||||
.post("/api/views/export", authorized(BUILDER), viewController.exportView)
|
.post("/api/views/export", authorized(BUILDER), viewController.exportView)
|
||||||
.get(
|
|
||||||
"/api/views/export/download/:fileName",
|
|
||||||
authorized(BUILDER),
|
|
||||||
viewController.downloadExport
|
|
||||||
)
|
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -10,7 +10,6 @@ const streamPipeline = promisify(stream.pipeline)
|
||||||
const { budibaseAppsDir } = require("./budibaseDir")
|
const { budibaseAppsDir } = require("./budibaseDir")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const CouchDB = require("../db")
|
const CouchDB = require("../db")
|
||||||
const { DocumentTypes } = require("../db/utils")
|
|
||||||
|
|
||||||
const DEFAULT_TEMPLATES_BUCKET =
|
const DEFAULT_TEMPLATES_BUCKET =
|
||||||
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
||||||
|
@ -56,6 +55,15 @@ exports.downloadTemplate = async function(type, name) {
|
||||||
return dirName
|
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 }) {
|
exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
||||||
// Copy frontend files
|
// Copy frontend files
|
||||||
const templatesDir = join(
|
const templatesDir = join(
|
||||||
|
@ -67,13 +75,6 @@ exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
||||||
"db"
|
"db"
|
||||||
)
|
)
|
||||||
fs.ensureDirSync(templatesDir)
|
fs.ensureDirSync(templatesDir)
|
||||||
const writeStream = fs.createWriteStream(join(templatesDir, "dump.txt"))
|
await performDump({ dir: templatesDir, appId })
|
||||||
// perform couch dump
|
|
||||||
const instanceDb = new CouchDB(appId)
|
|
||||||
await instanceDb.dump(writeStream, {
|
|
||||||
filter: doc => {
|
|
||||||
return !doc._id.startsWith(DocumentTypes.USER)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return templatesDir
|
return templatesDir
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491",
|
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.52.4",
|
"@budibase/bbui": "^1.55.1",
|
||||||
"@budibase/svelte-ag-grid": "^0.0.16",
|
"@budibase/svelte-ag-grid": "^0.0.16",
|
||||||
"apexcharts": "^3.22.1",
|
"apexcharts": "^3.22.1",
|
||||||
"flatpickr": "^4.6.6",
|
"flatpickr": "^4.6.6",
|
||||||
|
|
|
@ -135,8 +135,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateRow = async row => {
|
const updateRow = async row => {
|
||||||
const schema = (await API.fetchTableDefinition(row.tableId)).schema
|
await API.updateRow(row)
|
||||||
await API.updateRow(schema, { data: row })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRows = async () => {
|
const deleteRows = async () => {
|
||||||
|
|
|
@ -39,10 +39,10 @@
|
||||||
lodash "^4.17.19"
|
lodash "^4.17.19"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@budibase/bbui@^1.52.4":
|
"@budibase/bbui@^1.55.1":
|
||||||
version "1.52.4"
|
version "1.55.1"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.52.4.tgz#ae3c17e1f49f14e65831703958bcddc6e64afd24"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.55.1.tgz#291fb6fa10479b49f078d3a911ad0ed42c2e6b12"
|
||||||
integrity sha512-/wiv5dSyvXLgy2/zGEslnCsjwE8qqng1D8k5ScSOPEyMab8tzzd1XxfZAN9rp84zIMgAXeH6s5a4j4riR+jVkg==
|
integrity sha512-bxsHBwkOqCtuFz89e0hAXwvwycfS4xPPrEge5PxK1Lh3uqetO4bXoIxYaIDjfi2Ku7CYIzEmOwSloNaQWeTF4g==
|
||||||
dependencies:
|
dependencies:
|
||||||
markdown-it "^12.0.2"
|
markdown-it "^12.0.2"
|
||||||
quill "^1.3.7"
|
quill "^1.3.7"
|
||||||
|
|
Loading…
Reference in New Issue