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

This commit is contained in:
mike12345567 2021-05-05 18:00:22 +01:00
commit 82687bad26
18 changed files with 414 additions and 49 deletions

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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

View File

@ -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
}

View File

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

View File

@ -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>

View File

@ -0,0 +1 @@
Index route

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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",

View File

@ -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,

View File

@ -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", {

View File

@ -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
}