Merge branch 'dev-experience' of https://github.com/Budibase/budibase into feature/global-user-management
This commit is contained in:
commit
57b3a28708
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -144,8 +144,7 @@ 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({
|
||||||
|
|
Loading…
Reference in New Issue