development setup, adding data components

This commit is contained in:
Martin McKeaveney 2020-05-06 10:33:30 +01:00
parent 2afd1cd4dd
commit 392de2efcc
48 changed files with 364 additions and 7915 deletions

7563
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
"publishdev": "lerna run publishdev", "publishdev": "lerna run publishdev",
"publishnpm": "yarn build && lerna publish", "publishnpm": "yarn build && lerna publish",
"clean": "lerna clean", "clean": "lerna clean",
"dev": "lerna run --parallel --stream dev:builder", "dev": "./scripts/symlinkDev.js && lerna run --parallel --stream dev:builder",
"test": "lerna run test", "test": "lerna run test",
"lint": "eslint packages", "lint": "eslint packages",
"lint:fix": "eslint --fix packages", "lint:fix": "eslint --fix packages",

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -151,7 +151,7 @@ export default {
targets: [ targets: [
{ src: "src/index.html", dest: outputpath }, { src: "src/index.html", dest: outputpath },
{ src: "src/favicon.png", dest: outputpath }, { src: "src/favicon.png", dest: outputpath },
{ src: "src/assets", dest: outputpath }, { src: "assets", dest: outputpath },
{ {
src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs", src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs",
dest: outputpath, dest: outputpath,

View File

@ -161,10 +161,6 @@
text-transform: capitalize; text-transform: capitalize;
} }
.select {
background: white;
}
table { table {
border: 1px solid #ccc; border: 1px solid #ccc;
background: #fff; background: #fff;

View File

@ -1,7 +1,7 @@
import api from "builderStore/api" import api from "builderStore/api"
export async function createUser(user, instanceId) { export async function createUser(user, appId, instanceId) {
const CREATE_USER_URL = `/api/${instanceId}/users` const CREATE_USER_URL = `/api/${appId}/${instanceId}/users`
const response = await api.post(CREATE_USER_URL, user) const response = await api.post(CREATE_USER_URL, user)
const json = await response.json() const json = await response.json()
return json.user; return json.user;
@ -21,11 +21,11 @@ export async function deleteRecord(record, instanceId) {
return response return response
} }
export async function loadRecord(key, { appname, instanceId }) { // export async function loadRecord(key, { appname, instanceId }) {
const LOAD_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record${key}` // const LOAD_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record${key}`
const response = await api.get(LOAD_RECORDS_URL) // const response = await api.get(LOAD_RECORDS_URL)
return await response.json() // return await response.json()
} // }
export async function saveRecord(record, instanceId) { export async function saveRecord(record, instanceId) {
const SAVE_RECORDS_URL = `/api/${instanceId}/records` const SAVE_RECORDS_URL = `/api/${instanceId}/records`

View File

@ -10,10 +10,11 @@
$: valid = username && password $: valid = username && password
$: instanceId = $backendUiStore.selectedDatabase.id $: instanceId = $backendUiStore.selectedDatabase.id
$: appId = $store.appId
async function createUser() { async function createUser() {
const user = { name: username, username, password } const user = { name: username, username, password }
const response = await api.createUser(user, instanceId); const response = await api.createUser(user, appId, instanceId);
backendUiStore.actions.users.create(response) backendUiStore.actions.users.create(response)
onClosed() onClosed()
} }

View File

@ -4,8 +4,8 @@
import iframeTemplate from "./iframeTemplate"; import iframeTemplate from "./iframeTemplate";
import { pipe } from "components/common/core" import { pipe } from "components/common/core"
let iframe let iframe
let styles = ""
function transform_component(comp) { function transform_component(comp) {
const props = comp.props || comp const props = comp.props || comp
@ -24,7 +24,15 @@
) )
) )
$: hasComponent = !!$store.currentPreviewItem $: hasComponent = !!$store.currentPreviewItem
$: styles = hasComponent ? $store.currentPreviewItem._css : "" $: {
// Apply the CSS from the currently selected page and its screens
const currentPage = $store.pages[$store.currentPageName];
styles += currentPage._css;
for (let screen of currentPage._screens) {
styles += screen._css;
}
styles = styles
}
$: stylesheetLinks = pipe( $: stylesheetLinks = pipe(
$store.pages.stylesheets, $store.pages.stylesheets,
@ -67,7 +75,7 @@
] ]
} }
}], }],
appRootPath: `/_builder/instance/${$store.appname}/${$backendUiStore.selectedDatabase.id}/`, appRootPath: `/`,
} }
$: selectedComponentId = $store.currentComponentInfo ? $store.currentComponentInfo._id : "" $: selectedComponentId = $store.currentComponentInfo ? $store.currentComponentInfo._id : ""

View File

@ -34,7 +34,7 @@
const onStyleChanged = store.setComponentStyle const onStyleChanged = store.setComponentStyle
function getProps(obj, keys) { function getProps(obj, keys) {
return keys.map((k, i) => [k, obj[k], obj.props._id + i]) return keys.map((key, i) => [key, obj[key], obj.props._id + i])
} }
</script> </script>

View File

@ -24,17 +24,18 @@
<form on:submit|preventDefault class="uk-form-stacked form-root"> <form on:submit|preventDefault class="uk-form-stacked form-root">
{#if componentDef} {#if componentDef}
{#each Object.entries(componentDef.props) as [prop_name, prop_def], index} {#each Object.entries(componentDef.props) as [prop_name, prop_def], index}
<div class="prop-container"> {#if prop_def !== "event"}
<div class="prop-container">
<PropControl
{setProp}
{prop_name}
prop_value={component[prop_name]}
prop_definition={prop_def}
{index}
disabled={false} />
<PropControl </div>
{setProp} {/if}
{prop_name}
prop_value={component[prop_name]}
prop_definition={prop_def}
{index}
disabled={false} />
</div>
{/each} {/each}
{/if} {/if}
</form> </form>

View File

@ -166,7 +166,23 @@ export default {
name: "@budibase/materialdesign-components/recordForm", name: "@budibase/materialdesign-components/recordForm",
}, },
children: [] children: []
} },
{
_component: "@budibase/standard-components/datatable",
name: 'DataTable',
description: 'A table for displaying data from the backend.',
icon: 'ri-archive-drawer-fill',
commonProps: {},
children: []
},
{
_component: "@budibase/standard-components/dataform",
name: 'DataForm',
description: 'Form stuff',
icon: 'ri-file-edit-fill',
commonProps: {},
children: []
},
] ]
}, },
] ]

View File

@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "rollup -c", "build": "rollup -c",
"test": "jest", "test": "jest",
"publishdev": "yarn build && node ./scripts/publishDev.js" "dev:builder": "rollup -cw"
}, },
"jest": { "jest": {
"globals": { "globals": {

View File

@ -102,7 +102,6 @@ export default {
}), }),
builtins(), builtins(),
nodeglobals(), nodeglobals(),
//terser()
], ],
watch: { watch: {
clearScreen: false, clearScreen: false,

View File

@ -1,56 +0,0 @@
const { readdir, stat, copyFile, ensureDir } = require("fs-extra")
const { constants } = require("fs")
const { join, basename } = require("path")
const packagesFolder = ".."
const jsFile = dir => join(dir, "budibase-client.js")
const jsMapFile = dir => join(dir, "budibase-client.js.map")
const sourceJs = jsFile("dist")
const sourceJsMap = jsMapFile("dist")
const appPackages = join(
packagesFolder,
"server",
serverConfig.latestPackagesFolder
)
const publicMain = appName => join(appPackages, appName, "public", "main")
const publicUnauth = appName =>
join(appPackages, appName, "public", "unauthenticated")
;(async () => {
const apps = await readdir(appPackages)
const copySource = file => async toDir => {
await ensureDir(toDir)
const dest = join(toDir, basename(file))
try {
await copyFile(file, dest, constants.COPYFILE_FICLONE)
console.log(`COPIED ${file} to ${dest}`)
} catch (e) {
console.log(`COPY FAILED ${file} to ${dest}: ${e}`)
}
}
const copySourceJs = copySource(sourceJs)
const copySourceJsMap = copySource(sourceJsMap)
for (let app of apps) {
if (app === ".data") continue
if (!(await stat(join(appPackages, app))).isDirectory()) continue
//await copySourceJs(nodeModules(app))
//await copySourceJsMap(nodeModules(app))
await copySourceJs(publicMain(app))
await copySourceJsMap(publicMain(app))
await copySourceJs(publicUnauth(app))
await copySourceJsMap(publicUnauth(app))
await copySource(join("dist", "budibase-client.esm.mjs"))(
join(packagesFolder, "server", "builder")
)
}
})()

View File

@ -1,8 +1,8 @@
import { createApp } from "./createApp" import { createApp } from "./createApp"
import { trimSlash } from "./common/trimSlash" import { trimSlash } from "./common/trimSlash"
import { builtins, builtinLibName } from "./render/builtinComponents" import { builtins, builtinLibName } from "./render/builtinComponents"
import * as standardComponents from "@budibase/standard-components"; import * as standardComponents from "../../standard-components/dist";
import * as materialDesignComponents from "@budibase/materialdesign-components"; import * as materialDesignComponents from "../../materialdesign-components/dist";
/** /**
* create a web application from static budibase definition files. * create a web application from static budibase definition files.

View File

@ -9,7 +9,7 @@
"testbuild": "rollup -w -c rollup.testconfig.js", "testbuild": "rollup -w -c rollup.testconfig.js",
"dev": "run-p start:dev testbuild", "dev": "run-p start:dev testbuild",
"start:dev": "sirv public --single --dev", "start:dev": "sirv public --single --dev",
"publishdev": "yarn build && node ./scripts/publishDev.js" "dev:builder": "rollup -cw"
}, },
"devDependencies": { "devDependencies": {
"@budibase/client": "^0.0.32", "@budibase/client": "^0.0.32",

View File

@ -1,94 +0,0 @@
const { readdir, stat, copyFile, ensureDir } = require("fs-extra")
const { constants } = require("fs")
const { join, basename } = require("path")
const serverConfig = require("../../server/config")()
const packagesFolder = ".."
const jsFile = dir => join(dir, "index.js")
const jsMapFile = dir => join(dir, "index.js.map")
const sourceJs = jsFile("dist")
const sourceJsMap = jsMapFile("dist")
const componentsFile = "components.json"
const appPackages = join(
packagesFolder,
"server",
serverConfig.latestPackagesFolder
)
const publicMain = appName =>
join(
appPackages,
appName,
"public",
"main",
"lib",
"node_modules",
"@budibase",
"materialdesign-components"
)
const publicUnauth = appName =>
join(
appPackages,
appName,
"public",
"unauthenticated",
"lib",
"node_modules",
"@budibase",
"materialdesign-components"
)
const nodeModulesDist = appName =>
join(
appPackages,
appName,
"node_modules",
"@budibase",
"materialdesign-components",
"dist"
)
const nodeModules = appName =>
join(
appPackages,
appName,
"node_modules",
"@budibase",
"materialdesign-components"
)
;(async () => {
const apps = await readdir(appPackages)
const copySource = file => async toDir => {
await ensureDir(toDir)
const dest = join(toDir, basename(file))
try {
await copyFile(file, dest, constants.COPYFILE_FICLONE)
console.log(`COPIED ${file} to ${dest}`)
} catch (e) {
console.log(`COPY FAILED ${file} to ${dest}: ${e}`)
}
}
const copySourceJs = copySource(sourceJs)
const copySourceJsMap = copySource(sourceJsMap)
const copyComponentsJson = copySource(componentsFile)
for (let app of apps) {
if (app === ".data") continue
if (!(await stat(join(appPackages, app))).isDirectory()) continue
await copySourceJs(nodeModulesDist(app))
await copySourceJsMap(nodeModulesDist(app))
await copyComponentsJson(nodeModules(app))
await copySourceJs(join(publicMain(app), "dist"))
await copySourceJsMap(join(publicMain(app), "dist"))
await copySourceJs(join(publicUnauth(app), "dist"))
await copySourceJsMap(join(publicUnauth(app), "dist"))
}
})()

View File

@ -8,24 +8,43 @@ 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");
// query couch for their username // TODO: Don't use this. It can't be relied on
const db = new CouchDB(ctx.params.clientId); const referer = ctx.request.headers.referer.split("/");
const dbUser = await db.query("by_username", { const appId = referer[3];
// find the instance that the user is associated with
const db = new CouchDB(`client-${process.env.CLIENT_ID}`);
const app = await db.get(appId);
const instanceId = app.userInstanceMap[username];
if (!instanceId) ctx.throw(500, "User is not associated with an instance of app", appId)
// Check the user exists in the instance DB by username
const instanceDb = new CouchDB(instanceId);
const { rows } = await instanceDb.query("database/by_username", {
include_docs: true, include_docs: true,
key: username username
}); });
if (rows.length === 0) ctx.throw(500, `User does not exist.`);
const dbUser = rows[0].doc;
// authenticate
if (await bcrypt.compare(password, dbUser.password)) { if (await bcrypt.compare(password, dbUser.password)) {
const payload = { const payload = {
userId: dbUser._id, userId: dbUser._id,
accessLevel: "", accessLevel: "",
instanceId: ctx.params.instanceId instanceId: instanceId
}; };
const token = jwt.sign(payload, ctx.config.secret, { const token = jwt.sign(payload, ctx.config.secret, {
expiresIn: "1 day" expiresIn: "1 day"
}); });
ctx.body = token; ctx.body = {
token,
...dbUser
};
} else { } else {
ctx.throw(401, "Invalid credentials."); ctx.throw(401, "Invalid credentials.");
} }

View File

@ -2,12 +2,20 @@ const CouchDB = require("../../db");
const { homedir } = require("os"); const { homedir } = require("os");
const { resolve, join } = require("path"); const { resolve, join } = require("path");
const isDev = process.env.NODE_ENV !== "production";
exports.fetchAppComponentDefinitions = async function(ctx) { exports.fetchAppComponentDefinitions = async function(ctx) {
const db = new CouchDB(`client-${ctx.params.clientId}`); const db = new CouchDB(`client-${ctx.params.clientId}`);
const app = await db.get(ctx.params.appId) const app = await db.get(ctx.params.appId)
const componentDefinitions = app.componentLibraries.reduce((acc, componentLibrary) => { const componentDefinitions = app.componentLibraries.reduce((acc, componentLibrary) => {
const appDirectory = resolve(homedir(), ".budibase", ctx.params.appId, "node_modules");
let appDirectory = resolve(homedir(), ".budibase", ctx.params.appId, "node_modules");
if (isDev) {
appDirectory = "/tmp/.budibase";
}
const componentJson = require(join(appDirectory, componentLibrary, "components.json")); const componentJson = require(join(appDirectory, componentLibrary, "components.json"));
const result = {}; const result = {};

View File

@ -2,7 +2,6 @@ const CouchDB = require("../../db");
exports.create = async function(ctx) { exports.create = async function(ctx) {
const instanceName = ctx.request.body.name; const instanceName = ctx.request.body.name;
// await couchdb.db.create(instanceName);
const { clientId, applicationId } = ctx.params; const { clientId, applicationId } = ctx.params;
const db = new CouchDB(instanceName); const db = new CouchDB(instanceName);
@ -13,6 +12,13 @@ exports.create = async function(ctx) {
applicationId applicationId
}, },
views: { views: {
by_username: {
map: function(doc) {
if (doc.type === "user") {
emit([doc.username], doc._id);
}
}.toString()
},
by_type: { by_type: {
map: function(doc) { map: function(doc) {
emit([doc.type], doc._id); emit([doc.type], doc._id);

View File

@ -1,13 +1,19 @@
const send = require("koa-send"); const send = require("koa-send");
const { resolve } = require("path") const { resolve, join } = require("path")
const { homedir } = require("os"); const { homedir } = require("os");
const isProduction = process.env.NODE_ENV === "production";
exports.serveBuilder = async function(ctx) { exports.serveBuilder = async function(ctx) {
const builderPath = resolve(process.cwd(), "builder") const builderPath = resolve(process.cwd(), "builder")
await send(ctx, ctx.file, { root: builderPath }) await send(ctx, ctx.file, { root: builderPath })
} }
exports.serveApp = async function(ctx) { exports.serveApp = async function(ctx) {
// ONLY RELEVANT FOR THE CLIENT LIB
// const devPath = join("/tmp", ".budibase", ctx.params.appId);
// TODO: update homedir stuff to wherever budi is run // TODO: update homedir stuff to wherever budi is run
// default to homedir // default to homedir
const appPath = resolve( const appPath = resolve(
@ -18,38 +24,30 @@ exports.serveApp = async function(ctx) {
ctx.isAuthenticated ? "main" : "unauthenticated" ctx.isAuthenticated ? "main" : "unauthenticated"
); );
// TODO: Hook up to JWT auth in real app
// TODO: serve CSS and other assets
// resolve main page if user authenticated
await send(ctx, ctx.file, { root: appPath }) await send(ctx, ctx.file, { root: appPath })
} }
exports.serveComponentLibrary = async function(ctx) { exports.serveComponentLibrary = async function(ctx) {
// TODO: update homedir stuff to wherever budi is run
// default to homedir let componentLibraryPath = join(
const componentLibraryPath = resolve( "/tmp",
homedir(),
".budibase", ".budibase",
ctx.params.appId,
"node_modules",
decodeURI(ctx.query.library), decodeURI(ctx.query.library),
"dist" "dist"
); );
await send(ctx, "/index.js", { root: componentLibraryPath }) if (isProduction) {
} // TODO: update homedir stuff to wherever budi is run
// default to homedir
exports.serveComponentDefinitions = async function(ctx) { componentLibraryPath = resolve(
// TODO: update homedir stuff to wherever budi is run homedir(),
// default to homedir ".budibase",
const componentLibraryPath = resolve( ctx.params.appId,
homedir(), "node_modules",
".budibase", decodeURI(ctx.query.library),
ctx.params.appId, "dist"
"node_modules", );
decodeURI(ctx.query.library), }
"dist"
);
await send(ctx, "/index.js", { root: componentLibraryPath }) await send(ctx, "/index.js", { root: componentLibraryPath })
} }

View File

@ -25,12 +25,12 @@ exports.create = async function(ctx) {
}); });
// 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 clientDB = new CouchDB(process.env.CLIENT_ID); const clientDb = new CouchDB(`client-${process.env.CLIENT_ID}`);
const app = await clientDB.get(ctx.params.appId); const app = await clientDb.get(ctx.params.appId);
app.userInstanceMap = { app.userInstanceMap = {
...app.userInstanceMap, ...app.userInstanceMap,
[response._id]: ctx.params.instanceId [username]: ctx.params.instanceId
} }
await clientDb.put(app); await clientDb.put(app);

View File

@ -10,7 +10,7 @@ const controller = {
const response = []; const response = [];
for (let name in designDoc.views) { for (let name in designDoc.views) {
if (!name.startsWith("all") && name !== "by_type") { if (!name.startsWith("all") && name !== "by_type" && name !== "by_username") {
response.push({ response.push({
name, name,
...designDoc.views[name] ...designDoc.views[name]

View File

@ -36,7 +36,10 @@ module.exports = app => {
// TODO: temp dev middleware // TODO: temp dev middleware
// ctx.sessionId = ctx.session._sessCtx.externalKey // ctx.sessionId = ctx.session._sessCtx.externalKey
// ctx.session.accessed = true // ctx.session.accessed = true
ctx.config = { latestPackagesFolder: resolve(homedir(), ".budibase") } ctx.config = {
latestPackagesFolder: resolve(homedir(), ".budibase"),
secret: "foo"
}
await next(); await next();
}); });

View File

@ -11,30 +11,30 @@ const {
const router = Router() const router = Router()
router.post("/_builder/api/:appname/pages/:pageName", async ctx => { router.post("/_builder/api/:appId/pages/:pageName", async ctx => {
await buildPage( await buildPage(
ctx.config, ctx.config,
ctx.params.appname, ctx.params.appId,
ctx.params.pageName, ctx.params.pageName,
ctx.request.body ctx.request.body
) )
ctx.response.status = StatusCodes.OK ctx.response.status = StatusCodes.OK
}) })
router.get("/_builder/api/:appname/pages/:pagename/screens", async ctx => { router.get("/_builder/api/:appId/pages/:pagename/screens", async ctx => {
ctx.body = await listScreens( ctx.body = await listScreens(
ctx.config, ctx.config,
ctx.params.appname, ctx.params.appId,
ctx.params.pagename ctx.params.pagename
) )
ctx.response.status = StatusCodes.OK ctx.response.status = StatusCodes.OK
}) })
router router
.post("/_builder/api/:appname/pages/:pagename/screen", async ctx => { .post("/_builder/api/:appId/pages/:pagename/screen", async ctx => {
ctx.body = await saveScreen( ctx.body = await saveScreen(
ctx.config, ctx.config,
ctx.params.appname, ctx.params.appId,
ctx.params.pagename, ctx.params.pagename,
ctx.request.body ctx.request.body
) )

View File

@ -6,7 +6,7 @@ const {
readFile, readFile,
writeJSON, writeJSON,
} = require("fs-extra") } = require("fs-extra")
const { join, resolve, dirname } = require("path") const { join, resolve } = require("path")
const sqrl = require("squirrelly") const sqrl = require("squirrelly")
const { convertCssToFiles } = require("./convertCssToFiles") const { convertCssToFiles } = require("./convertCssToFiles")
const publicPath = require("./publicPath") const publicPath = require("./publicPath")

View File

@ -4977,9 +4977,9 @@ supports-color@^7.1.0:
has-flag "^4.0.0" has-flag "^4.0.0"
svelte@^3.9.2: svelte@^3.9.2:
version "3.21.0" version "3.22.2"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.21.0.tgz#e326591cb92267e90b4fb5b961d80d9763792551" resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.22.2.tgz#06585244191bf7a112af2a0025610f33d77c3715"
integrity sha512-smh3LZKPCGJ+UXa0iZvUmuDctPYCwPY1opmClTWTm+l6e4y9FHLoCZMiue8YIeyc9JvlGT/EK0xry0diXjFDZQ== integrity sha512-DxumO0+vvHA6NSc2jtVty08I8lFI43q8P2zX6JxZL8J1kqK5NVjad6TRM/twhnWXC+QScnwkZ15O6X1aTsEKTA==
symbol-tree@^3.2.2: symbol-tree@^3.2.2:
version "3.2.4" version "3.2.4"

View File

@ -262,6 +262,29 @@
"color": "colour" "color": "colour"
} }
}, },
"datatable": {
"description": "Other thingwy",
"props": {
"_viewName": "string",
"_instanceId": "string",
"model": {
"store": true,
"type": "options",
"default": "",
"options": [
"something",
"something"
]
}
}
},
"dataform": {
"description": "an HTML table that fetches data from a model or view and displays it.",
"props": {
"_viewName": "string",
"_instanceId": "string"
}
},
"link": { "link": {
"description": "an HTML anchor <a> tag", "description": "an HTML anchor <a> tag",
"props": { "props": {

View File

@ -9,7 +9,7 @@
"testbuild": "rollup -w -c rollup.testconfig.js", "testbuild": "rollup -w -c rollup.testconfig.js",
"dev": "run-p start:dev testbuild", "dev": "run-p start:dev testbuild",
"start:dev": "sirv public --single --dev", "start:dev": "sirv public --single --dev",
"publishdev": "yarn build && node ./scripts/publishDev.js" "dev:builder": "rollup -cw"
}, },
"devDependencies": { "devDependencies": {
"@budibase/client": "^0.0.32", "@budibase/client": "^0.0.32",

View File

@ -1,88 +0,0 @@
const { readdir, stat, copyFile, ensureDir } = require("fs-extra")
const { constants } = require("fs")
const { join, basename } = require("path")
const serverConfig = require("../../server/config")()
const packagesFolder = ".."
const jsFile = dir => join(dir, "index.js")
const jsMapFile = dir => join(dir, "index.js.map")
const sourceJs = jsFile("dist")
const sourceJsMap = jsMapFile("dist")
const componentsFile = "components.json"
const appPackages = join(
packagesFolder,
"server",
resolve(homedir(), ".budibase")
)
const publicMain = appName =>
join(
appPackages,
appName,
"public",
"main",
"lib",
"node_modules",
"@budibase",
"standard-components"
)
const publicUnauth = appName =>
join(
appPackages,
appName,
"public",
"unauthenticated",
"lib",
"node_modules",
"@budibase",
"standard-components"
)
const nodeModulesDist = appName =>
join(
appPackages,
appName,
"node_modules",
"@budibase",
"standard-components",
"dist"
)
const nodeModules = appName =>
join(appPackages, appName, "node_modules", "@budibase", "standard-components")
;(async () => {
const apps = await readdir(appPackages)
const copySource = file => async toDir => {
await ensureDir(toDir)
const dest = join(toDir, basename(file))
try {
await copyFile(file, dest, constants.COPYFILE_FICLONE)
console.log(`COPIED ${file} to ${dest}`)
} catch (e) {
console.log(`COPY FAILED ${file} to ${dest}: ${e}`)
}
}
const copySourceJs = copySource(sourceJs)
const copySourceJsMap = copySource(sourceJsMap)
const copyComponentsJson = copySource(componentsFile)
for (let app of apps) {
if (app === ".data") continue
if (!(await stat(join(appPackages, app))).isDirectory()) continue
await copySourceJs(nodeModulesDist(app))
await copySourceJsMap(nodeModulesDist(app))
await copyComponentsJson(nodeModules(app))
await copySourceJs(join(publicMain(app), "dist"))
await copySourceJsMap(join(publicMain(app), "dist"))
await copySourceJs(join(publicUnauth(app), "dist"))
await copySourceJsMap(join(publicUnauth(app), "dist"))
}
})()

View File

@ -0,0 +1,27 @@
<script>
import { onMount } from "svelte";
export let _bb
export let _viewName
export let _instanceId
let username
let password
</script>
<form class="uk-form">
<div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Username</label>
<input class="uk-input" type="text" bind:value={username} />
</div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Password</label>
<input class="uk-input" type="password" bind:value={password} />
</div>
</div>
</form>
<style>
</style>

View File

@ -0,0 +1,110 @@
<script>
import { onMount } from "svelte";
// import { cssVars, createClasses } from "./cssVars"
// import { buildStyle } from "./buildStyle"
export let _bb
export let onLoad
export let _viewName
export let _instanceId
let cssVariables
let headers = []
let data = []
async function fetchData() {
const FETCH_RECORDS_URL = `/api/${_instanceId}/${_viewName}/records`;
const response = await _bb.api.get(FETCH_RECORDS_URL);
if (response.status === 200) {
const json = await response.json();
if (json.length > 0) {
data = json;
headers = Object.keys(data[0]);
} else {
console.log("NO DATA");
}
} else {
throw new Error("Failed to fetch records..");
console.log("FAILED");
}
}
onMount(async () => {
await fetchData();
})
</script>
<!-- This prop was in the old one -->
<!-- use:cssVars={cssVariables} -->
<table class="uk-table">
<thead>
<tr>
{#each headers as header}
<th>{header}</th>
{/each}
</tr>
</thead>
<tbody>
{#each data as row}
<tr>
{#each headers as header}
{#if row[header]}
<td>
{row[header]}
</td>
{/if}
{/each}
</tr>
{/each}
</tbody>
</table>
<!-- <button
bind:this={theButton}
use:cssVars={cssVariables}
class="{className}
{customClasses}"
disabled={disabled || false}
on:click={clickHandler}
style={buttonStyles}>
{#if !_bb.props._children || _bb.props._children.length === 0}
{contentText}
{/if}
</button> -->
<style>
table {
border: 1px solid #ccc;
background: #fff;
border-radius: 3px;
border-collapse: collapse;
}
thead {
background: #f9f9f9;
border: 1px solid #ccc;
}
thead th {
color: var(--button-text);
text-transform: capitalize;
font-weight: 500;
font-size: 14px;
text-rendering: optimizeLegibility;
letter-spacing: 1px;
}
tbody tr {
border-bottom: 1px solid #ccc;
transition: 0.3s background-color;
color: var(--secondary100);
font-size: 14px;
}
tbody tr:hover {
background: #fafafa;
}
</style>

View File

@ -4,7 +4,6 @@
export let usernameLabel = "Username" export let usernameLabel = "Username"
export let passwordLabel = "Password" export let passwordLabel = "Password"
export let loginButtonLabel = "Login" export let loginButtonLabel = "Login"
export let loginRedirect = ""
export let logo = "" export let logo = ""
export let buttonClass = "" export let buttonClass = ""
export let inputClass = "" export let inputClass = ""
@ -13,8 +12,8 @@
let username = "" let username = ""
let password = "" let password = ""
let busy = false let loading = false
let incorrect = false let error = false
let _logo = "" let _logo = ""
let _buttonClass = "" let _buttonClass = ""
let _inputClass = "" let _inputClass = ""
@ -25,32 +24,25 @@
_inputClass = inputClass || "default-input" _inputClass = inputClass || "default-input"
} }
const login = () => { const login = async () => {
busy = true loading = true
_bb.api const response = _bb.api.post("/api/authenticate", { username, password });
.post("/api/authenticate", { username, password })
.then(r => { if (response.status === 200) {
busy = false const json = await response.json();
if (r.status === 200) { localStorage.setItem("budibase:token", json.token);
return r.json() // TODO: possibly do something with the user information in the response?
} else { location.reload()
incorrect = true } else {
return loading = false
} error = true
}) }
.then(user => {
if (user) {
localStorage.setItem("budibase:user", JSON.stringify(user))
location.reload()
}
})
} }
</script> </script>
<div class="root"> <div class="root">
<div class="content"> <div class="content">
{#if _logo} {#if _logo}
<div class="logo-container"> <div class="logo-container">
<img src={_logo} alt="logo" /> <img src={_logo} alt="logo" />
@ -69,17 +61,15 @@
</div> </div>
<div class="login-button-container"> <div class="login-button-container">
<button disabled={busy} on:click={login} class={_buttonClass}> <button disabled={loading} on:click={login} class={_buttonClass}>
{loginButtonLabel} {loginButtonLabel}
</button> </button>
</div> </div>
{#if incorrect} {#if error}
<div class="incorrect-details-panel">Incorrect username or password</div> <div class="incorrect-details-panel">Incorrect username or password</div>
{/if} {/if}
</div> </div>
</div> </div>
<style> <style>

View File

@ -14,3 +14,5 @@ export { default as link } from "./Link.svelte"
export { default as image } from "./Image.svelte" export { default as image } from "./Image.svelte"
export { default as icon } from "./Icon.svelte" export { default as icon } from "./Icon.svelte"
export { default as Navigation } from "./Navigation.svelte" export { default as Navigation } from "./Navigation.svelte"
export { default as datatable } from "./DataTable.svelte"
export { default as dataform } from "./DataForm.svelte"

43
scripts/symlinkDev.js Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env node
/**
This script symlinks the budibase component and client paths to the
ones that exist in your local development directories. This means you
can work your budibase apps but also change code for the components
and client library in real time.
*/
const fs = require("fs");
const { resolve } = require("path")
const devDir = "/tmp/.budibase/@budibase";
// create the dev directory if it doesn't exist
if (!fs.existsSync(devDir)) {
fs.mkdirSync(devDir, { recursive: true });
}
const SYMLINK_PATHS = [
{
symlink: "/tmp/.budibase/@budibase/materialdesign-components",
destination: resolve("packages/materialdesign-components"),
},
{
symlink: "/tmp/.budibase/@budibase/standard-components",
destination: resolve("packages/standard-components")
},
{
symlink: "/tmp/.budibase/budibase-client.esm.mjs",
destination: resolve("packages/client/dist/budibase-client.esm.mjs")
},
{
symlink: "/tmp/.budibase/budibase-client.js",
destination: resolve("packages/client/dist/budibase-client.js"),
}
]
SYMLINK_PATHS.forEach(sym => {
fs.symlinkSync(sym.destination, sym.symlink);
});
console.log("Dev Symlinks Created Successfully.")