Browse Source

Merge branch 'next' of github.com:Budibase/budibase into feature/password-reset

gh-pages
mike12345567 5 years ago
parent
commit
2f0c3562ac
  1. 6
      hosting/envoy.dev.yaml.hbs
  2. 21
      packages/auth/src/db/utils.js
  3. 58
      packages/bbui/src/Avatar/Avatar.svelte
  4. 49
      packages/bbui/src/ProgressCircle/ProgressCircle.svelte
  5. 2
      packages/builder/index.html
  6. 2
      packages/builder/src/builderStore/index.js
  7. 11
      packages/builder/src/builderStore/store/admin.js
  8. 4
      packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte
  9. 5
      packages/builder/src/pages/builder/_layout.svelte
  10. 1
      packages/builder/src/pages/index.svelte
  11. 126
      packages/builder/src/pages/portal/_layout.svelte
  12. 0
      packages/builder/src/pages/portal/index.svelte
  13. 118
      packages/builder/src/pages/portal/oauth/index.svelte
  14. 38
      packages/builder/src/pages/portal/oauth/logos/Google.svelte
  15. 2
      packages/builder/vite.config.js
  16. 7
      packages/worker/src/api/controllers/admin/configs.js
  17. 3
      packages/worker/src/api/routes/admin/configs.js
  18. 10
      packages/worker/src/utilities/templates.js

6
hosting/envoy.dev.yaml.hbs

@ -33,7 +33,7 @@ static_resources:
route:
cluster: server-dev
- match: { prefix: "/builder/" }
- match: { prefix: "/" }
route:
cluster: builder-dev
@ -41,10 +41,6 @@ static_resources:
route:
cluster: builder-dev
prefix_rewrite: "/builder/"
# special case in dev to redirect no path to builder
- match: { path: "/" }
redirect: { path_redirect: "/builder/" }
# minio is on the default route because this works
# best, minio + AWS SDK doesn't handle path proxy

21
packages/auth/src/db/utils.js

@ -132,29 +132,32 @@ const determineScopedConfig = async function (db, { type, user, group }) {
}
)
)
const configs = response.rows.map(row => {
function determineScore(row) {
const config = row.doc
// Config is specific to a user and a group
if (config._id.includes(generateConfigID({ type, user, group }))) {
config.score = 4
return 4
} else if (config._id.includes(generateConfigID({ type, user }))) {
// Config is specific to a user only
config.score = 3
return 3
} else if (config._id.includes(generateConfigID({ type, group }))) {
// Config is specific to a group only
config.score = 2
return 2
} else if (config._id.includes(generateConfigID({ type }))) {
// Config is specific to a type only
config.score = 1
return 1
}
return config
})
return 0
}
// Find the config with the most granular scope based on context
const scopedConfig = configs.sort((a, b) => b.score - a.score)[0]
const scopedConfig = response.rows.sort(
(a, b) => determineScore(a) - determineScore(b)
)[0]
return scopedConfig
return scopedConfig.doc
}
exports.generateConfigID = generateConfigID

58
packages/bbui/src/Avatar/Avatar.svelte

@ -1,12 +1,58 @@
<script>
import "@spectrum-css/avatar/dist/index-vars.css"
let sizes = new Map([
["XXS", "--spectrum-alias-avatar-size-50"],
["XS", "--spectrum-alias-avatar-size-75"],
["S", "--spectrum-alias-avatar-size-200"],
["M", "--spectrum-alias-avatar-size-300"],
["L", "--spectrum-alias-avatar-size-500"],
["XL", "--spectrum-alias-avatar-size-600"],
["XXL", "--spectrum-alias-avatar-size-700"],
])
export let size = "M"
export let url = ""
export let disabled = false
export let name = "John Doe"
function getInitials(name) {
let parts = name.split(" ")
return parts.map((name) => name[0]).join("")
}
</script>
<img
class:is-disabled={disabled}
class="spectrum-Avatar"
src={url}
alt="Avatar"
/>
{#if url}
<img
class:is-disabled={disabled}
class="spectrum-Avatar"
src={url}
alt="Avatar"
style="width: var({sizes.get(size)}); height: var({sizes.get(size)});"
/>
{:else}
<div
class:is-disabled={disabled}
style="width: var({sizes.get(size)}); height: var({sizes.get(
size
)}); font-size: calc(var({sizes.get(size)}) / 2)"
>
{getInitials(name)}
</div>
{/if}
<style>
div {
color: white;
display: grid;
place-items: center;
font-weight: 500;
background: rgb(63, 94, 251);
background: linear-gradient(
155deg,
rgba(63, 94, 251, 1) 0%,
rgba(53, 199, 86, 1) 47%
);
border-radius: 50%;
overflow: hidden;
user-select: none;
}
</style>

49
packages/bbui/src/ProgressCircle/ProgressCircle.svelte

@ -1,31 +1,60 @@
<script>
// WIP! Does not yet work.
import "@spectrum-css/progresscircle/dist/index-vars.css"
import { tweened } from "svelte/motion"
import { cubicOut } from "svelte/easing"
export let size = "M"
function convertSize(size) {
switch (size) {
case "S":
return "small"
case "L":
return "large"
default:
return
}
}
export let value = false
export let small
export let large
export let minValue = 0
export let maxValue = 100
let subMask1Style
let subMask2Style
$: calculateSubMasks(value)
function calculateSubMasks(value) {
if (value) {
let percentage = ((value - minValue) / (maxValue - minValue)) * 100
let angle
if (percentage > 0 && percentage <= 50) {
angle = -180 + (percentage / 50) * 180
subMask1Style = `transform: rotate(${angle}deg);`
subMask2Style = "transform: rotate(-180deg);"
} else if (percentage > 50) {
angle = -180 + ((percentage - 50) / 50) * 180
subMask1Style = "transform: rotate(0deg);"
subMask2Style = `transform: rotate(${angle}deg);`
}
}
}
export let overBackground
</script>
<div
on:click
class:spectrum-ProgressBar--indeterminate={!value}
class:spectrum-ProgressCircle--small={small}
class:spectrum-ProgressCircle--large={large}
class="spectrum-ProgressCircle"
class:spectrum-ProgressCircle--overBackground={overBackground}
class="spectrum-ProgressCircle spectrum-ProgressCircle--{convertSize(size)}"
>
<div class="spectrum-ProgressCircle-track" />
<div class="spectrum-ProgressCircle-fills">
<div class="spectrum-ProgressCircle-fillMask1">
<div class="spectrum-ProgressCircle-fillSubMask1">
<div class="spectrum-ProgressCircle-fillSubMask1" style={subMask1Style}>
<div class="spectrum-ProgressCircle-fill" />
</div>
</div>
<div class="spectrum-ProgressCircle-fillMask2">
<div class="spectrum-ProgressCircle-fillSubMask2">
<div class="spectrum-ProgressCircle-fillSubMask2" style={subMask2Style}>
<div class="spectrum-ProgressCircle-fill" />
</div>
</div>

2
packages/builder/index.html

@ -3,7 +3,7 @@
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Budibase Builder</title>
<title>Budibase</title>
<link rel='icon' type='image/png' href='./src/favicon.png'>
</head>
<body id="app">

2
packages/builder/src/builderStore/index.js

@ -2,6 +2,7 @@ import { getFrontendStore } from "./store/frontend"
import { getAutomationStore } from "./store/automation"
import { getHostingStore } from "./store/hosting"
import { getThemeStore } from "./store/theme"
import { getAdminStore } from "./store/admin"
import { derived, writable } from "svelte/store"
import analytics from "analytics"
import { FrontendTypes, LAYOUT_NAMES } from "../constants"
@ -11,6 +12,7 @@ export const store = getFrontendStore()
export const automationStore = getAutomationStore()
export const themeStore = getThemeStore()
export const hostingStore = getHostingStore()
export const adminPanelStore = getAdminStore()
export const currentAsset = derived(store, $store => {
const type = $store.currentFrontEndType

11
packages/builder/src/builderStore/store/admin.js

@ -0,0 +1,11 @@
import { writable } from "svelte/store"
const INITIAL_ADMIN_STATE = {
oauth: [],
}
export const getAdminStore = () => {
const store = writable({ ...INITIAL_ADMIN_STATE })
store.actions = {}
return store
}

4
packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte

@ -119,10 +119,6 @@
top: var(--spacing-l);
right: var(--spacing-xl);
}
.title i:hover {
cursor: pointer;
color: var(--blue);
}
.role-select {
display: flex;

5
packages/builder/src/pages/builder/_layout.svelte

@ -16,11 +16,14 @@
{#if $auth.user}
<div class="root">
<div class="ui-nav">
<div class="home-logo"><img src={Logo} alt="Budibase icon" /></div>
<div class="home-logo">
<img src={Logo} alt="Budibase icon" />
</div>
<div class="nav-section">
<div class="nav-top">
<Navigation>
<Item href="/builder/" icon="Apps" selected>Apps</Item>
<Item href="/builder/oauth/" icon="OAuth" selected>OAuth</Item>
<Item external href="https://portal.budi.live/" icon="Servers">
Hosting
</Item>

1
packages/builder/src/pages/index.svelte

@ -0,0 +1 @@
Index route

126
packages/builder/src/pages/portal/_layout.svelte

@ -0,0 +1,126 @@
<script>
import { isActive, url } from "@roxi/routify"
import { onMount } from "svelte"
import {
Icon,
Avatar,
Search,
Layout,
ProgressCircle,
SideNavigation as Navigation,
SideNavigationItem as Item,
} from "@budibase/bbui"
let orgName, orgLogo, onBoardingProgress, user
async function getInfo() {
// fetch orgInfo
orgName = "ACME Inc."
orgLogo = "https://via.placeholder.com/150"
// set onBoardingProgress
onBoardingProgress = 20
user = { name: "John Doe" }
}
onMount(getInfo)
let menu = [
{ title: "Apps", href: "/portal" },
{ title: "Drafts", href: "/portal/drafts" },
{ title: "Users", href: "/portal/users", heading: "Manage" },
{ title: "Groups", href: "/portal/groups" },
{ title: "Auth", href: "/portal/oauth" },
{ title: "Email", href: "/portal/email" },
{ title: "General", href: "/portal/general", heading: "Settings" },
{ title: "Theming", href: "/portal/theming" },
{ title: "Account", href: "/portal/account" },
]
</script>
<div class="container">
<Layout>
<div class="nav">
<div class="branding">
<div class="name">
<img src={orgLogo} alt="Logotype" />
<span>{orgName}</span>
</div>
<div class="onboarding">
<ProgressCircle size="S" value={onBoardingProgress} />
</div>
</div>
<div class="menu">
<Navigation>
{#each menu as { title, href, heading }}
<Item selected={$isActive(href)} href={$url(href)} {heading}>
{title}
</Item>
{/each}
</Navigation>
</div>
</div>
</Layout>
<div class="main">
<div class="toolbar">
<Search />
<div class="avatar">
<Avatar size="M" name="John Doe" />
<Icon size="XL" name="ChevronDown" />
</div>
</div>
<div class="content">
<slot />
</div>
</div>
</div>
<style>
.container {
min-height: 100vh;
display: grid;
grid-template-columns: 250px 1fr;
}
.main {
display: grid;
grid-template-rows: auto 1fr;
border-left: 2px solid var(--spectrum-alias-background-color-primary);
}
.branding {
display: grid;
grid-template-columns: auto auto;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xl);
}
.name {
display: grid;
grid-template-columns: auto auto;
grid-gap: var(--spacing-m);
align-items: center;
}
.content {
padding: var(--spacing-m);
}
.avatar {
display: grid;
grid-template-columns: auto auto;
place-items: center;
grid-gap: var(--spacing-xs);
}
.avatar:hover {
cursor: pointer;
filter: brightness(110%);
}
.toolbar {
border-bottom: 2px solid var(--spectrum-alias-background-color-primary);
display: grid;
grid-template-columns: 250px auto;
justify-content: space-between;
padding: var(--spacing-m) calc(var(--spacing-xl) * 2);
}
img {
width: 32px;
height: 32px;
}
</style>

0
packages/builder/src/pages/portal/index.svelte

118
packages/builder/src/pages/portal/oauth/index.svelte

@ -0,0 +1,118 @@
<script>
import GoogleLogo from "./logos/Google.svelte"
import {
Button,
Heading,
Divider,
Label,
notifications,
Layout,
Input,
Body,
} from "@budibase/bbui"
import { onMount } from "svelte"
import api from "builderStore/api"
const ConfigTypes = {
Google: "google",
// Github: "github",
// AzureAD: "ad",
}
const ConfigFields = {
Google: ["clientID", "clientSecret", "callbackURL"],
}
let google
async function save(doc) {
try {
// Save an oauth config
const response = await api.post(`/api/admin/configs`, doc)
const json = await response.json()
if (response.status !== 200) throw new Error(json.message)
google._rev = json._rev
google._id = json._id
notifications.success(`Settings saved.`)
} catch (err) {
notifications.error(`Failed to update OAuth settings. ${err}`)
}
}
onMount(async () => {
// fetch the configs for oauth
const googleResponse = await api.get(
`/api/admin/configs/${ConfigTypes.Google}`
)
const googleDoc = await googleResponse.json()
if (!googleDoc._id) {
google = {
type: ConfigTypes.Google,
config: {},
}
} else {
google = googleDoc
}
})
</script>
<section class="container">
<header>
<Heading size="M">OAuth</Heading>
<Body size="S">
Every budibase app comes with basic authentication (email/password)
included. You can add additional authentication methods from the options
below.
</Body>
</header>
<Divider />
{#if google}
<div class="config-form">
<Layout gap="S">
<Heading size="S">
<span>
<GoogleLogo />
Google
</span>
</Heading>
{#each ConfigFields.Google as field}
<div class="form-row">
<Label>{field}</Label>
<Input bind:value={google.config[field]} />
</div>
{/each}
</Layout>
<Button primary on:click={() => save(google)}>Save</Button>
</div>
<Divider />
{/if}
</section>
<style>
section {
margin: 60px 320px;
}
.config-form {
margin-top: 42px;
margin-bottom: 42px;
}
.form-row {
display: grid;
grid-template-columns: 20% 1fr;
grid-gap: var(--spacing-l);
}
span {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
header {
margin-bottom: 42px;
}
</style>

38
packages/builder/src/pages/portal/oauth/logos/Google.svelte

@ -0,0 +1,38 @@
<svg
width="18"
height="18"
viewBox="0 0 268 268"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path
d="M58.8037 109.043C64.0284 93.2355 74.1116 79.4822 87.615 69.7447C101.118
60.0073 117.352 54.783 134 54.8172C152.872 54.8172 169.934 61.5172 183.334
72.4828L222.328 33.5C198.566 12.7858 168.114 0 134 0C81.1817 0 35.711
30.1277 13.8467 74.2583L58.8037 109.043Z"
fill="#EA4335" />
<path
d="M179.113 201.145C166.942 208.995 151.487 213.183 134 213.183C117.418
213.217 101.246 208.034 87.7727 198.369C74.2993 188.703 64.2077 175.044
58.9265 159.326L13.8132 193.574C24.8821 215.978 42.012 234.828 63.2572
247.984C84.5024 261.14 109.011 268.075 134 268C166.752 268 198.041 256.353
221.48 234.5L179.125 201.145H179.113Z"
fill="#34A853" />
<path
d="M221.48 234.5C245.991 211.631 261.903 177.595 261.903 134C261.903
126.072 260.686 117.552 258.866 109.634H134V161.414H205.869C202.329
178.823 192.804 192.301 179.125 201.145L221.48 234.5Z"
fill="#4A90E2" />
<path
d="M58.9265 159.326C56.1947 151.162 54.8068 142.609 54.8172 134C54.8172
125.268 56.213 116.882 58.8037 109.043L13.8467 74.2584C4.64957 92.825
-0.0915078 113.28 1.86708e-05 134C1.86708e-05 155.44 4.96919 175.652
13.8132 193.574L58.9265 159.326Z"
fill="#FBBC05" />
</g>
<defs>
<clipPath id="clip0">
<rect width="268" height="268" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

2
packages/builder/vite.config.js

@ -6,7 +6,7 @@ import path from "path"
export default ({ mode }) => {
const isProduction = mode === "production"
return {
base: "/builder/",
base: "/",
build: {
minify: isProduction,
outDir: "../server/builder",

7
packages/worker/src/api/controllers/admin/configs.js

@ -12,13 +12,10 @@ const GLOBAL_DB = StaticDatabases.GLOBAL.name
exports.save = async function (ctx) {
const db = new CouchDB(GLOBAL_DB)
const { type, config } = ctx.request.body
const { group, user } = config
// insert the type into the doc
config.type = type
const { type, group, user, config } = ctx.request.body
// Config does not exist yet
if (!config._id) {
if (!ctx.request.body._id) {
config._id = generateConfigID({
type,
group,

3
packages/worker/src/api/routes/admin/configs.js

@ -44,6 +44,9 @@ function googleValidation() {
function buildConfigSaveValidation() {
// prettier-ignore
return joiValidator.body(Joi.object({
_id: Joi.string(),
_rev: Joi.string(),
group: Joi.string(),
type: Joi.string().valid(...Object.values(Configs)).required(),
config: Joi.alternatives()
.conditional("type", {

10
packages/worker/src/utilities/templates.js

@ -1,5 +1,5 @@
const CouchDB = require("../db")
const { getConfigParams, StaticDatabases } = require("@budibase/auth").db
const { determineScopedConfig, StaticDatabases } = require("@budibase/auth").db
const {
Configs,
TemplateBindings,
@ -14,12 +14,8 @@ const BASE_COMPANY = "Budibase"
exports.getSettingsTemplateContext = async (purpose, code = null) => {
const db = new CouchDB(StaticDatabases.GLOBAL.name)
const response = await db.allDocs(
getConfigParams(Configs.SETTINGS, {
include_docs: true,
})
)
let settings = response.rows.map(row => row.doc)[0] || {}
// TODO: use more granular settings in the future if required
const settings = await determineScopedConfig(db, { type: Configs.SETTINGS })
if (!settings.platformUrl) {
settings.platformUrl = LOCAL_URL
}

Loading…
Cancel
Save