Merge branch 'master' of github.com:Budibase/budibase into linked-records

This commit is contained in:
Martin McKeaveney 2020-06-11 16:22:28 +01:00
commit a49747074a
17 changed files with 237 additions and 71 deletions

View File

@ -19,9 +19,10 @@
onChange(_value) onChange(_value)
} }
$: displayValues = value && suffix $: displayValues =
? value.map(v => v.replace(new RegExp(`${suffix}$`), "")) value && suffix
: value || [] ? value.map(v => v.replace(new RegExp(`${suffix}$`), ""))
: value || []
</script> </script>
<div class="input-container"> <div class="input-container">

View File

@ -21,7 +21,7 @@
return componentName || "element" return componentName || "element"
} }
const screenPlaceholder = { const screenPlaceholder = {
name: "Screen Placeholder", name: "Screen Placeholder",
route: "*", route: "*",
props: { props: {
@ -60,9 +60,8 @@
}, },
} }
$: hasComponent = !!$store.currentPreviewItem $: hasComponent = !!$store.currentPreviewItem
$: { $: {
styles = "" styles = ""
// Apply the CSS from the currently selected page and its screens // Apply the CSS from the currently selected page and its screens
@ -88,9 +87,9 @@
libraries: $store.libraries, libraries: $store.libraries,
page: $store.pages[$store.currentPageName], page: $store.pages[$store.currentPageName],
screens: [ screens: [
$store.currentFrontEndType === "page" $store.currentFrontEndType === "page"
? screenPlaceholder ? screenPlaceholder
: $store.currentPreviewItem, : $store.currentPreviewItem,
], ],
appRootPath: "", appRootPath: "",
} }
@ -102,20 +101,25 @@
: "" : ""
const refreshContent = () => { const refreshContent = () => {
iframe.contentWindow.postMessage(JSON.stringify({ iframe.contentWindow.postMessage(
styles, JSON.stringify({
stylesheetLinks, styles,
selectedComponentType, stylesheetLinks,
selectedComponentId, selectedComponentType,
frontendDefinition, selectedComponentId,
})) frontendDefinition,
})
)
} }
$: if(iframe) iframe.contentWindow.addEventListener("bb-ready", refreshContent, { once: true }) $: if (iframe)
iframe.contentWindow.addEventListener("bb-ready", refreshContent, {
once: true,
})
$: if(iframe && frontendDefinition) { $: if (iframe && frontendDefinition) {
refreshContent() refreshContent()
} }
</script> </script>
<div class="component-container"> <div class="component-container">

View File

@ -38,15 +38,30 @@
c => c._component === componentInstance._component c => c._component === componentInstance._component
) || {} ) || {}
$: panelDefinition = componentPropDefinition.properties let panelDefinition = {}
? componentPropDefinition.properties[selectedCategory.value]
: {}
// SCREEN PROPS ============================================= $: {
$: screen_props = if (componentPropDefinition.properties) {
$store.currentFrontEndType === "page" if (selectedCategory.value === "design") {
? getProps($store.currentPreviewItem, ["name", "favicon"]) panelDefinition = componentPropDefinition.properties["design"]
: getProps($store.currentPreviewItem, ["name", "description", "route"]) } else {
let panelDef = componentPropDefinition.properties["settings"]
if (
$store.currentFrontEndType === "page" &&
$store.currentView !== "component"
) {
panelDefinition = [...page, ...panelDef]
} else if (
$store.currentFrontEndType === "screen" &&
$store.currentView !== "component"
) {
panelDefinition = [...screen, ...panelDef]
} else {
panelDefinition = panelDef
}
}
}
}
const onStyleChanged = store.setComponentStyle const onStyleChanged = store.setComponentStyle
const onPropChanged = store.setComponentProp const onPropChanged = store.setComponentProp
@ -106,12 +121,10 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* Merge Check */
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden; overflow-y: hidden;
padding: 20px; padding: 20px;
box-sizing: border-box; box-sizing: border-box;
/* Merge Check */
} }
.title > div:nth-child(1) { .title > div:nth-child(1) {

View File

@ -12,8 +12,9 @@ export const layout = [
label: "Display", label: "Display",
key: "display", key: "display",
control: OptionSelect, control: OptionSelect,
initialValue: "Flex", initialValue: "",
options: [ options: [
{ label: "", value: "" },
{ label: "Flex", value: "flex" }, { label: "Flex", value: "flex" },
{ label: "Inline Flex", value: "inline-flex" }, { label: "Inline Flex", value: "inline-flex" },
], ],
@ -39,6 +40,7 @@ export const layout = [
control: OptionSelect, control: OptionSelect,
initialValue: "Flex Start", initialValue: "Flex Start",
options: [ options: [
{ label: "", value: "" },
{ label: "Flex Start", value: "flex-start" }, { label: "Flex Start", value: "flex-start" },
{ label: "Flex End", value: "flex-end" }, { label: "Flex End", value: "flex-end" },
{ label: "Center", value: "center" }, { label: "Center", value: "center" },
@ -317,19 +319,31 @@ export const border = [
{ {
label: "Radius", label: "Radius",
key: "border-radius", key: "border-radius",
control: Input, control: OptionSelect,
width: "48px", defaultValue: "None",
placeholder: "px", options: [
textAlign: "center", { label: "None", value: "0" },
{ label: "small", value: "0.125rem" },
{ label: "Medium", value: "0.25rem;" },
{ label: "Large", value: "0.375rem" },
{ label: "Extra large", value: "0.5rem" },
{ label: "Full", value: "5678px" },
],
}, },
{ {
label: "Width", label: "Width",
key: "border-width", key: "border-width",
control: Input, control: OptionSelect,
width: "48px", defaultValue: "None",
placeholder: "px", options: [
textAlign: "center", { label: "None", value: "0" },
}, //custom { label: "Extra small", value: "0.5px" },
{ label: "Small", value: "1px" },
{ label: "Medium", value: "2px" },
{ label: "Large", value: "4px" },
{ label: "Extra large", value: "8px" },
],
},
{ {
label: "Color", label: "Color",
key: "border-color", key: "border-color",
@ -339,6 +353,7 @@ export const border = [
label: "Style", label: "Style",
key: "border-style", key: "border-style",
control: OptionSelect, control: OptionSelect,
defaultValue: "None",
options: [ options: [
"none", "none",
"hidden", "hidden",
@ -365,17 +380,50 @@ export const effects = [
}, },
{ {
label: "Rotate", label: "Rotate",
key: "transform", key: "transform-rotate",
control: Input, control: OptionSelect,
width: "48px", defaultValue: "0",
textAlign: "center", options: [
placeholder: "deg", "0",
"45deg",
"90deg",
"90deg",
"135deg",
"180deg",
"225deg",
"270deg",
"315dev",
],
}, //needs special control }, //needs special control
{ {
label: "Shadow", label: "Shadow",
key: "box-shadow", key: "box-shadow",
control: InputGroup, control: OptionSelect,
meta: [{ placeholder: "X" }, { placeholder: "Y" }, { placeholder: "B" }], defaultValue: "None",
options: [
{ label: "None", value: "none" },
{ label: "Extra small", value: "0 1px 2px 0 rgba(0, 0, 0, 0.05)" },
{
label: "Small",
value:
"0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)",
},
{
label: "Medium",
value:
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
},
{
label: "Large",
value:
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
},
{
label: "Extra large",
value:
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
},
],
}, },
] ]

View File

@ -187,6 +187,17 @@ export default {
], ],
}, },
}, },
{
_component: "@budibase/standard-components/image",
name: "Image",
description: "A basic component for displaying images",
icon: "ri-image-fill",
children: [],
properties: {
design: { ...all },
settings: [{ label: "URL", key: "url", control: Input }],
},
},
{ {
_component: "@budibase/standard-components/icon", _component: "@budibase/standard-components/icon",
name: "Icon", name: "Icon",

View File

@ -62,7 +62,7 @@
<span <span
class:active={false} class:active={false}
class="topnavitemright" class="topnavitemright"
on:click={() => (location = `/${application}`)}> on:click={() => window.open(`/${application}`)}>
<PreviewIcon /> <PreviewIcon />
</span> </span>
</div> </div>

View File

@ -11,7 +11,7 @@ const { exec } = require("child_process")
const sqrl = require("squirrelly") const sqrl = require("squirrelly")
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const db = new CouchDB(ClientDb.name(getClientId(ctx)))
const body = await db.query("client/by_type", { const body = await db.query("client/by_type", {
include_docs: true, include_docs: true,
key: ["app"], key: ["app"],
@ -21,16 +21,30 @@ exports.fetch = async function(ctx) {
} }
exports.fetchAppPackage = async function(ctx) { exports.fetchAppPackage = async function(ctx) {
const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const clientId = await lookupClientId(ctx.params.applicationId)
const db = new CouchDB(ClientDb.name(clientId))
const application = await db.get(ctx.params.applicationId) const application = await db.get(ctx.params.applicationId)
ctx.body = await getPackageForBuilder(ctx.config, application) ctx.body = await getPackageForBuilder(ctx.config, application)
} }
exports.create = async function(ctx) { exports.create = async function(ctx) {
const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const clientId =
(ctx.request.body && ctx.request.body.clientId) || env.CLIENT_ID
if (!clientId) {
ctx.throw(400, "ClientId not suplied")
}
const appId = newid()
// insert an appId -> clientId lookup
const masterDb = new CouchDB("clientAppLookup")
await masterDb.put({
_id: appId,
clientId,
})
const db = new CouchDB(ClientDb.name(clientId))
const newApplication = { const newApplication = {
_id: newid(), _id: appId,
type: "app", type: "app",
instances: [], instances: [],
userInstanceMap: {}, userInstanceMap: {},
@ -47,11 +61,10 @@ exports.create = async function(ctx) {
const createInstCtx = { const createInstCtx = {
params: { params: {
clientId: env.CLIENT_ID,
applicationId: newApplication._id, applicationId: newApplication._id,
}, },
request: { request: {
body: { name: `dev-${env.CLIENT_ID}` }, body: { name: `dev-${clientId}` },
}, },
} }
await instanceController.create(createInstCtx) await instanceController.create(createInstCtx)
@ -61,6 +74,7 @@ exports.create = async function(ctx) {
await runNpmInstall(newAppFolder) await runNpmInstall(newAppFolder)
} }
ctx.status = 200
ctx.body = newApplication ctx.body = newApplication
ctx.message = `Application ${ctx.request.body.name} created successfully` ctx.message = `Application ${ctx.request.body.name} created successfully`
} }
@ -79,7 +93,6 @@ const createEmptyAppPackage = async (ctx, app) => {
if (await exists(newAppFolder)) { if (await exists(newAppFolder)) {
ctx.throw(400, "App folder already exists for this application") ctx.throw(400, "App folder already exists for this application")
return
} }
await copy(templateFolder, newAppFolder) await copy(templateFolder, newAppFolder)
@ -99,6 +112,24 @@ const createEmptyAppPackage = async (ctx, app) => {
return newAppFolder return newAppFolder
} }
const lookupClientId = async appId => {
const masterDb = new CouchDB("clientAppLookup")
const { clientId } = await masterDb.get(appId)
return clientId
}
const getClientId = ctx => {
const clientId =
(ctx.request.body && ctx.request.body.clientId) ||
(ctx.query && ctx.query.clientId) ||
env.CLIENT_ID
if (!clientId) {
ctx.throw(400, "ClientId not suplied")
}
return clientId
}
const updateJsonFile = async (filePath, app) => { const updateJsonFile = async (filePath, app) => {
const json = await readFile(filePath, "utf8") const json = await readFile(filePath, "utf8")
const newJson = sqrl.Render(json, app) const newJson = sqrl.Render(json, app)

View File

@ -2,7 +2,6 @@ const jwt = require("jsonwebtoken")
const CouchDB = require("../../db") const CouchDB = require("../../db")
const ClientDb = require("../../db/clientDb") const ClientDb = require("../../db/clientDb")
const bcrypt = require("../../utilities/bcrypt") const bcrypt = require("../../utilities/bcrypt")
const env = require("../../environment")
exports.authenticate = async ctx => { exports.authenticate = async ctx => {
const { username, password } = ctx.request.body const { username, password } = ctx.request.body
@ -10,8 +9,14 @@ exports.authenticate = async ctx => {
if (!username) ctx.throw(400, "Username Required.") if (!username) ctx.throw(400, "Username Required.")
if (!password) ctx.throw(400, "Password Required") if (!password) ctx.throw(400, "Password Required")
const masterDb = new CouchDB("clientAppLookup")
const { clientId } = await masterDb.get(ctx.params.appId)
if (!clientId) {
ctx.throw(400, "ClientId not suplied")
}
// find the instance that the user is associated with // find the instance that the user is associated with
const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const db = new CouchDB(ClientDb.name(clientId))
const appId = ctx.params.appId const appId = ctx.params.appId
const app = await db.get(appId) const app = await db.get(appId)
const instanceId = app.userInstanceMap[username] const instanceId = app.userInstanceMap[username]

View File

@ -6,13 +6,26 @@ exports.getClientId = async function(ctx) {
} }
exports.create = async function(ctx) { exports.create = async function(ctx) {
await create(env.CLIENT_ID) const clientId = getClientId(ctx)
await create(clientId)
ctx.status = 200 ctx.status = 200
ctx.message = `Client Database ${env.CLIENT_ID} successfully provisioned.` ctx.message = `Client Database ${clientId} successfully provisioned.`
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
await destroy(env.CLIENT_ID) const clientId = getClientId(ctx)
await destroy(clientId)
ctx.status = 200 ctx.status = 200
ctx.message = `Client Database ${env.CLIENT_ID} successfully deleted.` ctx.message = `Client Database ${clientId} successfully deleted.`
}
const getClientId = ctx => {
const clientId =
(ctx.query && ctx.query.clientId) ||
(ctx.body && ctx.body.clientId) ||
env.CLIENT_ID
if (!clientId) {
ctx.throw(400, "ClientId not suplied")
}
return clientId
} }

View File

@ -5,10 +5,11 @@ const {
budibaseTempDir, budibaseTempDir,
budibaseAppsDir, budibaseAppsDir,
} = require("../../utilities/budibaseDir") } = require("../../utilities/budibaseDir")
const env = require("../../environment")
exports.fetchAppComponentDefinitions = async function(ctx) { exports.fetchAppComponentDefinitions = async function(ctx) {
const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const masterDb = new CouchDB("clientAppLookup")
const { clientId } = await masterDb.get(ctx.params.appId)
const db = new CouchDB(ClientDb.name(clientId))
const app = await db.get(ctx.params.appId) const app = await db.get(ctx.params.appId)
const componentDefinitions = app.componentLibraries.reduce( const componentDefinitions = app.componentLibraries.reduce(

View File

@ -1,14 +1,16 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const client = require("../../db/clientDb") const client = require("../../db/clientDb")
const newid = require("../../db/newid") const newid = require("../../db/newid")
const env = require("../../environment")
exports.create = async function(ctx) { exports.create = async function(ctx) {
const instanceName = ctx.request.body.name const instanceName = ctx.request.body.name
const appShortId = ctx.params.applicationId.substring(0, 7) const appShortId = ctx.params.applicationId.substring(0, 7)
const instanceId = `inst_${appShortId}_${newid()}` const instanceId = `inst_${appShortId}_${newid()}`
const { applicationId } = ctx.params const { applicationId } = ctx.params
const clientId = env.CLIENT_ID
const masterDb = new CouchDB("clientAppLookup")
const { clientId } = await masterDb.get(applicationId)
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
await db.put({ await db.put({
_id: "_design/database", _id: "_design/database",

View File

@ -1,7 +1,6 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const clientDb = require("../../db/clientDb") const clientDb = require("../../db/clientDb")
const bcrypt = require("../../utilities/bcrypt") const bcrypt = require("../../utilities/bcrypt")
const env = require("../../environment")
const getUserId = userName => `user_${userName}` const getUserId = userName => `user_${userName}`
const { const {
POWERUSER_LEVEL_ID, POWERUSER_LEVEL_ID,
@ -42,8 +41,11 @@ exports.create = async function(ctx) {
const response = await database.post(user) const response = await database.post(user)
const masterDb = new CouchDB("clientAppLookup")
const { clientId } = await masterDb.get(appId)
// the clientDB needs to store a map of users against the app // the clientDB needs to store a map of users against the app
const db = new CouchDB(clientDb.name(env.CLIENT_ID)) const db = new CouchDB(clientDb.name(clientId))
const app = await db.get(appId) const app = await db.get(appId)
app.userInstanceMap = { app.userInstanceMap = {

View File

@ -5,6 +5,7 @@ const {
destroyClientDatabase, destroyClientDatabase,
builderEndpointShouldBlockNormalUsers, builderEndpointShouldBlockNormalUsers,
supertest, supertest,
TEST_CLIENT_ID,
defaultHeaders, defaultHeaders,
} = require("./couchTestUtils") } = require("./couchTestUtils")
@ -51,6 +52,7 @@ describe("/applications", () => {
body: { name: "My App" } body: { name: "My App" }
}) })
}) })
}) })
describe("fetch", () => { describe("fetch", () => {
@ -68,6 +70,37 @@ describe("/applications", () => {
expect(res.body.length).toBe(2) expect(res.body.length).toBe(2)
}) })
it("lists only applications in requested client databse", async () => {
await createApplication(request, "app1")
await createClientDatabase("new_client")
const blah = await request
.post("/api/applications")
.send({ name: "app2", clientId: "new_client"})
.set(defaultHeaders)
.expect('Content-Type', /json/)
//.expect(200)
const client1Res = await request
.get(`/api/applications?clientId=${TEST_CLIENT_ID}`)
.set(defaultHeaders)
.expect('Content-Type', /json/)
.expect(200)
expect(client1Res.body.length).toBe(1)
expect(client1Res.body[0].name).toBe("app1")
const client2Res = await request
.get(`/api/applications?clientId=new_client`)
.set(defaultHeaders)
.expect('Content-Type', /json/)
.expect(200)
expect(client2Res.body.length).toBe(1)
expect(client2Res.body[0].name).toBe("app2")
})
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
const otherApplication = await createApplication(request) const otherApplication = await createApplication(request)
const instance = await createInstance(request, otherApplication._id) const instance = await createInstance(request, otherApplication._id)

View File

@ -9,6 +9,7 @@ const {
const TEST_CLIENT_ID = "test-client-id" const TEST_CLIENT_ID = "test-client-id"
exports.TEST_CLIENT_ID = TEST_CLIENT_ID
exports.supertest = async () => { exports.supertest = async () => {
let request let request
let port = 4002 let port = 4002
@ -59,7 +60,7 @@ exports.createView = async (request, instanceId, view) => {
return res.body return res.body
} }
exports.createClientDatabase = async () => await create(TEST_CLIENT_ID) exports.createClientDatabase = async id => await create(id || TEST_CLIENT_ID)
exports.createApplication = async (request, name = "test_application") => { exports.createApplication = async (request, name = "test_application") => {
const res = await request const res = await request

View File

@ -87,7 +87,6 @@
.form-content { .form-content {
margin-bottom: 20px; margin-bottom: 20px;
} }
.input { .input {

View File

@ -65,7 +65,7 @@
} }
thead { thead {
background: #393C44; background: #393c44;
border: 1px solid #ccc; border: 1px solid #ccc;
height: 40px; height: 40px;
text-align: left; text-align: left;
@ -87,7 +87,7 @@
tbody tr { tbody tr {
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
transition: 0.3s background-color; transition: 0.3s background-color;
color: #393C44; color: #393c44;
font-size: 14px; font-size: 14px;
height: 40px; height: 40px;
} }

View File

@ -7,6 +7,8 @@
export let height export let height
export let width export let width
export let _bb
$: style = buildStyle({ height, width }) $: style = buildStyle({ height, width })
</script> </script>