This commit is contained in:
Martin McKeaveney 2020-07-07 21:29:20 +01:00
parent be8311aba2
commit c953fa679a
21 changed files with 167 additions and 167 deletions

View File

@ -36,7 +36,7 @@
class={determineClassName(type)} class={determineClassName(type)}
bind:value bind:value
class:uk-form-danger={errors.length > 0}> class:uk-form-danger={errors.length > 0}>
<option></option> <option />
{#each options as opt} {#each options as opt}
<option value={opt}>{opt}</option> <option value={opt}>{opt}</option>
{/each} {/each}

View File

@ -59,7 +59,9 @@
if (field.name.startsWith("_")) { if (field.name.startsWith("_")) {
errors.push(`field '${field.name}' - name cannot begin with '_''`) errors.push(`field '${field.name}' - name cannot begin with '_''`)
} else if (restrictedFieldNames.includes(field.name)) { } else if (restrictedFieldNames.includes(field.name)) {
errors.push(`field '${field.name}' - is a restricted name, please rename`) errors.push(
`field '${field.name}' - is a restricted name, please rename`
)
} else if (!field.name || !field.name.trim()) { } else if (!field.name || !field.name.trim()) {
errors.push("field name cannot be blank") errors.push("field name cannot be blank")
} }
@ -75,9 +77,7 @@
async function saveModel() { async function saveModel() {
const errors = validate() const errors = validate()
if (errors.length > 0) { if (errors.length > 0) {
notifier.danger( notifier.danger(errors.join("/n"))
errors.join("/n")
)
return return
} }

View File

@ -25,69 +25,69 @@
name: "Screen Placeholder", name: "Screen Placeholder",
route: "*", route: "*",
props: { props: {
"_id": "49c3d0a2-7028-46f0-b004-7eddf62ad01c", _id: "49c3d0a2-7028-46f0-b004-7eddf62ad01c",
"_component": "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
"_styles": { _styles: {
"normal": { normal: {
"padding": "0px", padding: "0px",
"font-family": "Roboto", "font-family": "Roboto",
"border-width": "0", "border-width": "0",
"border-style": "None", "border-style": "None",
"text-align": "center" "text-align": "center",
}, },
"hover": {}, hover: {},
"active": {}, active: {},
"selected": {} selected: {},
}, },
"_code": "", _code: "",
"className": "", className: "",
"onLoad": [], onLoad: [],
"type": "div", type: "div",
"_children": [ _children: [
{ {
"_id": "335428f7-f9ca-4acd-9e76-71bc8ad27324", _id: "335428f7-f9ca-4acd-9e76-71bc8ad27324",
"_component": "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
"_styles": { _styles: {
"normal": { normal: {
"padding": "16px", padding: "16px",
"border-style": "Dashed", "border-style": "Dashed",
"border-width": "2px", "border-width": "2px",
"border-color": "#8a8989fa" "border-color": "#8a8989fa",
}, },
"hover": {}, hover: {},
"active": {}, active: {},
"selected": {} selected: {},
}, },
"_code": "", _code: "",
"className": "", className: "",
"onLoad": [], onLoad: [],
"type": "div", type: "div",
"_instanceId": "inst_b3b4e95_ab0df02dda3f4d8eb4b35eea2968bad3", _instanceId: "inst_b3b4e95_ab0df02dda3f4d8eb4b35eea2968bad3",
"_instanceName": "Container", _instanceName: "Container",
"_children": [ _children: [
{ {
"_id": "ddb6a225-33ba-4ba8-91da-bc6a2697ebf9", _id: "ddb6a225-33ba-4ba8-91da-bc6a2697ebf9",
"_component": "@budibase/standard-components/heading", _component: "@budibase/standard-components/heading",
"_styles": { _styles: {
"normal": { normal: {
"font-family": "Roboto" "font-family": "Roboto",
}, },
"hover": {}, hover: {},
"active": {}, active: {},
"selected": {} selected: {},
}, },
"_code": "", _code: "",
"className": "", className: "",
"text": "Your screens go here", text: "Your screens go here",
"type": "h1", type: "h1",
"_instanceId": "inst_b3b4e95_ab0df02dda3f4d8eb4b35eea2968bad3", _instanceId: "inst_b3b4e95_ab0df02dda3f4d8eb4b35eea2968bad3",
"_instanceName": "Heading", _instanceName: "Heading",
"_children": [] _children: [],
} },
] ],
} },
], ],
"_instanceName": "Content Placeholder" _instanceName: "Content Placeholder",
}, },
} }

View File

@ -1,5 +1,5 @@
<script> <script>
import {buildStyle} from "../helpers.js" import { buildStyle } from "../helpers.js"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
export let backgroundSize = "10px" export let backgroundSize = "10px"

View File

@ -177,7 +177,6 @@
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : "" $: border = v > 90 && s < 5 ? "1px dashed #dedada" : ""
$: selectedColorStyle = buildStyle({ background: value, border }) $: selectedColorStyle = buildStyle({ background: value, border })
$: hasSwatches = swatches.length > 0 $: hasSwatches = swatches.length > 0
</script> </script>
<Portal> <Portal>

View File

@ -1,6 +1,6 @@
<script> <script>
import {createEventDispatcher} from "svelte" import { createEventDispatcher } from "svelte"
import {keyevents} from "../actions" import { keyevents } from "../actions"
export let text = "" export let text = ""
export let selected = false export let selected = false
@ -8,7 +8,14 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
</script> </script>
<div class="flatbutton" tabindex="0" use:keyevents={{"Enter": () => dispatch("click")}} class:selected on:click>{text}</div> <div
class="flatbutton"
tabindex="0"
use:keyevents={{ Enter: () => dispatch('click') }}
class:selected
on:click>
{text}
</div>
<style> <style>
.flatbutton { .flatbutton {
@ -24,7 +31,7 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: #f1f3f4; background: #f1f3f4;
outline-color: #003cb0; outline-color: #003cb0;
outline-width: thin; outline-width: thin;
} }

View File

@ -1,6 +1,6 @@
<script> <script>
import { onMount, createEventDispatcher } from "svelte" import { onMount, createEventDispatcher } from "svelte"
import {drag, keyevents} from "../actions" import { drag, keyevents } from "../actions"
export let value = 1 export let value = 1
export let type = "hue" export let type = "hue"
@ -21,15 +21,14 @@
let percentageClick = (clickPosition / sliderWidth).toFixed(2) let percentageClick = (clickPosition / sliderWidth).toFixed(2)
if (percentageClick >= 0 && percentageClick <= 1) { if (percentageClick >= 0 && percentageClick <= 1) {
let value = type === "hue" ? 360 * percentageClick : percentageClick let value = type === "hue" ? 360 * percentageClick : percentageClick
dispatch("change", { color: value, isDrag }) dispatch("change", { color: value, isDrag })
} }
} }
function handleLeftKey() { function handleLeftKey() {
let v = value - incrementFactor let v = value - incrementFactor
if(isWithinLimit(v)) { if (isWithinLimit(v)) {
value = v value = v
dispatch("change", { color: value }) dispatch("change", { color: value })
} }
@ -37,13 +36,12 @@
function handleRightKey() { function handleRightKey() {
let v = value + incrementFactor let v = value + incrementFactor
if(isWithinLimit(v)) { if (isWithinLimit(v)) {
value = v value = v
dispatch("change", { color: value }) dispatch("change", { color: value })
} }
} }
$: thumbPosition = $: thumbPosition =
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value
@ -53,7 +51,7 @@
<div <div
tabindex="0" tabindex="0"
bind:this={slider} bind:this={slider}
use:keyevents={{37: handleLeftKey, 39: handleRightKey}} use:keyevents={{ 37: handleLeftKey, 39: handleRightKey }}
bind:clientWidth={sliderWidth} bind:clientWidth={sliderWidth}
on:click={event => onSliderChange(event.clientX)} on:click={event => onSliderChange(event.clientX)}
class="color-format-slider" class="color-format-slider"
@ -77,7 +75,7 @@
margin: 10px 0px; margin: 10px 0px;
border: 1px solid #e8e8ef; border: 1px solid #e8e8ef;
cursor: pointer; cursor: pointer;
outline-color: #003cb0; outline-color: #003cb0;
outline-width: thin; outline-width: thin;
} }

View File

@ -27,7 +27,8 @@
animation: false, animation: false,
}) })
$: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false)) $: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false))
$: noChildrenAllowed = !component || $: noChildrenAllowed =
!component ||
getComponentDefinition($store, component._component).children === false getComponentDefinition($store, component._component).children === false
$: noPaste = !$store.componentToPaste $: noPaste = !$store.componentToPaste

View File

@ -1 +1 @@
<slot/> <slot />

View File

@ -43,7 +43,7 @@
<Button secondary medium on:click={deployApp}> <Button secondary medium on:click={deployApp}>
Deploy App Deploy App
{#if loading} {#if loading}
<Spinner ratio={"0.5"} /> <Spinner ratio={'0.5'} />
{/if} {/if}
</Button> </Button>
{/if} {/if}

View File

@ -14,9 +14,9 @@
if ($leftover) { if ($leftover) {
// Get the correct screen children. // Get the correct screen children.
const screenChildren = $store.pages[$params.page]._screens.find( const screenChildren = $store.pages[$params.page]._screens.find(
screen => screen =>
(screen.props._instanceName === $params.screen screen.props._instanceName === $params.screen ||
|| screen.props._instanceName === decodeURIComponent($params.screen)) screen.props._instanceName === decodeURIComponent($params.screen)
).props._children ).props._children
findComponent(componentIds, screenChildren) findComponent(componentIds, screenChildren)
} }

View File

@ -1,49 +1,49 @@
const fs = require("fs") const fs = require("fs")
const AWS = require("aws-sdk") const AWS = require("aws-sdk")
const fetch = require("node-fetch") const fetch = require("node-fetch")
const { const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
budibaseAppsDir,
} = require("../../../utilities/budibaseDir")
async function invalidateCDN(cfDistribution, appId) { async function invalidateCDN(cfDistribution, appId) {
const cf = new AWS.CloudFront({}) const cf = new AWS.CloudFront({})
return cf.createInvalidation({ return cf
DistributionId: cfDistribution, .createInvalidation({
InvalidationBatch: { DistributionId: cfDistribution,
CallerReference: appId, InvalidationBatch: {
Paths: { CallerReference: appId,
Quantity: 1, Paths: {
Items: [ Quantity: 1,
`/assets/${appId}/*` Items: [`/assets/${appId}/*`],
] },
} },
} })
}).promise() .promise()
} }
async function fetchTemporaryCredentials() { async function fetchTemporaryCredentials() {
const response = await fetch(process.env.DEPLOYMENT_CREDENTIALS_URL, { const response = await fetch(process.env.DEPLOYMENT_CREDENTIALS_URL, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
apiKey: process.env.BUDIBASE_API_KEY apiKey: process.env.BUDIBASE_API_KEY,
}) }),
}) })
if (response.status !== 200) { if (response.status !== 200) {
throw new Error(`Error fetching temporary credentials for api key: ${BUDIBASE_API_KEY}`) throw new Error(
`Error fetching temporary credentials for api key: ${process.env.BUDIBASE_API_KEY}`
)
} }
const json = await response.json() const json = await response.json()
return json return json
} }
const CONTENT_TYPE_MAP = { const CONTENT_TYPE_MAP = {
html: "text/html", html: "text/html",
css: "text/css", css: "text/css",
js: "application/javascript" js: "application/javascript",
}; }
/** /**
* Recursively walk a directory tree and execute a callback on all files. * Recursively walk a directory tree and execute a callback on all files.
@ -52,9 +52,9 @@ const CONTENT_TYPE_MAP = {
*/ */
function walkDir(dirPath, callback) { function walkDir(dirPath, callback) {
for (let filename of fs.readdirSync(dirPath)) { for (let filename of fs.readdirSync(dirPath)) {
const filePath = `${dirPath}/${filename}` const filePath = `${dirPath}/${filename}`
const stat = fs.lstatSync(filePath) const stat = fs.lstatSync(filePath)
if (stat.isFile()) { if (stat.isFile()) {
callback(filePath) callback(filePath)
} else { } else {
@ -63,24 +63,24 @@ function walkDir(dirPath, callback) {
} }
} }
exports.uploadAppAssets = async function ({ appId }) { exports.uploadAppAssets = async function({ appId }) {
const { const {
credentials, credentials,
accountId, accountId,
bucket, bucket,
cfDistribution, cfDistribution,
} = await fetchTemporaryCredentials() } = await fetchTemporaryCredentials()
AWS.config.update({ AWS.config.update({
accessKeyId: credentials.AccessKeyId, accessKeyId: credentials.AccessKeyId,
secretAccessKey: credentials.SecretAccessKey, secretAccessKey: credentials.SecretAccessKey,
sessionToken: credentials.SessionToken sessionToken: credentials.SessionToken,
}); })
const s3 = new AWS.S3({ const s3 = new AWS.S3({
params: { params: {
Bucket: bucket Bucket: bucket,
} },
}) })
const appAssetsPath = `${budibaseAppsDir()}/${appId}/public` const appAssetsPath = `${budibaseAppsDir()}/${appId}/public`
@ -94,14 +94,16 @@ exports.uploadAppAssets = async function ({ appId }) {
const fileExtension = [...filePath.split(".")].pop() const fileExtension = [...filePath.split(".")].pop()
const fileBytes = fs.readFileSync(filePath) const fileBytes = fs.readFileSync(filePath)
const upload = s3.upload({ const upload = s3
Key: filePath.replace(appAssetsPath, `assets/${appId}`), .upload({
Body: fileBytes, Key: filePath.replace(appAssetsPath, `assets/${appId}`),
ContentType: CONTENT_TYPE_MAP[fileExtension], Body: fileBytes,
Metadata: { ContentType: CONTENT_TYPE_MAP[fileExtension],
accountId Metadata: {
} accountId,
}).promise() },
})
.promise()
uploads.push(upload) uploads.push(upload)
}) })
@ -109,9 +111,9 @@ exports.uploadAppAssets = async function ({ appId }) {
try { try {
await Promise.all(uploads) await Promise.all(uploads)
await invalidateCDN(cfDistribution, appId) await invalidateCDN(cfDistribution, appId)
} catch (err) { } catch (err) {
console.error("Error uploading budibase app assets to s3", err) console.error("Error uploading budibase app assets to s3", err)
throw err throw err
} }
} }

View File

@ -1,35 +1,29 @@
const CouchDB = require("pouchdb") const CouchDB = require("pouchdb")
const PouchDB = require("../../../db") const PouchDB = require("../../../db")
const { const { uploadAppAssets } = require("./aws")
uploadAppAssets,
} = require("./aws")
function replicate(local, remote) { function replicate(local, remote) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const replication = local.sync(remote)
const replication = local.sync(remote);
replication.on("complete", () => resolve()) replication.on("complete", () => resolve())
replication.on("error", err => reject(err)) replication.on("error", err => reject(err))
}); })
} }
async function replicateCouch(instanceId, clientId) { async function replicateCouch(instanceId, clientId) {
const databases = [`client_${clientId}`, "client_app_lookup", instanceId]
const databases = [ const replications = databases.map(local => {
`client_${clientId}`, const localDb = new PouchDB(local)
"client_app_lookup", const remoteDb = new CouchDB(
instanceId `${process.env.DEPLOYMENT_COUCH_DB_URL}/${local}`
]; )
const replications = databases.map(local => { return replicate(localDb, remoteDb)
const localDb = new PouchDB(local); })
const remoteDb = new CouchDB(`${process.env.DEPLOYMENT_COUCH_DB_URL}/${local}`)
return replicate(localDb, remoteDb); await Promise.all(replications)
});
await Promise.all(replications)
} }
exports.deployApp = async function(ctx) { exports.deployApp = async function(ctx) {
@ -37,21 +31,21 @@ exports.deployApp = async function(ctx) {
const clientAppLookupDB = new PouchDB("client_app_lookup") const clientAppLookupDB = new PouchDB("client_app_lookup")
const { clientId } = await clientAppLookupDB.get(ctx.user.appId) const { clientId } = await clientAppLookupDB.get(ctx.user.appId)
ctx.log.info(`Uploading assets for appID ${ctx.user.appId} assets to s3..`); ctx.log.info(`Uploading assets for appID ${ctx.user.appId} assets to s3..`)
await uploadAppAssets({ await uploadAppAssets({
clientId, clientId,
appId: ctx.user.appId appId: ctx.user.appId,
}) })
// replicate the DB to the couchDB cluster in prod // replicate the DB to the couchDB cluster in prod
ctx.log.info("Replicating local PouchDB to remote.."); ctx.log.info("Replicating local PouchDB to remote..")
await replicateCouch(ctx.user.instanceId, clientId); await replicateCouch(ctx.user.instanceId, clientId)
ctx.body = { ctx.body = {
status: "SUCCESS", status: "SUCCESS",
completed: Date.now() completed: Date.now(),
} }
} catch (err) { } catch (err) {
ctx.throw(err.status || 500, `Deployment Failed: ${err.message}`); ctx.throw(err.status || 500, `Deployment Failed: ${err.message}`)
} }
} }

View File

@ -18,7 +18,7 @@ exports.serveBuilder = async function(ctx) {
} }
exports.serveApp = async function(ctx) { exports.serveApp = async function(ctx) {
const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"; const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"
// default to homedir // default to homedir
const appPath = resolve( const appPath = resolve(
@ -32,7 +32,7 @@ exports.serveApp = async function(ctx) {
if (process.env.CLOUD) { if (process.env.CLOUD) {
appId = ctx.subdomains[1] appId = ctx.subdomains[1]
} }
// only set the appId cookie for /appId .. we COULD check for valid appIds // only set the appId cookie for /appId .. we COULD check for valid appIds
// but would like to avoid that DB hit // but would like to avoid that DB hit
const looksLikeAppId = /^[0-9a-f]{32}$/.test(appId) const looksLikeAppId = /^[0-9a-f]{32}$/.test(appId)
@ -50,10 +50,11 @@ exports.serveApp = async function(ctx) {
} }
if (process.env.CLOUD) { if (process.env.CLOUD) {
const S3_URL = `https://${appId}.app.budi.live/assets/${appId}/${mainOrAuth}/${ctx.file || "index.production.html"}` const S3_URL = `https://${appId}.app.budi.live/assets/${appId}/${mainOrAuth}/${ctx.file ||
"index.production.html"}`
const response = await fetch(S3_URL) const response = await fetch(S3_URL)
const body = await response.text() const body = await response.text()
ctx.body = body ctx.body = body
return return
} }
@ -62,7 +63,7 @@ exports.serveApp = async function(ctx) {
exports.serveAppAsset = async function(ctx) { exports.serveAppAsset = async function(ctx) {
// default to homedir // default to homedir
const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"; const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"
const appPath = resolve( const appPath = resolve(
budibaseAppsDir(), budibaseAppsDir(),
@ -94,11 +95,13 @@ exports.serveComponentLibrary = async function(ctx) {
if (process.env.CLOUD) { if (process.env.CLOUD) {
const appId = ctx.user.appId const appId = ctx.user.appId
const S3_URL = encodeURI(`https://${appId}.app.budi.live/assets/componentlibrary/${ctx.query.library}/dist/index.js`) const S3_URL = encodeURI(
`https://${appId}.app.budi.live/assets/componentlibrary/${ctx.query.library}/dist/index.js`
)
const response = await fetch(S3_URL) const response = await fetch(S3_URL)
const body = await response.text() const body = await response.text()
ctx.type = 'application/javascript' ctx.type = "application/javascript"
ctx.body = body; ctx.body = body
return return
} }

View File

@ -11,7 +11,7 @@ const staticRoutes = require("./static")
const componentRoutes = require("./component") const componentRoutes = require("./component")
const workflowRoutes = require("./workflow") const workflowRoutes = require("./workflow")
const accesslevelRoutes = require("./accesslevel") const accesslevelRoutes = require("./accesslevel")
const deployRoutes = require("./deploy"); const deployRoutes = require("./deploy")
module.exports = { module.exports = {
deployRoutes, deployRoutes,

View File

@ -29,4 +29,4 @@ module.exports = async port => {
const serverPort = port || env.PORT const serverPort = port || env.PORT
const server = http.createServer(app.callback()) const server = http.createServer(app.callback())
return server.listen(serverPort || 4001) return server.listen(serverPort || 4001)
} }

View File

@ -1,6 +1,4 @@
const { resolve, join } = require("path") const { resolve } = require("path")
const { homedir } = require("os")
async function runServer() { async function runServer() {
const budibaseDir = "~/.budibase" const budibaseDir = "~/.budibase"

View File

@ -61,11 +61,11 @@ module.exports = async (ctx, next) => {
} }
/** /**
* Return the full access level object either from constants * Return the full access level object either from constants
* or the database based on the access level ID passed. * or the database based on the access level ID passed.
* *
* @param {*} instanceId - instanceId of the user * @param {*} instanceId - instanceId of the user
* @param {*} accessLevelId - the id of the users access level * @param {*} accessLevelId - the id of the users access level
*/ */
const getAccessLevel = async (instanceId, accessLevelId) => { const getAccessLevel = async (instanceId, accessLevelId) => {
if ( if (

View File

@ -70,7 +70,7 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
const indexHtml = sqrl.Render(indexHtmlTemplate, templateObj) const indexHtml = sqrl.Render(indexHtmlTemplate, templateObj)
const deployableHtml = sqrl.Render(indexHtmlTemplate, { const deployableHtml = sqrl.Render(indexHtmlTemplate, {
...templateObj, ...templateObj,
production: true production: true,
}) })
await writeFile(indexHtmlPath, indexHtml, { flag: "w+" }) await writeFile(indexHtmlPath, indexHtml, { flag: "w+" })

View File

@ -32,5 +32,4 @@
}) })
</script> </script>
<section bind:this={target}> <section bind:this={target} />
</section>

View File

@ -22,7 +22,7 @@
let record let record
// if srcdoc, then we assume this is the builder preview // if srcdoc, then we assume this is the builder preview
if(pathParts.length === 0 || pathParts[0] === "srcdoc") { if (pathParts.length === 0 || pathParts[0] === "srcdoc") {
record = await fetchFirstRecord() record = await fetchFirstRecord()
} else { } else {
const id = pathParts[pathParts.length - 1] const id = pathParts[pathParts.length - 1]
@ -48,5 +48,4 @@
}) })
</script> </script>
<section bind:this={target}> <section bind:this={target} />
</section>