Merge pull request #1491 from Budibase/admin/user-management-ui
User Management UI and Settings Fixes
This commit is contained in:
commit
bc94dfa86b
|
@ -16,7 +16,10 @@
|
|||
|
||||
function getInitials(name) {
|
||||
let parts = name.split(" ")
|
||||
return parts.map(name => name[0]).join("")
|
||||
if (parts.length > 0) {
|
||||
return parts.map(name => name[0]).join("")
|
||||
}
|
||||
return name
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -282,6 +282,7 @@
|
|||
{#if sortedRows?.length && fields.length}
|
||||
{#each sortedRows as row, idx}
|
||||
<tr
|
||||
on:click={() => dispatch("click", row)}
|
||||
on:click={() => toggleSelectRow(row)}
|
||||
class="spectrum-Table-row"
|
||||
class:hidden={idx < firstVisibleRow || idx > lastVisibleRow}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
export let selected = false
|
||||
export let open = false
|
||||
export let href = false
|
||||
export let title
|
||||
export let icon
|
||||
</script>
|
||||
|
@ -10,7 +11,7 @@
|
|||
class:is-open={open}
|
||||
class="spectrum-TreeView-item"
|
||||
>
|
||||
<a on:click class="spectrum-TreeView-itemLink" href="#">
|
||||
<a on:click class="spectrum-TreeView-itemLink" {href}>
|
||||
{#if $$slots.default}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-ChevronRight100 spectrum-TreeView-itemIndicator"
|
||||
|
|
|
@ -4,16 +4,17 @@
|
|||
import { routes } from "../.routify/routes"
|
||||
import { initialise } from "builderStore"
|
||||
import { NotificationDisplay } from "@budibase/bbui"
|
||||
import { parse, stringify } from "qs"
|
||||
|
||||
onMount(async () => {
|
||||
await initialise()
|
||||
})
|
||||
|
||||
const config = {}
|
||||
const queryHandler = { parse, stringify }
|
||||
</script>
|
||||
|
||||
<NotificationDisplay />
|
||||
<Router {routes} {config} />
|
||||
<Router {routes} config={{ queryHandler }} />
|
||||
<div class="modal-container" />
|
||||
|
||||
<style>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { writable } from "svelte/store"
|
||||
import api from "builderStore/api"
|
||||
|
||||
export default function (url) {
|
||||
const store = writable({ status: "LOADING", data: {}, error: {} })
|
||||
|
||||
async function get() {
|
||||
store.update(u => ({ ...u, status: "LOADING" }))
|
||||
try {
|
||||
const response = await api.get(url)
|
||||
store.set({ data: await response.json(), status: "SUCCESS" })
|
||||
} catch (e) {
|
||||
store.set({ data: {}, error: e, status: "ERROR" })
|
||||
}
|
||||
}
|
||||
|
||||
get()
|
||||
|
||||
return { subscribe: store.subscribe, refresh: get }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export { default as fetchData } from "./fetchData"
|
||||
export {
|
||||
buildStyle,
|
||||
convertCamel,
|
||||
pipe,
|
||||
capitalise,
|
||||
get_name,
|
||||
get_capitalised_name,
|
||||
} from "./helpers"
|
|
@ -0,0 +1,2 @@
|
|||
export { emailValidator, requiredValidator } from "./validators"
|
||||
export { createValidationStore } from "./validation"
|
|
@ -0,0 +1,23 @@
|
|||
import { writable, derived } from "svelte/store"
|
||||
|
||||
export function createValidationStore(initialValue, ...validators) {
|
||||
let touched = false
|
||||
|
||||
const value = writable(initialValue || "")
|
||||
const error = derived(value, $v => validate($v, validators))
|
||||
const touchedStore = derived(value, () => {
|
||||
if (!touched) {
|
||||
touched = true
|
||||
return false
|
||||
}
|
||||
return touched
|
||||
})
|
||||
|
||||
return [value, error, touchedStore]
|
||||
}
|
||||
|
||||
function validate(value, validators) {
|
||||
const failing = validators.find(v => v(value) !== true)
|
||||
|
||||
return failing && failing(value)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
export function emailValidator(value) {
|
||||
return (
|
||||
(value &&
|
||||
!!value.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
)) ||
|
||||
"Please enter a valid email"
|
||||
)
|
||||
}
|
||||
|
||||
export function requiredValidator(value) {
|
||||
return (
|
||||
(value !== undefined && value !== null && value !== "") ||
|
||||
"This field is required"
|
||||
)
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { page, goto } from "@roxi/routify"
|
||||
import { auth } from "stores/backend"
|
||||
import { admin } from "stores/portal"
|
||||
|
||||
|
@ -22,7 +22,12 @@
|
|||
|
||||
// Redirect to log in at any time if the user isn't authenticated
|
||||
$: {
|
||||
if (loaded && hasAdminUser && !$auth.user) {
|
||||
if (
|
||||
!$page.path.includes("/builder/invite") &&
|
||||
loaded &&
|
||||
hasAdminUser &&
|
||||
!$auth.user
|
||||
) {
|
||||
$goto("./auth/login")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<script>
|
||||
import {
|
||||
Layout,
|
||||
Heading,
|
||||
Body,
|
||||
Input,
|
||||
Button,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { goto, params } from "@roxi/routify"
|
||||
import { createValidationStore, requiredValidator } from "helpers/validation"
|
||||
import { users } from "stores/portal"
|
||||
|
||||
const [password, passwordError, passwordTouched] = createValidationStore(
|
||||
"",
|
||||
requiredValidator
|
||||
)
|
||||
const [repeat, _, repeatTouched] = createValidationStore(
|
||||
"",
|
||||
requiredValidator
|
||||
)
|
||||
const inviteCode = $params["?code"]
|
||||
|
||||
async function acceptInvite() {
|
||||
try {
|
||||
const res = await users.acceptInvite(inviteCode, $password)
|
||||
if (!res) {
|
||||
throw new Error(res.message)
|
||||
}
|
||||
notifications.success(`User created.`)
|
||||
$goto("../auth/login")
|
||||
} catch (err) {
|
||||
notifications.error(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<Layout gap="XS">
|
||||
<img src="https://i.imgur.com/ZKyklgF.png" />
|
||||
</Layout>
|
||||
<div class="center">
|
||||
<Layout gap="XS">
|
||||
<Heading size="M">Accept Invitation</Heading>
|
||||
<Body size="M">Please enter a password to setup your user.</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
<Layout gap="XS">
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
error={$passwordTouched && $passwordError}
|
||||
bind:value={$password}
|
||||
/>
|
||||
<Input
|
||||
label="Repeat Password"
|
||||
type="password"
|
||||
error={$repeatTouched &&
|
||||
$password !== $repeat &&
|
||||
"Passwords must match"}
|
||||
bind:value={$repeat}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout gap="S">
|
||||
<Button
|
||||
disabled={!$passwordTouched || !$repeatTouched || $password !== $repeat}
|
||||
cta
|
||||
on:click={acceptInvite}>Accept invite</Button
|
||||
>
|
||||
</Layout>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
width: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
width: 40px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { isActive, goto } from "@roxi/routify"
|
||||
import { onMount } from "svelte"
|
||||
import {
|
||||
Icon,
|
||||
Avatar,
|
||||
|
@ -13,34 +12,21 @@
|
|||
Modal,
|
||||
} from "@budibase/bbui"
|
||||
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
|
||||
import { organisation, apps } from "stores/portal"
|
||||
import { organisation } from "stores/portal"
|
||||
import { auth } from "stores/backend"
|
||||
import BuilderSettingsModal from "components/start/BuilderSettingsModal.svelte"
|
||||
|
||||
let orgName
|
||||
let orgLogo
|
||||
let user
|
||||
let oldSettingsModal
|
||||
|
||||
async function getInfo() {
|
||||
// fetch orgInfo
|
||||
orgName = "ACME Inc."
|
||||
orgLogo = "https://via.placeholder.com/150"
|
||||
user = { name: "John Doe" }
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
organisation.init()
|
||||
getInfo()
|
||||
})
|
||||
organisation.init()
|
||||
|
||||
let menu = [
|
||||
{ title: "Apps", href: "/builder/portal/apps" },
|
||||
{ title: "Drafts", href: "/builder/portal/drafts" },
|
||||
{ title: "Users", href: "/builder/portal/users", heading: "Manage" },
|
||||
{ title: "Groups", href: "/builder/portal/groups" },
|
||||
{ title: "Auth", href: "/builder/portal/oauth" },
|
||||
{ title: "Email", href: "/builder/portal/email" },
|
||||
{ title: "Users", href: "/builder/portal/manage/users", heading: "Manage" },
|
||||
{ title: "Groups", href: "/builder/portal/manage/groups" },
|
||||
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
||||
{ title: "Email", href: "/builder/portal/manage/email" },
|
||||
{
|
||||
title: "General",
|
||||
href: "/builder/portal/settings/general",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import { Page } from "@budibase/bbui"
|
||||
</script>
|
||||
|
||||
<Page>
|
||||
<slot />
|
||||
</Page>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,114 @@
|
|||
<script>
|
||||
import GoogleLogo from "./_logos/Google.svelte"
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
Divider,
|
||||
Page,
|
||||
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>
|
||||
|
||||
<Page>
|
||||
<Layout noPadding>
|
||||
<div>
|
||||
<Heading size="M">OAuth</Heading>
|
||||
<Body>
|
||||
Every budibase app comes with basic authentication (email/password)
|
||||
included. You can add additional authentication methods from the options
|
||||
below.
|
||||
</Body>
|
||||
</div>
|
||||
<Divider />
|
||||
{#if google}
|
||||
<div>
|
||||
<Heading size="S">
|
||||
<span>
|
||||
<GoogleLogo />
|
||||
Google
|
||||
</span>
|
||||
</Heading>
|
||||
<Body>
|
||||
To allow users to authenticate using their Google accounts, fill out
|
||||
the fields below.
|
||||
</Body>
|
||||
</div>
|
||||
|
||||
{#each ConfigFields.Google as field}
|
||||
<div class="form-row">
|
||||
<Label size="L">{field}</Label>
|
||||
<Input bind:value={google.config[field]} />
|
||||
</div>
|
||||
{/each}
|
||||
<div>
|
||||
<Button primary on:click={() => save(google)}>Save</Button>
|
||||
</div>
|
||||
<Divider />
|
||||
{/if}
|
||||
</Layout>
|
||||
</Page>
|
||||
|
||||
<style>
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 20% 1fr;
|
||||
grid-gap: var(--spacing-l);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
</style>
|
|
@ -1,32 +1,19 @@
|
|||
<script>
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
Button,
|
||||
Detail,
|
||||
Heading,
|
||||
Divider,
|
||||
Label,
|
||||
Modal,
|
||||
ModalContent,
|
||||
notifications,
|
||||
Layout,
|
||||
Icon,
|
||||
Body,
|
||||
Page,
|
||||
Select,
|
||||
Tabs,
|
||||
Tab,
|
||||
MenuSection,
|
||||
MenuSeparator,
|
||||
} from "@budibase/bbui"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { onMount } from "svelte"
|
||||
import { fade } from "svelte/transition"
|
||||
import { email } from "stores/portal"
|
||||
import Editor from "components/integration/QueryEditor.svelte"
|
||||
import TemplateBindings from "./TemplateBindings.svelte"
|
||||
import api from "builderStore/api"
|
||||
import TemplateBindings from "./_components/TemplateBindings.svelte"
|
||||
|
||||
const ConfigTypes = {
|
||||
SMTP: "smtp",
|
|
@ -23,8 +23,8 @@
|
|||
import { onMount } from "svelte"
|
||||
import { email } from "stores/portal"
|
||||
import Editor from "components/integration/QueryEditor.svelte"
|
||||
import TemplateBindings from "./TemplateBindings.svelte"
|
||||
import TemplateLink from "./TemplateLink.svelte"
|
||||
import TemplateBindings from "./_components/TemplateBindings.svelte"
|
||||
import TemplateLink from "./_components/TemplateLink.svelte"
|
||||
import api from "builderStore/api"
|
||||
|
||||
const ConfigTypes = {
|
|
@ -0,0 +1,43 @@
|
|||
<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 |
|
@ -0,0 +1,115 @@
|
|||
<script>
|
||||
import GoogleLogo from "./_logos/Google.svelte"
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
Divider,
|
||||
Label,
|
||||
notifications,
|
||||
Layout,
|
||||
Input,
|
||||
Body,
|
||||
Page,
|
||||
} 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>
|
||||
|
||||
<Page>
|
||||
<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}
|
||||
</Page>
|
||||
|
||||
<style>
|
||||
.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>
|
|
@ -0,0 +1,168 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import {
|
||||
ActionButton,
|
||||
Button,
|
||||
Layout,
|
||||
Heading,
|
||||
Body,
|
||||
Divider,
|
||||
Label,
|
||||
Input,
|
||||
Modal,
|
||||
Table,
|
||||
ModalContent,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { fetchData } from "helpers"
|
||||
import { users } from "stores/portal"
|
||||
|
||||
import TagsRenderer from "./_components/TagsTableRenderer.svelte"
|
||||
import UpdateRolesModal from "./_components/UpdateRolesModal.svelte"
|
||||
|
||||
export let userId
|
||||
let deleteUserModal
|
||||
let editRolesModal
|
||||
|
||||
const roleSchema = {
|
||||
name: { displayName: "App" },
|
||||
role: {},
|
||||
}
|
||||
|
||||
// Merge the Apps list and the roles response to get something that makes sense for the table
|
||||
$: appList = Object.keys($apps?.data).map(id => ({
|
||||
...$apps?.data?.[id],
|
||||
_id: id,
|
||||
role: [$roleFetch?.data?.roles?.[id]],
|
||||
}))
|
||||
let selectedApp
|
||||
|
||||
const roleFetch = fetchData(`/api/admin/users/${userId}`)
|
||||
const apps = fetchData(`/api/admin/roles`)
|
||||
|
||||
async function deleteUser() {
|
||||
const res = await users.del(userId)
|
||||
if (res.message) {
|
||||
notifications.success(`User ${$roleFetch?.data?.email} deleted.`)
|
||||
$goto("./")
|
||||
} else {
|
||||
notifications.error("Failed to delete user.")
|
||||
}
|
||||
}
|
||||
|
||||
async function openUpdateRolesModal({ detail }) {
|
||||
console.log(detail)
|
||||
selectedApp = detail
|
||||
editRolesModal.show()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Layout noPadding gap="XS">
|
||||
<div class="back">
|
||||
<ActionButton on:click={() => $goto("./")} quiet size="S" icon="BackAndroid"
|
||||
>Back to users</ActionButton
|
||||
>
|
||||
</div>
|
||||
<div class="heading">
|
||||
<Layout noPadding gap="XS">
|
||||
<Heading>User: {$roleFetch?.data?.email}</Heading>
|
||||
<Body
|
||||
>Lorem ipsum dolor sit amet consectetur adipisicing elit. Debitis porro
|
||||
ut nesciunt ipsam perspiciatis aliquam et hic minus alias beatae. Odit
|
||||
veritatis quos quas laborum magnam tenetur perspiciatis ex hic.
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
<div class="general">
|
||||
<Heading size="S">General</Heading>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<Label size="L">Email</Label>
|
||||
<Input disabled thin value={$roleFetch?.data?.email} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="regenerate">
|
||||
<ActionButton size="S" icon="Refresh" quiet
|
||||
>Regenerate password</ActionButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
<div class="roles">
|
||||
<Heading size="S">Configure roles</Heading>
|
||||
<Table
|
||||
on:click={openUpdateRolesModal}
|
||||
schema={roleSchema}
|
||||
data={appList}
|
||||
allowEditColumns={false}
|
||||
allowEditRows={false}
|
||||
allowSelectRows={false}
|
||||
customRenderers={[{ column: "role", component: TagsRenderer }]}
|
||||
/>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
<div class="delete">
|
||||
<Layout gap="S" noPadding
|
||||
><Heading size="S">Delete user</Heading>
|
||||
<Body>Deleting a user completely removes them from your account.</Body>
|
||||
<div class="delete-button">
|
||||
<Button warning on:click={deleteUserModal.show}>Delete user</Button>
|
||||
</div></Layout
|
||||
>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<Modal bind:this={deleteUserModal}>
|
||||
<ModalContent
|
||||
warning
|
||||
onConfirm={deleteUser}
|
||||
title="Delete User"
|
||||
confirmText="Delete user"
|
||||
cancelText="Cancel"
|
||||
showCloseIcon={false}
|
||||
>
|
||||
<Body
|
||||
>Are you sure you want to delete <strong>{$roleFetch?.data?.email}</strong
|
||||
></Body
|
||||
>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal bind:this={editRolesModal}>
|
||||
<UpdateRolesModal
|
||||
app={selectedApp}
|
||||
user={$roleFetch.data}
|
||||
on:update={roleFetch.refresh}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.fields {
|
||||
display: grid;
|
||||
grid-gap: var(--spacing-m);
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
.field {
|
||||
display: grid;
|
||||
grid-template-columns: 32% 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
.heading {
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
.general {
|
||||
position: relative;
|
||||
margin: var(--spacing-xl) 0;
|
||||
}
|
||||
.roles {
|
||||
margin: var(--spacing-xl) 0;
|
||||
}
|
||||
.delete {
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
.regenerate {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,59 @@
|
|||
<script>
|
||||
import {
|
||||
Body,
|
||||
Input,
|
||||
Select,
|
||||
ModalContent,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { createValidationStore, emailValidator } from "helpers/validation"
|
||||
import { users } from "stores/portal"
|
||||
|
||||
export let disabled
|
||||
|
||||
const options = ["Email onboarding", "Basic onboarding"]
|
||||
let selected = options[0]
|
||||
|
||||
const [email, error, touched] = createValidationStore("", emailValidator)
|
||||
|
||||
async function createUserFlow() {
|
||||
const res = await users.invite($email)
|
||||
console.log(res)
|
||||
if (res.status) {
|
||||
notifications.error(res.message)
|
||||
} else {
|
||||
notifications.success(res.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
onConfirm={createUserFlow}
|
||||
size="M"
|
||||
title="Add new user options"
|
||||
confirmText="Add user"
|
||||
confirmDisabled={disabled}
|
||||
cancelText="Cancel"
|
||||
disabled={$error}
|
||||
showCloseIcon={false}
|
||||
>
|
||||
<Body noPadding
|
||||
>If you have SMTP configured and an email for the new user, you can use the
|
||||
automated email onboarding flow. Otherwise, use our basic onboarding process
|
||||
with autogenerated passwords.</Body
|
||||
>
|
||||
<Select
|
||||
placeholder={null}
|
||||
bind:value={selected}
|
||||
on:change
|
||||
{options}
|
||||
label="Add new user via:"
|
||||
/>
|
||||
<Input
|
||||
type="email"
|
||||
bind:value={$email}
|
||||
error={$touched && $error}
|
||||
placeholder="john@doe.com"
|
||||
label="Email"
|
||||
/>
|
||||
</ModalContent>
|
|
@ -0,0 +1,40 @@
|
|||
<script>
|
||||
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
||||
import { createValidationStore, emailValidator } from "helpers/validation"
|
||||
import { users } from "stores/portal"
|
||||
|
||||
const [email, error, touched] = createValidationStore("", emailValidator)
|
||||
const password = Math.random().toString(36).substr(2, 20)
|
||||
|
||||
async function createUser() {
|
||||
const res = await users.create({ email: $email, password })
|
||||
if (res.status) {
|
||||
notifications.error(res.message)
|
||||
} else {
|
||||
notifications.success("Succesfully created user")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
onConfirm={createUser}
|
||||
size="M"
|
||||
title="Basic user onboarding"
|
||||
confirmText="Continue"
|
||||
cancelText="Cancel"
|
||||
disabled={$error}
|
||||
error={$touched && $error}
|
||||
showCloseIcon={false}
|
||||
>
|
||||
<Body noPadding
|
||||
>Below you will find the user’s username and password. The password will not
|
||||
be accessible from this point. Please download the credentials.</Body
|
||||
>
|
||||
<Input
|
||||
type="email"
|
||||
label="Username"
|
||||
bind:value={$email}
|
||||
error={$touched && $error}
|
||||
/>
|
||||
<Input disabled label="Password" value={password} />
|
||||
</ModalContent>
|
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
import { Tag, Tags } from "@budibase/bbui"
|
||||
export let value
|
||||
|
||||
const displayLimit = 5
|
||||
|
||||
$: tags = value?.slice(0, displayLimit) ?? []
|
||||
$: leftover = (value?.length ?? 0) - tags.length
|
||||
</script>
|
||||
|
||||
<Tags>
|
||||
{#each tags as tag}
|
||||
<Tag>
|
||||
{tag}
|
||||
</Tag>
|
||||
{/each}
|
||||
{#if leftover}
|
||||
<Tag>+{leftover} more</Tag>
|
||||
{/if}
|
||||
</Tags>
|
|
@ -0,0 +1,51 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { Body, Select, ModalContent, notifications } from "@budibase/bbui"
|
||||
import { fetchData } from "helpers"
|
||||
import { users } from "stores/portal"
|
||||
|
||||
export let app
|
||||
export let user
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const roles = app.roles
|
||||
let options = roles.map(role => role._id)
|
||||
let selectedRole
|
||||
|
||||
async function updateUserRoles() {
|
||||
const res = await users.updateRoles({
|
||||
...user,
|
||||
roles: {
|
||||
...user.roles,
|
||||
[app._id]: selectedRole,
|
||||
},
|
||||
})
|
||||
if (res.status === 400) {
|
||||
notifications.error("Failed to update role")
|
||||
} else {
|
||||
notifications.success("Roles updated")
|
||||
dispatch("update")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
onConfirm={updateUserRoles}
|
||||
title="Update App Roles"
|
||||
confirmText="Update roles"
|
||||
cancelText="Cancel"
|
||||
size="M"
|
||||
showCloseIcon={false}
|
||||
>
|
||||
<Body noPadding
|
||||
>Update {user.email}'s roles for <strong>{app.name}</strong>.</Body
|
||||
>
|
||||
<Select
|
||||
placeholder={null}
|
||||
bind:value={selectedRole}
|
||||
on:change
|
||||
{options}
|
||||
label="Select roles:"
|
||||
/>
|
||||
</ModalContent>
|
|
@ -0,0 +1,105 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import {
|
||||
Heading,
|
||||
Body,
|
||||
Divider,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Search,
|
||||
Table,
|
||||
Label,
|
||||
Layout,
|
||||
Modal,
|
||||
} from "@budibase/bbui"
|
||||
import TagsRenderer from "./_components/TagsTableRenderer.svelte"
|
||||
import AddUserModal from "./_components/AddUserModal.svelte"
|
||||
import BasicOnboardingModal from "./_components/BasicOnboardingModal.svelte"
|
||||
import { users } from "stores/portal"
|
||||
|
||||
users.init()
|
||||
|
||||
const schema = {
|
||||
email: {},
|
||||
status: { displayName: "Development Access", type: "boolean" },
|
||||
// role: { type: "options" },
|
||||
group: {},
|
||||
// access: {},
|
||||
// group: {}
|
||||
}
|
||||
|
||||
let search
|
||||
let email
|
||||
$: filteredUsers = $users
|
||||
.filter(user => user.email.includes(search || ""))
|
||||
.map(user => ({ ...user, group: ["All"] }))
|
||||
|
||||
let createUserModal
|
||||
let basicOnboardingModal
|
||||
|
||||
function openBasicOnoboardingModal() {
|
||||
createUserModal.hide()
|
||||
basicOnboardingModal.show()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Layout>
|
||||
<div class="heading">
|
||||
<Heading>Users</Heading>
|
||||
<Body
|
||||
>Users are the common denominator in Budibase. Each user is assigned to a
|
||||
group that contains apps and permissions. In this section, you can add
|
||||
users, or edit and delete an existing user.</Body
|
||||
>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
|
||||
<div class="users">
|
||||
<Heading size="S">Users</Heading>
|
||||
<div class="field">
|
||||
<Label size="L">Search / filter</Label>
|
||||
<Search bind:value={search} placeholder="" />
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<ButtonGroup>
|
||||
<Button disabled secondary>Import users</Button>
|
||||
<Button overBackground on:click={createUserModal.show}>Add user</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<Table
|
||||
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
||||
{schema}
|
||||
data={filteredUsers || $users}
|
||||
allowEditColumns={false}
|
||||
allowEditRows={false}
|
||||
allowSelectRows={false}
|
||||
customRenderers={[{ column: "group", component: TagsRenderer }]}
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<Modal bind:this={createUserModal}
|
||||
><AddUserModal on:change={openBasicOnoboardingModal} /></Modal
|
||||
>
|
||||
<Modal bind:this={basicOnboardingModal}><BasicOnboardingModal {email} /></Modal>
|
||||
|
||||
<style>
|
||||
.users {
|
||||
position: relative;
|
||||
}
|
||||
.field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
grid-gap: var(--spacing-m);
|
||||
margin: var(--spacing-xl) 0;
|
||||
}
|
||||
.field > :global(*) + :global(*) {
|
||||
margin-left: var(--spacing-m);
|
||||
}
|
||||
.buttons {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<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 |
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import GoogleLogo from "./logos/Google.svelte"
|
||||
import GoogleLogo from "./_logos/Google.svelte"
|
||||
import {
|
||||
Button,
|
||||
Page,
|
||||
Heading,
|
||||
Divider,
|
||||
Label,
|
||||
|
@ -9,7 +10,6 @@
|
|||
Layout,
|
||||
Input,
|
||||
Body,
|
||||
Page,
|
||||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import api from "builderStore/api"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { organisation } from "stores/portal"
|
||||
import { post } from "builderStore/api"
|
||||
import analytics from "analytics"
|
||||
let analyticsDisabled = analytics.disabled()
|
||||
|
||||
|
@ -24,18 +25,30 @@
|
|||
}
|
||||
|
||||
let loading = false
|
||||
let file
|
||||
|
||||
$: company = $organisation?.company
|
||||
$: logoUrl = $organisation.logoUrl
|
||||
async function uploadLogo() {
|
||||
let data = new FormData()
|
||||
data.append("file", file)
|
||||
|
||||
const res = await post("/api/admin/configs/upload/settings/logo", data, {})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
loading = true
|
||||
await toggleAnalytics()
|
||||
const res = await organisation.save({ ...$organisation, company })
|
||||
if (file) {
|
||||
await uploadLogo()
|
||||
}
|
||||
const res = await organisation.save({
|
||||
company: $organisation.company,
|
||||
platformUrl: $organisation.platformUrl,
|
||||
})
|
||||
if (res.status === 200) {
|
||||
notifications.success("General settings saved.")
|
||||
notifications.success("Settings saved.")
|
||||
} else {
|
||||
notifications.danger("Error when saving settings.")
|
||||
notifications.error(res.message)
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
@ -46,10 +59,9 @@
|
|||
<div class="intro">
|
||||
<Heading size="M">General</Heading>
|
||||
<Body>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Hic vero, aut
|
||||
culpa provident sunt ratione! Voluptas doloremque, dicta nisi velit
|
||||
perspiciatis, ratione vel blanditiis totam, nam voluptate repellat
|
||||
aperiam fuga!
|
||||
General is the place where you edit your organisation name, logo. You
|
||||
can also configure your platform URL as well as turn on or off
|
||||
analytics.
|
||||
</Body>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
|
@ -59,14 +71,30 @@
|
|||
<div class="fields">
|
||||
<div class="field">
|
||||
<Label size="L">Organization name</Label>
|
||||
<Input thin bind:value={company} />
|
||||
<Input thin bind:value={$organisation.company} />
|
||||
</div>
|
||||
<!-- <div class="field">
|
||||
<Label>Logo</Label>
|
||||
<div class="field logo">
|
||||
<Label size="L">Logo</Label>
|
||||
<div class="file">
|
||||
<Dropzone />
|
||||
<Dropzone
|
||||
value={[file]}
|
||||
on:change={e => {
|
||||
file = e.detail?.[0]
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
<div class="analytics">
|
||||
<Heading size="S">Platform</Heading>
|
||||
<Body>Here you can set up general platform settings.</Body>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<Label size="L">Platform URL</Label>
|
||||
<Input thin bind:value={$organisation.platformUrl} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
|
@ -103,6 +131,9 @@
|
|||
.file {
|
||||
max-width: 30ch;
|
||||
}
|
||||
.logo {
|
||||
align-items: start;
|
||||
}
|
||||
.intro {
|
||||
display: grid;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export { organisation } from "./organisation"
|
||||
export { users } from "./users"
|
||||
export { admin } from "./admin"
|
||||
export { apps } from "./apps"
|
||||
export { email } from "./email"
|
||||
|
|
|
@ -1,35 +1,46 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { writable, get } from "svelte/store"
|
||||
import api from "builderStore/api"
|
||||
|
||||
const FALLBACK_CONFIG = {
|
||||
platformUrl: "",
|
||||
logoUrl: "",
|
||||
docsUrl: "",
|
||||
company: "http://localhost:10000",
|
||||
}
|
||||
|
||||
export function createOrganisationStore() {
|
||||
const { subscribe, set } = writable({})
|
||||
const store = writable({})
|
||||
const { subscribe, set } = store
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const response = await api.get(`/api/admin/configs/settings`)
|
||||
const json = await response.json()
|
||||
set(json)
|
||||
} catch (error) {
|
||||
set({
|
||||
platformUrl: "",
|
||||
logoUrl: "",
|
||||
docsUrl: "",
|
||||
company: "",
|
||||
})
|
||||
const res = await api.get(`/api/admin/configs/settings`)
|
||||
const json = await res.json()
|
||||
|
||||
if (json.status === 400) {
|
||||
set(FALLBACK_CONFIG)
|
||||
} else {
|
||||
set({ ...json.config, _rev: json._rev })
|
||||
}
|
||||
}
|
||||
|
||||
async function save(config) {
|
||||
const res = await api.post("/api/admin/configs", {
|
||||
type: "settings",
|
||||
config,
|
||||
_rev: get(store)._rev,
|
||||
})
|
||||
const json = await res.json()
|
||||
if (json.status) {
|
||||
return json
|
||||
}
|
||||
await init()
|
||||
return { status: 200 }
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
save: async config => {
|
||||
try {
|
||||
await api.post("/api/admin/configs", { type: "settings", config })
|
||||
await init()
|
||||
return { status: 200 }
|
||||
} catch (error) {
|
||||
return { error }
|
||||
}
|
||||
},
|
||||
set,
|
||||
save,
|
||||
init,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import { writable } from "svelte/store"
|
||||
import api, { post } from "builderStore/api"
|
||||
import { update } from "lodash"
|
||||
|
||||
export function createUsersStore() {
|
||||
const { subscribe, set } = writable([])
|
||||
|
||||
async function init() {
|
||||
const response = await api.get(`/api/admin/users`)
|
||||
const json = await response.json()
|
||||
set(json)
|
||||
}
|
||||
|
||||
async function invite(email) {
|
||||
const response = await api.post(`/api/admin/users/invite`, { email })
|
||||
return await response.json()
|
||||
}
|
||||
async function acceptInvite(inviteCode, password) {
|
||||
const response = await api.post("/api/admin/users/invite/accept", {
|
||||
inviteCode,
|
||||
password,
|
||||
})
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async function create({ email, password }) {
|
||||
const response = await api.post("/api/admin/users", {
|
||||
email,
|
||||
password,
|
||||
builder: { global: true },
|
||||
roles: {},
|
||||
})
|
||||
init()
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async function del(id) {
|
||||
const response = await api.delete(`/api/admin/users/${id}`)
|
||||
update(users => users.filter(user => user._id !== id))
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async function updateRoles(data) {
|
||||
try {
|
||||
const res = await post(`/api/admin/users`, data)
|
||||
const json = await res.json()
|
||||
return json
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
init,
|
||||
invite,
|
||||
acceptInvite,
|
||||
create,
|
||||
updateRoles,
|
||||
del,
|
||||
}
|
||||
}
|
||||
|
||||
export const users = createUsersStore()
|
|
@ -1,5 +1,4 @@
|
|||
const { SearchIndexes } = require("../../../db/utils")
|
||||
const { checkSlashesInUrl } = require("../../../utilities")
|
||||
const env = require("../../../environment")
|
||||
const fetch = require("node-fetch")
|
||||
|
||||
|
@ -10,7 +9,7 @@ const fetch = require("node-fetch")
|
|||
* @returns {string}
|
||||
*/
|
||||
const luceneEscape = value => {
|
||||
return `${value}`.replace(/[ #+\-&|!(){}\[\]^"~*?:\\]/g, "\\$&")
|
||||
return `${value}`.replace(/[ #+\-&|!(){}\]^"~*?:\\]/g, "\\$&")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -118,16 +118,17 @@ exports.upload = async function (ctx) {
|
|||
// add to configuration structure
|
||||
// TODO: right now this only does a global level
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
let config = await getScopedFullConfig(db, { type })
|
||||
if (!config) {
|
||||
config = {
|
||||
let cfgStructure = await getScopedFullConfig(db, { type })
|
||||
if (!cfgStructure) {
|
||||
cfgStructure = {
|
||||
_id: generateConfigID({ type }),
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
const url = `/${bucket}/${key}`
|
||||
config[`${name}Url`] = url
|
||||
cfgStructure.config[`${name}Url`] = url
|
||||
// write back to db with url updated
|
||||
await db.put(config)
|
||||
await db.put(cfgStructure)
|
||||
|
||||
ctx.body = {
|
||||
message: "File has been uploaded and url stored to config.",
|
||||
|
|
|
@ -25,9 +25,9 @@ function smtpValidation() {
|
|||
function settingValidation() {
|
||||
// prettier-ignore
|
||||
return Joi.object({
|
||||
platformUrl: Joi.string().valid("", null),
|
||||
logoUrl: Joi.string().valid("", null),
|
||||
docsUrl: Joi.string().valid("", null),
|
||||
platformUrl: Joi.string().optional(),
|
||||
logoUrl: Joi.string().optional(),
|
||||
docsUrl: Joi.string().optional(),
|
||||
company: Joi.string().required(),
|
||||
}).unknown(true)
|
||||
}
|
||||
|
@ -44,9 +44,9 @@ function googleValidation() {
|
|||
function buildConfigSaveValidation() {
|
||||
// prettier-ignore
|
||||
return joiValidator.body(Joi.object({
|
||||
_id: Joi.string(),
|
||||
_rev: Joi.string(),
|
||||
group: Joi.string(),
|
||||
_id: Joi.string().optional(),
|
||||
_rev: Joi.string().optional(),
|
||||
group: Joi.string().optional(),
|
||||
type: Joi.string().valid(...Object.values(Configs)).required(),
|
||||
config: Joi.alternatives()
|
||||
.conditional("type", {
|
||||
|
|
|
@ -7,9 +7,8 @@ const {
|
|||
EmailTemplatePurpose,
|
||||
} = require("../constants")
|
||||
const { checkSlashesInUrl } = require("./index")
|
||||
const env = require("../environment")
|
||||
|
||||
const LOCAL_URL = `http://localhost:${env.PORT}`
|
||||
const LOCAL_URL = `http://localhost:10000`
|
||||
const BASE_COMPANY = "Budibase"
|
||||
|
||||
exports.getSettingsTemplateContext = async (purpose, code = null) => {
|
||||
|
@ -42,7 +41,7 @@ exports.getSettingsTemplateContext = async (purpose, code = null) => {
|
|||
case EmailTemplatePurpose.INVITATION:
|
||||
context[InternalTemplateBindings.INVITE_CODE] = code
|
||||
context[InternalTemplateBindings.INVITE_URL] = checkSlashesInUrl(
|
||||
`${URL}/invite?code=${code}`
|
||||
`${URL}/builder/invite?code=${code}`
|
||||
)
|
||||
break
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue