Merge branch 'dev-experience' of https://github.com/Budibase/budibase into feature/global-user-management

This commit is contained in:
Martin McKeaveney 2021-04-07 17:17:00 +01:00
commit 57b3a28708
20 changed files with 122 additions and 80 deletions

1
.gitignore vendored
View File

@ -63,6 +63,7 @@ typings/
# dotenv environment variables file # dotenv environment variables file
.env .env
!hosting/.env !hosting/.env
hosting/generated-envoy.dev.yaml
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache

View File

@ -27,7 +27,7 @@ services:
restart: always restart: always
image: envoyproxy/envoy:v1.16-latest image: envoyproxy/envoy:v1.16-latest
volumes: volumes:
- ./envoy.dev.yaml:/etc/envoy/envoy.yaml - ./generated-envoy.dev.yaml:/etc/envoy/envoy.yaml
ports: ports:
- "${MAIN_PORT}:10000" - "${MAIN_PORT}:10000"
depends_on: depends_on:

View File

@ -106,7 +106,7 @@ static_resources:
- endpoint: - endpoint:
address: address:
socket_address: socket_address:
address: 172.17.0.1 address: {{ address }}
port_value: 4001 port_value: 4001
- name: builder-dev - name: builder-dev
@ -120,6 +120,6 @@ static_resources:
- endpoint: - endpoint:
address: address:
socket_address: socket_address:
address: 172.17.0.1 address: {{ address }}
port_value: 3000 port_value: 3000

View File

@ -23,7 +23,9 @@
"publishdev": "lerna run publishdev", "publishdev": "lerna run publishdev",
"publishnpm": "yarn build && lerna publish --force-publish", "publishnpm": "yarn build && lerna publish --force-publish",
"restore": "npm run clean && npm run bootstrap && npm run build", "restore": "npm run clean && npm run bootstrap && npm run build",
"nuke": "rimraf ~/.budibase && npm run restore && lerna run --parallel dev:stack:nuke", "nuke": "npm run nuke:packages && npm run nuke:docker",
"nuke:packages": "npm run restore",
"nuke:docker": "lerna run --parallel dev:stack:nuke",
"clean": "lerna clean", "clean": "lerna clean",
"kill-port": "kill-port 4001", "kill-port": "kill-port 4001",
"dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1", "dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1",

View File

@ -48,11 +48,11 @@ export default function positionDropdown(element, { anchor, align }) {
element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
const resizeObserver = new ResizeObserver(entries => { const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) { entries.forEach(() => {
dimensions = getDimensions() dimensions = getDimensions()
element.style[positionSide] = `${dimensions[positionSide]}px` element.style[positionSide] = `${dimensions[positionSide]}px`
element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
} })
}) })
resizeObserver.observe(anchor) resizeObserver.observe(anchor)

View File

@ -11,8 +11,6 @@ const createNotificationStore = () => {
}) })
} }
let timers = []
const notifications = derived(_notifications, ($_notifications, set) => { const notifications = derived(_notifications, ($_notifications, set) => {
set($_notifications) set($_notifications)
if ($_notifications.length > 0) { if ($_notifications.length > 0) {

View File

@ -109,15 +109,17 @@ context("Create a View", () => {
.find(".ag-cell") .find(".ag-cell")
.then($values => { .then($values => {
const values = Array.from($values).map(value => value.textContent) const values = Array.from($values).map(value => value.textContent)
expect(values.sort()).to.deep.eq([ expect(values.sort()).to.deep.eq(
"Students", [
"23.333333333333332", "Students",
"1650", "23.333333333333332",
"3", "1650",
"25", "3",
"20", "25",
"70", "20",
].sort()) "70",
].sort()
)
}) })
}) })

View File

@ -57,9 +57,9 @@ Cypress.Commands.add("createApp", name => {
Cypress.Commands.add("deleteApp", name => { Cypress.Commands.add("deleteApp", name => {
cy.visit(`localhost:${Cypress.env("PORT")}/builder`) cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
cy.get("body").then($body => { cy.get(".apps").then($apps => {
cy.wait(1000) cy.wait(1000)
if ($body.find(`[data-cy="app-${name}"]`).length) { if ($apps.find(`[data-cy="app-${name}"]`).length) {
cy.get(`[data-cy="app-${name}"] a`).click() cy.get(`[data-cy="app-${name}"] a`).click()
cy.get("[data-cy=settings-icon]").click() cy.get("[data-cy=settings-icon]").click()
cy.get(".modal-content").within(() => { cy.get(".modal-content").within(() => {

View File

@ -20,6 +20,9 @@ export const get = apiCall("GET")
export const patch = apiCall("PATCH") export const patch = apiCall("PATCH")
export const del = apiCall("DELETE") export const del = apiCall("DELETE")
export const put = apiCall("PUT") export const put = apiCall("PUT")
export const getBuilderCookie = async () => {
await post("/api/builder/login", {})
}
export default { export default {
post: apiCall("POST"), post: apiCall("POST"),
@ -27,4 +30,5 @@ export default {
patch: apiCall("PATCH"), patch: apiCall("PATCH"),
delete: apiCall("DELETE"), delete: apiCall("DELETE"),
put: apiCall("PUT"), put: apiCall("PUT"),
getBuilderCookie,
} }

View File

@ -6,6 +6,7 @@ import { derived, writable } from "svelte/store"
import analytics from "analytics" import analytics from "analytics"
import { FrontendTypes, LAYOUT_NAMES } from "../constants" import { FrontendTypes, LAYOUT_NAMES } from "../constants"
import { findComponent } from "./storeUtils" import { findComponent } from "./storeUtils"
import { getBuilderCookie } from "./api"
export const store = getFrontendStore() export const store = getFrontendStore()
export const automationStore = getAutomationStore() export const automationStore = getAutomationStore()
@ -57,6 +58,8 @@ export const selectedAccessRole = writable("BASIC")
export const initialise = async () => { export const initialise = async () => {
try { try {
// TODO this needs to be replaced by a real login
await getBuilderCookie()
await analytics.activate() await analytics.activate()
analytics.captureEvent("Builder Started") analytics.captureEvent("Builder Started")
} catch (err) { } catch (err) {

View File

@ -26,7 +26,7 @@
notifier.success(`View ${name} created`) notifier.success(`View ${name} created`)
onClosed() onClosed()
analytics.captureEvent("View Created", { name }) analytics.captureEvent("View Created", { name })
$goto(`../../../view/${name}`) $goto(`../../view/${name}`)
} }
</script> </script>

View File

@ -45,65 +45,71 @@
} }
</script> </script>
<div class="root"> {#await promise}
<div class="top-nav"> <!-- This should probably be some kind of loading state? -->
<div class="topleftnav"> <div class="loading" />
<button class="home-logo"> {:then _}
<img <div class="root">
src={Logo} <div class="top-nav">
alt="budibase icon" <div class="topleftnav">
on:click={() => $goto(`/builder/`)} /> <button class="home-logo">
</button> <img
src={Logo}
alt="budibase icon"
on:click={() => $goto(`/builder/`)} />
</button>
<!-- This gets all indexable subroutes and sticks them in the top nav. --> <!-- This gets all indexable subroutes and sticks them in the top nav. -->
{#each $layout.children as { path, title }} {#each $layout.children as { path, title }}
<span <span
class:active={$isActive(path)} class:active={$isActive(path)}
class="topnavitem" class="topnavitem"
on:click={topItemNavigate(path)}> on:click={topItemNavigate(path)}>
{title} {title}
</span> </span>
{/each} {/each}
</div>
<div class="toprightnav">
<ThemeEditorDropdown />
<FeedbackNavLink />
<div class="topnavitemright">
<a
target="_blank"
href="https://github.com/Budibase/budibase/discussions">
<i class="ri-github-fill" />
</a>
</div> </div>
<SettingsLink /> <div class="toprightnav">
<ThemeEditorDropdown />
<FeedbackNavLink />
<div class="topnavitemright">
<a
target="_blank"
href="https://github.com/Budibase/budibase/discussions">
<i class="ri-github-fill" />
</a>
</div>
<SettingsLink />
<Button
secondary
on:click={() => {
window.open(`/${application}`)
}}>
Preview
</Button>
</div>
</div>
<div class="beta">
<Button <Button
secondary secondary
on:click={() => { href="https://github.com/Budibase/budibase/discussions/categories/ideas">
window.open(`/${application}`) Request feature
}}>
Preview
</Button> </Button>
</div> </div>
</div>
<div class="beta">
<Button
secondary
href="https://github.com/Budibase/budibase/discussions/categories/ideas">
Request feature
</Button>
</div>
{#await promise}
<!-- This should probably be some kind of loading state? -->
<div />
{:then _}
<slot /> <slot />
{:catch error} </div>
<p>Something went wrong: {error.message}</p> {:catch error}
{/await} <p>Something went wrong: {error.message}</p>
</div> {/await}
<style> <style>
.loading {
min-height: 100%;
height: 100%;
width: 100%;
background: var(--background);
}
.root { .root {
min-height: 100%; min-height: 100%;
height: 100%; height: 100%;

View File

@ -18,6 +18,7 @@
let previousType let previousType
let previousAsset let previousAsset
let previousComponentId let previousComponentId
let hydrationComplete = false
// Hydrate state from URL params // Hydrate state from URL params
$: hydrateStateFromURL($params, $leftover) $: hydrateStateFromURL($params, $leftover)
@ -30,6 +31,12 @@
) )
const hydrateStateFromURL = (params, leftover) => { const hydrateStateFromURL = (params, leftover) => {
if (hydrationComplete) {
return
} else {
hydrationComplete = true
}
// Do nothing if no asset type, as that means we've left the page // Do nothing if no asset type, as that means we've left the page
if (!params.assetType) { if (!params.assetType) {
return return

View File

@ -1,2 +1,2 @@
<!-- routify:options index=1 --> <!-- routify:options index=1 -->
<slot /> <slot />

View File

@ -27,7 +27,7 @@ export default ({ mode }) => {
}), }),
], ],
optimizeDeps: { optimizeDeps: {
include: ['ag-grid-community'], include: ["ag-grid-community"],
exclude: ["@roxi/routify"], exclude: ["@roxi/routify"],
}, },
resolve: { resolve: {

View File

@ -2,6 +2,11 @@
const compose = require("docker-compose") const compose = require("docker-compose")
const path = require("path") const path = require("path")
const fs = require("fs") const fs = require("fs")
const { processStringSync } = require("@budibase/string-templates")
function isLinux() {
return process.platform !== "darwin" && process.platform !== "win32"
}
// This script wraps docker-compose allowing you to manage your dev infrastructure with simple commands. // This script wraps docker-compose allowing you to manage your dev infrastructure with simple commands.
const CONFIG = { const CONFIG = {
@ -17,6 +22,16 @@ const Commands = {
} }
async function init() { async function init() {
// generate envoy file, always do this incase it has changed
const hostingPath = path.join(process.cwd(), "..", "..", "hosting")
const envoyHbsPath = path.join(hostingPath, "envoy.dev.yaml.hbs")
const envoyOutputPath = path.join(hostingPath, "generated-envoy.dev.yaml")
const contents = fs.readFileSync(envoyHbsPath, "utf8")
const config = {
address: isLinux() ? "172.17.0.1" : "host.docker.internal",
}
fs.writeFileSync(envoyOutputPath, processStringSync(contents, config))
const envFilePath = path.join(process.cwd(), ".env") const envFilePath = path.join(process.cwd(), ".env")
if (fs.existsSync(envFilePath)) { if (fs.existsSync(envFilePath)) {
return return

View File

@ -8,6 +8,7 @@ const { setCookie } = require("../../utilities")
const { outputProcessing } = require("../../utilities/rowProcessor") const { outputProcessing } = require("../../utilities/rowProcessor")
const { ViewNames } = require("../../db/utils") const { ViewNames } = require("../../db/utils")
const { UserStatus } = require("../../constants") const { UserStatus } = require("../../constants")
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
const INVALID_ERR = "Invalid Credentials" const INVALID_ERR = "Invalid Credentials"
@ -69,6 +70,11 @@ exports.authenticate = async ctx => {
} }
} }
exports.builderLogin = async ctx => {
await setBuilderToken(ctx)
ctx.status = 200
}
exports.fetchSelf = async ctx => { exports.fetchSelf = async ctx => {
const { userId, appId } = ctx.user const { userId, appId } = ctx.user
/* istanbul ignore next */ /* istanbul ignore next */

View File

@ -1,9 +1,13 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const controller = require("../controllers/auth") const controller = require("../controllers/auth")
const authorized = require("../../middleware/authorized")
const { BUILDER } = require("../../utilities/security/permissions")
const router = Router() const router = Router()
router.post("/api/authenticate", controller.authenticate) router.post("/api/authenticate", controller.authenticate)
// TODO: this is a hack simply to make sure builder has a cookie until auth reworked
router.post("/api/builder/login", authorized(BUILDER), controller.builderLogin)
// doesn't need authorization as can only fetch info about self // doesn't need authorization as can only fetch info about self
router.get("/api/self", controller.fetchSelf) router.get("/api/self", controller.fetchSelf)

View File

@ -42,11 +42,6 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
const isAdmin = ADMIN_ROLES.includes(role._id) const isAdmin = ADMIN_ROLES.includes(role._id)
const isAuthed = ctx.auth.authenticated const isAuthed = ctx.auth.authenticated
// TODO: this was added while we work towards a better auth method
if (permType === PermissionTypes.BUILDER) {
return next()
}
const { basePermissions, permissions } = await getUserPermissions( const { basePermissions, permissions } = await getUserPermissions(
ctx.appId, ctx.appId,
role._id role._id

View File

@ -143,9 +143,8 @@ describe("Authorization middleware", () => {
expect(config.next).toHaveBeenCalled() expect(config.next).toHaveBeenCalled()
}) })
// TODO: this has been skipped while auth is still in flux it("throws if the user has only builder permissions", async () => {
xit("throws if the user has only builder permissions", async () => {
config.setEnvironment(false) config.setEnvironment(false)
config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER) config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER)
config.setUser({ config.setUser({