SMTP and template management E2E
This commit is contained in:
parent
52d87c8267
commit
7588030780
|
@ -12,55 +12,47 @@
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $auth}
|
<div class="root">
|
||||||
{#if $auth.user}
|
<div class="ui-nav">
|
||||||
<div class="root">
|
<div class="home-logo">
|
||||||
<div class="ui-nav">
|
<img src={Logo} alt="Budibase icon" />
|
||||||
<div class="home-logo">
|
</div>
|
||||||
<img src={Logo} alt="Budibase icon" />
|
<div class="nav-section">
|
||||||
</div>
|
<div class="nav-top">
|
||||||
<div class="nav-section">
|
<Navigation>
|
||||||
<div class="nav-top">
|
<Item href="/builder/" icon="Apps" selected>Apps</Item>
|
||||||
<Navigation>
|
<Item external href="https://portal.budi.live/" icon="Servers">
|
||||||
<Item href="/builder/" icon="Apps" selected>Apps</Item>
|
Hosting
|
||||||
<Item external href="https://portal.budi.live/" icon="Servers">
|
</Item>
|
||||||
Hosting
|
<Item external href="https://docs.budibase.com/" icon="Book">
|
||||||
</Item>
|
Documentation
|
||||||
<Item external href="https://docs.budibase.com/" icon="Book">
|
</Item>
|
||||||
Documentation
|
<Item
|
||||||
</Item>
|
external
|
||||||
<Item
|
href="https://github.com/Budibase/budibase/discussions"
|
||||||
external
|
icon="PeopleGroup"
|
||||||
href="https://github.com/Budibase/budibase/discussions"
|
>
|
||||||
icon="PeopleGroup"
|
Community
|
||||||
>
|
</Item>
|
||||||
Community
|
<Item
|
||||||
</Item>
|
external
|
||||||
<Item
|
href="https://github.com/Budibase/budibase/issues/new/choose"
|
||||||
external
|
icon="Bug"
|
||||||
href="https://github.com/Budibase/budibase/issues/new/choose"
|
>
|
||||||
icon="Bug"
|
Raise an issue
|
||||||
>
|
</Item>
|
||||||
Raise an issue
|
</Navigation>
|
||||||
</Item>
|
|
||||||
</Navigation>
|
|
||||||
</div>
|
|
||||||
<div class="nav-bottom">
|
|
||||||
<BuilderSettingsButton />
|
|
||||||
<LogoutButton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="nav-bottom">
|
||||||
<slot />
|
<BuilderSettingsButton />
|
||||||
|
<LogoutButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
</div>
|
||||||
<section class="login">
|
<div class="main">
|
||||||
<LoginForm />
|
<slot />
|
||||||
</section>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
|
|
|
@ -14,8 +14,10 @@
|
||||||
SideNavigation as Navigation,
|
SideNavigation as Navigation,
|
||||||
SideNavigationItem as Item,
|
SideNavigationItem as Item,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
import LoginForm from "components/login/LoginForm.svelte"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
|
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
|
||||||
|
import { auth } from "stores/backend"
|
||||||
import { organisation, admin } from "stores/portal"
|
import { organisation, admin } from "stores/portal"
|
||||||
|
|
||||||
organisation.init()
|
organisation.init()
|
||||||
|
@ -47,43 +49,52 @@
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
{#if $auth}
|
||||||
<div class="nav">
|
{#if $auth.user}
|
||||||
<Layout paddingX="L" paddingY="L">
|
<div class="container">
|
||||||
<div class="branding">
|
<div class="nav">
|
||||||
<div class="name">
|
<Layout paddingX="L" paddingY="L">
|
||||||
<img
|
<div class="branding">
|
||||||
src={$organisation?.logoUrl || "https://i.imgur.com/ZKyklgF.png"}
|
<div class="name">
|
||||||
alt="Logotype"
|
<img
|
||||||
/>
|
src={$organisation?.logoUrl ||
|
||||||
<span>{$organisation?.company || "Budibase"}</span>
|
"https://i.imgur.com/ZKyklgF.png"}
|
||||||
|
alt="Logotype"
|
||||||
|
/>
|
||||||
|
<span>{$organisation?.company || "Budibase"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="onboarding">
|
||||||
|
<ConfigChecklist />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="menu">
|
||||||
|
<Navigation>
|
||||||
|
{#each menu as { title, href, heading }}
|
||||||
|
<Item selected={$isActive(href)} {href} {heading}>{title}</Item>
|
||||||
|
{/each}
|
||||||
|
</Navigation>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
<div class="main">
|
||||||
|
<div class="toolbar">
|
||||||
|
<Search placeholder="Global search" />
|
||||||
|
<div class="avatar">
|
||||||
|
<Avatar size="M" name="John Doe" />
|
||||||
|
<Icon size="XL" name="ChevronDown" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="onboarding">
|
<div class="content">
|
||||||
<ConfigChecklist />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu">
|
|
||||||
<Navigation>
|
|
||||||
{#each menu as { title, href, heading }}
|
|
||||||
<Item selected={$isActive(href)} {href} {heading}>{title}</Item>
|
|
||||||
{/each}
|
|
||||||
</Navigation>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
<div class="main">
|
|
||||||
<div class="toolbar">
|
|
||||||
<Search placeholder="Global search" />
|
|
||||||
<div class="avatar">
|
|
||||||
<Avatar size="M" name="John Doe" />
|
|
||||||
<Icon size="XL" name="ChevronDown" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{:else}
|
||||||
<slot />
|
<section class="login">
|
||||||
</div>
|
<LoginForm />
|
||||||
</div>
|
</section>
|
||||||
</div>
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Heading,
|
||||||
|
Divider,
|
||||||
|
Label,
|
||||||
|
notifications,
|
||||||
|
Layout,
|
||||||
|
Input,
|
||||||
|
TextArea,
|
||||||
|
Body,
|
||||||
|
Page,
|
||||||
|
Select,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
const ConfigTypes = {
|
||||||
|
SMTP: "smtp",
|
||||||
|
}
|
||||||
|
|
||||||
|
let smtpConfig
|
||||||
|
let templateIdx = 0
|
||||||
|
let templateDefinition
|
||||||
|
let templates = []
|
||||||
|
|
||||||
|
$: templateTypes = templates.map((template, idx) => ({
|
||||||
|
label: template.purpose,
|
||||||
|
value: idx,
|
||||||
|
}))
|
||||||
|
|
||||||
|
$: selectedTemplate = templates[templateIdx]
|
||||||
|
|
||||||
|
async function saveSmtp() {
|
||||||
|
try {
|
||||||
|
// Save your SMTP config
|
||||||
|
const response = await api.post(`/api/admin/configs`, smtpConfig)
|
||||||
|
const json = await response.json()
|
||||||
|
if (response.status !== 200) throw new Error(json.message)
|
||||||
|
smtpConfig._rev = json._rev
|
||||||
|
smtpConfig._id = json._id
|
||||||
|
|
||||||
|
notifications.success(`Settings saved.`)
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Failed to save email settings. ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTemplate() {
|
||||||
|
try {
|
||||||
|
// Save your SMTP config
|
||||||
|
const response = await api.post(`/api/admin/template`, selectedTemplate)
|
||||||
|
const json = await response.json()
|
||||||
|
if (response.status !== 200) throw new Error(json.message)
|
||||||
|
selectedTemplate._rev = json._rev
|
||||||
|
selectedTemplate._id = json._id
|
||||||
|
|
||||||
|
notifications.success(`Template saved.`)
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Failed to update template settings. ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSmtp() {
|
||||||
|
// fetch the configs for smtp
|
||||||
|
const smtpResponse = await api.get(`/api/admin/configs/${ConfigTypes.SMTP}`)
|
||||||
|
const smtpDoc = await smtpResponse.json()
|
||||||
|
|
||||||
|
if (!smtpDoc._id) {
|
||||||
|
smtpConfig = {
|
||||||
|
type: ConfigTypes.SMTP,
|
||||||
|
config: {
|
||||||
|
auth: {
|
||||||
|
type: "login",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
smtpConfig = smtpDoc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTemplates() {
|
||||||
|
// fetch the email template definitions
|
||||||
|
const templatesResponse = await api.get(`/api/admin/template/definitions`)
|
||||||
|
const templateDefDoc = await templatesResponse.json()
|
||||||
|
|
||||||
|
// fetch the email templates themselves
|
||||||
|
const emailTemplatesResponse = await api.get(`/api/admin/template/email`)
|
||||||
|
const emailTemplates = await emailTemplatesResponse.json()
|
||||||
|
|
||||||
|
templateDefinition = templateDefDoc
|
||||||
|
templates = emailTemplates
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await fetchSmtp()
|
||||||
|
await fetchTemplates()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Page>
|
||||||
|
<header>
|
||||||
|
<Heading size="M">Email</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Sending email is not required, but highly recommended for processes such
|
||||||
|
as password recovery. To setup automated auth emails, simply add the
|
||||||
|
values below and click activate.
|
||||||
|
</Body>
|
||||||
|
</header>
|
||||||
|
<Divider />
|
||||||
|
{#if smtpConfig}
|
||||||
|
<div class="config-form">
|
||||||
|
<Heading size="S">SMTP</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
To allow your app to benefit from automated auth emails, add your SMTP
|
||||||
|
details below.
|
||||||
|
</Body>
|
||||||
|
<Layout gap="S">
|
||||||
|
<Heading size="S">
|
||||||
|
<span />
|
||||||
|
</Heading>
|
||||||
|
<div class="form-row">
|
||||||
|
<Label>Host</Label>
|
||||||
|
<Input bind:value={smtpConfig.config.host} />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<Label>Port</Label>
|
||||||
|
<Input type="number" bind:value={smtpConfig.config.port} />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<Label>User</Label>
|
||||||
|
<Input bind:value={smtpConfig.config.auth.user} />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<Label>Password</Label>
|
||||||
|
<Input type="password" bind:value={smtpConfig.config.auth.pass} />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<Label>From email address</Label>
|
||||||
|
<Input type="email" bind:value={smtpConfig.config.from} />
|
||||||
|
</div>
|
||||||
|
<Button cta on:click={saveSmtp}>Save</Button>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<div class="config-form">
|
||||||
|
<Heading size="S">Templates</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Budibase comes out of the box with ready-made email templates to help
|
||||||
|
with user onboarding. Please refrain from changing the links.
|
||||||
|
</Body>
|
||||||
|
<div class="template-controls">
|
||||||
|
<Select bind:value={templateIdx} options={templateTypes} />
|
||||||
|
<Button cta on:click={saveTemplate}>Save</Button>
|
||||||
|
</div>
|
||||||
|
{#if selectedTemplate}
|
||||||
|
<TextArea bind:value={selectedTemplate.contents} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 80% 1fr;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
const { generateTemplateID, StaticDatabases } = require("@budibase/auth").db
|
const { generateTemplateID, StaticDatabases } = require("@budibase/auth").db
|
||||||
const { CouchDB } = require("../../../db")
|
const CouchDB = require("../../../db")
|
||||||
const {
|
const {
|
||||||
TemplateMetadata,
|
TemplateMetadata,
|
||||||
TemplateBindings,
|
TemplateBindings,
|
||||||
|
|
|
@ -17,7 +17,7 @@ function smtpValidation() {
|
||||||
auth: Joi.object({
|
auth: Joi.object({
|
||||||
type: Joi.string().valid("login", "oauth2", null),
|
type: Joi.string().valid("login", "oauth2", null),
|
||||||
user: Joi.string().required(),
|
user: Joi.string().required(),
|
||||||
pass: Joi.string().valid("", null),
|
pass: Joi.string().allow("", null),
|
||||||
}).optional(),
|
}).optional(),
|
||||||
}).unknown(true)
|
}).unknown(true)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue