mirror of https://github.com/Budibase/budibase.git
48 changed files with 2790 additions and 2209 deletions
@ -0,0 +1,198 @@ |
|||
<p align="center"> |
|||
<a href="https://www.budibase.com"> |
|||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" /> |
|||
</a> |
|||
</p> |
|||
<h1 align="center"> |
|||
Budibase |
|||
</h1> |
|||
|
|||
<h3 align="center"> |
|||
Construye herramientas empresariales personalizadas en cuestión de minutos y en su propia infraestructura. |
|||
</h3> |
|||
<p align="center"> |
|||
Budibase es una plataforma de código bajo de código abierto, que ayuda a desarrolladores y profesionales de TI a crear, automatizar y enviar aplicaciones empresariales personalizadas en cuestión de minutos y en su propia infraestructura |
|||
</p> |
|||
|
|||
<h3 align="center"> |
|||
🤖 🎨 🚀 |
|||
</h3> |
|||
|
|||
|
|||
<p align="center"> |
|||
<img src="https://i.imgur.com/tPQHruf.png"> |
|||
</p> |
|||
|
|||
<p align="center"> |
|||
<a href="https://github.com/Budibase/budibase/releases"> |
|||
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/Budibase/budibase/total"> |
|||
</a> |
|||
<a href="https://github.com/Budibase/budibase/releases"> |
|||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/Budibase/budibase"> |
|||
</a> |
|||
<a href="https://discord.gg/rCYayfe"> |
|||
<img alt="Discord" src="https://img.shields.io/discord/733030666647765003"> |
|||
</a> |
|||
<a href="https://twitter.com/intent/follow?screen_name=budibase"> |
|||
<img src="https://img.shields.io/twitter/follow/budibase?style=social" alt="Follow @budibase" /> |
|||
</a> |
|||
<img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Code of conduct" /> |
|||
<a href="https://codecov.io/gh/Budibase/budibase"> |
|||
<img src="https://codecov.io/gh/Budibase/budibase/graph/badge.svg?token=E8W2ZFXQOH"/> |
|||
</a> |
|||
</p> |
|||
|
|||
<h3 align="center"> |
|||
<a href="https://portal.budi.live/signup">Sign-up</a> |
|||
<span> · </span> |
|||
<a href="https://docs.budibase.com">Docs</a> |
|||
<span> · </span> |
|||
<a href="https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas">Feature request</a> |
|||
<span> · </span> |
|||
<a href="https://github.com/Budibase/budibase/issues">Report a bug</a> |
|||
<span> · </span> |
|||
Support: <a href="https://github.com/Budibase/budibase/discussions">Discussions</a> |
|||
<span> & </span> |
|||
<a href="https://discord.gg/rCYayfe">Discord</a> |
|||
</h3> |
|||
|
|||
|
|||
## ✨ Features |
|||
When other platforms chose the closed source route, we decided to go open source. When other platforms chose cloud builders, we decided a local builder offered the better developer experience. We like to do things differently at Budibase. |
|||
|
|||
- **Build and ship real software.** Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing your users with a great experience. |
|||
|
|||
- **Open source and extensable.** Budibase is open-source. The builder is licensed AGPL v3, the server is GPL v3, and the client is MPL. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience. |
|||
|
|||
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, mySQL, Airtable, Google Sheets, S3, DyanmoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). |
|||
|
|||
- **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new components](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). |
|||
|
|||
- **Automate processes, integrate with other tools, and connect to webhooks.** Save time by automating manual processes and workflows. From connecting to webhooks, to automating emails, simply tell Budibase what to do and let it work for you. You can easily [create new automations for Budibase here](https://github.com/Budibase/automations) or [request new integrations here](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). |
|||
|
|||
- **Cloud hosting and self-hosting.** Users can self-host (see below), or host their apps with Budibase. Currently, our cloud hosting offering is limited to the free tier but we aim to change this in the future. For heavy usage, we advise users to self-host. |
|||
|
|||
<p align="center"> |
|||
<img alt="Budibase design ui" src="https://imgur.com/v8m6v3q.png"> |
|||
</p> |
|||
|
|||
|
|||
## ⌛ Status |
|||
- [x] Alpha: We are demoing Budibase to users and receiving feedback |
|||
- [x] Private Beta: We are testing Budibase with a closed set of customers |
|||
- [x] Public Beta: Anyone can [sign-up and use Budibase](https://portal.budi.live/signup). |
|||
- [ ] Official Launch |
|||
|
|||
Watch "releases" of this repo to get notified of major updates, and give the star button a click whilst you're there. |
|||
|
|||
<p align="center"> |
|||
<img src="https://i.imgur.com/cJpgqm8.png"> |
|||
</p> |
|||
|
|||
### Stargazers over time |
|||
|
|||
[](https://starchart.cc/Budibase/budibase) |
|||
|
|||
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) to clear down your environment. |
|||
|
|||
|
|||
## 🏁 Getting Started with Budibase |
|||
|
|||
The Budibase builder runs in Electron, on Mac, PC and Linux. Follow the steps below to get started: |
|||
- [ ] [Sign-up to Budibase](https://portal.budi.live/signup) |
|||
- [ ] Create a username and password |
|||
- [ ] Copy your API key |
|||
- [ ] Download Budibase |
|||
- [ ] Open Budibase and enter your API key |
|||
|
|||
[Here is a guided tutorial](https://docs.budibase.com/tutorial/tutorial-signing-up) if you need extra help. |
|||
|
|||
|
|||
## 🤖 Self-hosting |
|||
|
|||
Budibase wants to make sure anyone can use the tools we develop and we know a lot of people need to be able to host the apps they make on their own systems - that is why we've decided to try and make self hosting as easy as possible! |
|||
|
|||
Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting). |
|||
|
|||
[](https://cloud.digitalocean.com/droplets/new?onboarding_origin=marketplace&i=09038e&fleetUuid=bb04f9c8-1de8-4687-b2ae-1d5177a0535b&appId=77729671&type=applications&size=s-4vcpu-8gb®ion=nyc1&refcode=0caaa6085a82&image=budibase-20-04) |
|||
|
|||
|
|||
## 🎓 Learning Budibase |
|||
|
|||
The Budibase [documentation lives here](https://docs.budibase.com). |
|||
|
|||
You can also follow a quick tutorial on [how to build a CRM with Budibase](https://docs.budibase.com/tutorial/tutorial-introduction) |
|||
|
|||
|
|||
## Roadmap |
|||
|
|||
Checkout our [Public Roadmap](https://github.com/Budibase/budibase/projects/10). If you would like to discuss some of the items on the roadmap, please feel to reach out on [Discord](https://discord.gg/rCYayfe), or via [Github discussions](https://github.com/Budibase/budibase/discussions) |
|||
|
|||
|
|||
## ❗ Code of Conduct |
|||
|
|||
Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Please read it. |
|||
|
|||
## 🙌 Contributing to Budibase |
|||
|
|||
From opening a bug report to creating a pull request: every contribution is appreciated and welcomed. If you're planning to implement a new feature or change the API please create an issue first. This way we can ensure your work is not in vain. |
|||
|
|||
### Not Sure Where to Start? |
|||
A good place to start contributing, is the [First time issues project](https://github.com/Budibase/budibase/projects/22). |
|||
|
|||
### How the repository is organized |
|||
Budibase is a monorepo managed by lerna. Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up Budibase. |
|||
|
|||
- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contains code for the budibase builder client side svelte application. |
|||
|
|||
- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it. |
|||
|
|||
- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - The budibase server. This Koa app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system. |
|||
|
|||
For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md) |
|||
|
|||
## 📝 License |
|||
|
|||
Budibase is open-source. The builder is licensed [AGPL v3](https://www.gnu.org/licenses/agpl-3.0.en.html), the server is licensed [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html), and the client is licensed [MPL](https://directory.fsf.org/wiki/License:MPL-2.0). |
|||
|
|||
## 💬 Get in touch |
|||
|
|||
If you have a question or would like to talk with other Budibase users, please hop over to [Github discussions](https://github.com/Budibase/budibase/discussions) or join our Discord server: |
|||
|
|||
[Discord chatroom](https://discord.gg/rCYayfe) |
|||
|
|||
 |
|||
|
|||
|
|||
## Contributors ✨ |
|||
|
|||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): |
|||
|
|||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> |
|||
<!-- prettier-ignore-start --> |
|||
<!-- markdownlint-disable --> |
|||
<table> |
|||
<tr> |
|||
<td align="center"><a href="http://martinmck.com"><img src="https://avatars1.githubusercontent.com/u/11256663?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Martin McKeaveney</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Tests">⚠️</a> <a href="#infra-shogunpurple" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td> |
|||
<td align="center"><a href="http://www.michaeldrury.co.uk/"><img src="https://avatars2.githubusercontent.com/u/4407001?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Drury</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Tests">⚠️</a> <a href="#infra-mike12345567" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td> |
|||
<td align="center"><a href="https://github.com/aptkingston"><img src="https://avatars3.githubusercontent.com/u/9075550?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Kingston</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Tests">⚠️</a> <a href="#design-aptkingston" title="Design">🎨</a></td> |
|||
<td align="center"><a href="https://budibase.com/"><img src="https://avatars3.githubusercontent.com/u/3524181?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Shanks</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Tests">⚠️</a></td> |
|||
<td align="center"><a href="https://github.com/kevmodrome"><img src="https://avatars3.githubusercontent.com/u/534488?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Åberg Kultalahti</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Tests">⚠️</a></td> |
|||
<td align="center"><a href="https://www.budibase.com/"><img src="https://avatars2.githubusercontent.com/u/49767913?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joe</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=joebudi" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=joebudi" title="Code">💻</a> <a href="#content-joebudi" title="Content">🖋</a> <a href="#design-joebudi" title="Design">🎨</a></td> |
|||
<td align="center"><a href="https://github.com/Conor-Mack"><img src="https://avatars1.githubusercontent.com/u/36074859?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Conor_Mack</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=Conor-Mack" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=Conor-Mack" title="Tests">⚠️</a></td> |
|||
</tr> |
|||
<tr> |
|||
<td align="center"><a href="https://github.com/pngwn"><img src="https://avatars1.githubusercontent.com/u/12937446?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pngwn</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Tests">⚠️</a></td> |
|||
<td align="center"><a href="https://github.com/HugoLd"><img src="https://avatars0.githubusercontent.com/u/26521848?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HugoLd</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=HugoLd" title="Code">💻</a></td> |
|||
<td align="center"><a href="https://github.com/victoriasloan"><img src="https://avatars.githubusercontent.com/u/9913651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>victoriasloan</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=victoriasloan" title="Code">💻</a></td> |
|||
<td align="center"><a href="https://github.com/yashank09"><img src="https://avatars.githubusercontent.com/u/37672190?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yashank09</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=yashank09" title="Code">💻</a></td> |
|||
<td align="center"><a href="https://github.com/SOVLOOKUP"><img src="https://avatars.githubusercontent.com/u/53158137?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SOVLOOKUP</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=SOVLOOKUP" title="Code">💻</a></td> |
|||
</tr> |
|||
</table> |
|||
|
|||
<!-- markdownlint-restore --> |
|||
<!-- prettier-ignore-end --> |
|||
|
|||
<!-- ALL-CONTRIBUTORS-LIST:END --> |
|||
|
|||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! |
|||
@ -0,0 +1,22 @@ |
|||
<script> |
|||
import { |
|||
Body, |
|||
Menu, |
|||
MenuItem, |
|||
Detail, |
|||
MenuSection, |
|||
DetailSummary, |
|||
} from "@budibase/bbui" |
|||
|
|||
export let bindings |
|||
export let onBindingClick = () => {} |
|||
</script> |
|||
|
|||
<Menu> |
|||
{#each bindings as binding} |
|||
<MenuItem on:click={() => onBindingClick(binding)}> |
|||
<Detail size="M">{binding.name}</Detail> |
|||
<Body size="XS" noPadding>{binding.description}</Body> |
|||
</MenuItem> |
|||
{/each} |
|||
</Menu> |
|||
@ -0,0 +1,13 @@ |
|||
<script> |
|||
import { goto } from "@roxi/routify" |
|||
|
|||
export let value |
|||
</script> |
|||
|
|||
<span on:click={() => $goto(`./${value}`)}>{value}</span> |
|||
|
|||
<style> |
|||
span { |
|||
text-transform: capitalize; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,142 @@ |
|||
<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" |
|||
|
|||
const ConfigTypes = { |
|||
SMTP: "smtp", |
|||
} |
|||
|
|||
export let template |
|||
|
|||
let selected = "Edit" |
|||
let selectedBindingTab = "Template" |
|||
let htmlEditor |
|||
|
|||
$: selectedTemplate = $email.templates.find( |
|||
({ purpose }) => purpose === template |
|||
) |
|||
$: templateBindings = |
|||
$email.definitions?.bindings[selectedTemplate.purpose] || [] |
|||
|
|||
async function saveTemplate() { |
|||
try { |
|||
// Save your template config |
|||
await email.templates.save(selectedTemplate) |
|||
notifications.success(`Template saved.`) |
|||
} catch (err) { |
|||
notifications.error(`Failed to update template settings. ${err}`) |
|||
} |
|||
} |
|||
|
|||
function setTemplateBinding(binding) { |
|||
htmlEditor.update((selectedTemplate.contents += `{{ ${binding.name} }}`)) |
|||
} |
|||
</script> |
|||
|
|||
<Page wide gap="L"> |
|||
<div class="backbutton" on:click={() => $goto("./")}> |
|||
<Icon name="BackAndroid" /> |
|||
<span>Back</span> |
|||
</div> |
|||
<header> |
|||
<Heading> |
|||
Email Template: {template} |
|||
</Heading> |
|||
<Button cta on:click={saveTemplate}>Save</Button> |
|||
</header> |
|||
<Tabs {selected}> |
|||
<Tab title="Edit"> |
|||
<div class="template-editor"> |
|||
<Editor |
|||
editorHeight={800} |
|||
bind:this={htmlEditor} |
|||
mode="handlebars" |
|||
on:change={e => { |
|||
selectedTemplate.contents = e.detail.value |
|||
}} |
|||
value={selectedTemplate.contents} |
|||
/> |
|||
<div class="bindings-editor"> |
|||
<Detail size="L">Bindings</Detail> |
|||
<Tabs selected={selectedBindingTab}> |
|||
<Tab title="Template"> |
|||
<TemplateBindings |
|||
title="Template Bindings" |
|||
bindings={templateBindings} |
|||
onBindingClick={setTemplateBinding} |
|||
/> |
|||
</Tab> |
|||
<Tab title="Common"> |
|||
<TemplateBindings |
|||
title="Common Bindings" |
|||
bindings={$email.definitions.bindings.common} |
|||
onBindingClick={setTemplateBinding} |
|||
/> |
|||
</Tab> |
|||
</Tabs> |
|||
</div> |
|||
</div></Tab |
|||
> |
|||
<Tab title="Preview"> |
|||
<div class="preview" transition:fade> |
|||
{@html selectedTemplate.contents} |
|||
</div> |
|||
</Tab> |
|||
</Tabs> |
|||
</Page> |
|||
|
|||
<style> |
|||
.template-editor { |
|||
display: grid; |
|||
grid-template-columns: 1fr 20%; |
|||
grid-gap: var(--spacing-xl); |
|||
margin-top: var(--spacing-xl); |
|||
} |
|||
|
|||
header { |
|||
display: flex; |
|||
width: 100%; |
|||
justify-content: space-between; |
|||
margin-bottom: var(--spacing-l); |
|||
margin-top: var(--spacing-l); |
|||
} |
|||
|
|||
.preview { |
|||
background: white; |
|||
height: 800px; |
|||
padding: var(--spacing-xl); |
|||
} |
|||
|
|||
.backbutton { |
|||
display: flex; |
|||
gap: var(--spacing-m); |
|||
margin-bottom: var(--spacing-xl); |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,191 @@ |
|||
<script> |
|||
import { goto } from "@roxi/routify" |
|||
import { |
|||
Menu, |
|||
MenuItem, |
|||
Button, |
|||
Heading, |
|||
Divider, |
|||
Label, |
|||
Modal, |
|||
ModalContent, |
|||
notifications, |
|||
Layout, |
|||
Input, |
|||
TextArea, |
|||
Body, |
|||
Page, |
|||
Select, |
|||
MenuSection, |
|||
MenuSeparator, |
|||
Table, |
|||
} from "@budibase/bbui" |
|||
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 api from "builderStore/api" |
|||
|
|||
const ConfigTypes = { |
|||
SMTP: "smtp", |
|||
} |
|||
|
|||
const templateSchema = { |
|||
purpose: { |
|||
displayName: "Email", |
|||
editable: false, |
|||
}, |
|||
} |
|||
|
|||
const customRenderers = [ |
|||
{ |
|||
column: "purpose", |
|||
component: TemplateLink, |
|||
}, |
|||
] |
|||
|
|||
let smtpConfig |
|||
let bindingsOpen = false |
|||
let htmlModal |
|||
let htmlEditor |
|||
let loading |
|||
|
|||
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 { |
|||
await email.templates.save(selectedTemplate) |
|||
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 |
|||
} |
|||
} |
|||
|
|||
onMount(async () => { |
|||
loading = true |
|||
await fetchSmtp() |
|||
await email.templates.fetch() |
|||
loading = false |
|||
}) |
|||
</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> |
|||
</Layout> |
|||
<Button cta on:click={saveSmtp}>Save</Button> |
|||
</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> |
|||
<Table |
|||
{customRenderers} |
|||
data={$email.templates} |
|||
schema={templateSchema} |
|||
{loading} |
|||
allowEditRows={false} |
|||
allowSelectRows={false} |
|||
allowEditColumns={false} |
|||
/> |
|||
{/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,44 @@ |
|||
import { writable } from "svelte/store" |
|||
import api from "builderStore/api" |
|||
|
|||
export function createEmailStore() { |
|||
const store = writable([]) |
|||
|
|||
return { |
|||
subscribe: store.subscribe, |
|||
templates: { |
|||
fetch: async () => { |
|||
// fetch the email template definitions
|
|||
const response = await api.get(`/api/admin/template/definitions`) |
|||
const definitions = await response.json() |
|||
|
|||
// fetch the email templates themselves
|
|||
const templatesResponse = await api.get(`/api/admin/template/email`) |
|||
const templates = await templatesResponse.json() |
|||
|
|||
store.set({ |
|||
definitions, |
|||
templates, |
|||
}) |
|||
}, |
|||
save: async template => { |
|||
// Save your template config
|
|||
const response = await api.post(`/api/admin/template`, template) |
|||
const json = await response.json() |
|||
if (response.status !== 200) throw new Error(json.message) |
|||
template._rev = json._rev |
|||
template._id = json._id |
|||
|
|||
store.update(state => { |
|||
const currentIdx = state.templates.findIndex( |
|||
template => template.purpose === json.purpose |
|||
) |
|||
state.templates.splice(currentIdx, 1, template) |
|||
return state |
|||
}) |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
|
|||
export const email = createEmailStore() |
|||
@ -1,3 +1,4 @@ |
|||
export { organisation } from "./organisation" |
|||
export { admin } from "./admin" |
|||
export { apps } from "./apps" |
|||
export { email } from "./email" |
|||
|
|||
File diff suppressed because it is too large
@ -0,0 +1,67 @@ |
|||
const { sendSmtpEmail } = require("../../utilities/workerRequests") |
|||
|
|||
module.exports.definition = { |
|||
description: "Send an email using SMTP", |
|||
tagline: "Send SMTP email to {{inputs.to}}", |
|||
icon: "ri-mail-open-line", |
|||
name: "Send Email (SMTP)", |
|||
type: "ACTION", |
|||
stepId: "SEND_EMAIL_SMTP", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
to: { |
|||
type: "string", |
|||
title: "Send To", |
|||
}, |
|||
from: { |
|||
type: "string", |
|||
title: "Send From", |
|||
}, |
|||
subject: { |
|||
type: "string", |
|||
title: "Email Subject", |
|||
}, |
|||
contents: { |
|||
type: "string", |
|||
title: "HTML Contents", |
|||
}, |
|||
}, |
|||
required: ["to", "from", "subject", "contents"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the email was sent", |
|||
}, |
|||
response: { |
|||
type: "object", |
|||
description: "A response from the email client, this may be an error", |
|||
}, |
|||
}, |
|||
required: ["success"], |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
module.exports.run = async function ({ inputs }) { |
|||
let { to, from, subject, contents } = inputs |
|||
if (!contents) { |
|||
contents = "<h1>No content</h1>" |
|||
} |
|||
try { |
|||
let response = await sendSmtpEmail(to, from, subject, contents) |
|||
return { |
|||
success: true, |
|||
response, |
|||
} |
|||
} catch (err) { |
|||
console.error(err) |
|||
return { |
|||
success: false, |
|||
response: err, |
|||
} |
|||
} |
|||
} |
|||
@ -1,8 +1,8 @@ |
|||
module.exports.definition = { |
|||
description: "Send an email", |
|||
description: "Send an email using SendGrid", |
|||
tagline: "Send email to {{inputs.to}}", |
|||
icon: "ri-mail-open-line", |
|||
name: "Send Email", |
|||
name: "Send Email (SendGrid)", |
|||
type: "ACTION", |
|||
stepId: "SEND_EMAIL", |
|||
inputs: {}, |
|||
File diff suppressed because it is too large
@ -0,0 +1,93 @@ |
|||
const authPkg = require("@budibase/auth") |
|||
const { google } = require("@budibase/auth/src/middleware") |
|||
const { Configs } = require("../../constants") |
|||
const CouchDB = require("../../db") |
|||
const { clearCookie } = authPkg.utils |
|||
const { Cookies } = authPkg.constants |
|||
const { passport } = authPkg.auth |
|||
|
|||
const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name |
|||
|
|||
exports.authenticate = async (ctx, next) => { |
|||
return passport.authenticate("local", async (err, user) => { |
|||
if (err) { |
|||
return ctx.throw(403, "Unauthorized") |
|||
} |
|||
|
|||
const expires = new Date() |
|||
expires.setDate(expires.getDate() + 1) |
|||
|
|||
if (!user) { |
|||
return ctx.throw(403, "Unauthorized") |
|||
} |
|||
|
|||
ctx.cookies.set(Cookies.Auth, user.token, { |
|||
expires, |
|||
path: "/", |
|||
httpOnly: false, |
|||
overwrite: true, |
|||
}) |
|||
|
|||
delete user.token |
|||
|
|||
ctx.body = { user } |
|||
})(ctx, next) |
|||
} |
|||
|
|||
exports.logout = async ctx => { |
|||
clearCookie(ctx, Cookies.Auth) |
|||
ctx.body = { message: "User logged out" } |
|||
} |
|||
|
|||
/** |
|||
* The initial call that google authentication makes to take you to the google login screen. |
|||
* On a successful login, you will be redirected to the googleAuth callback route. |
|||
*/ |
|||
exports.googlePreAuth = async (ctx, next) => { |
|||
const db = new CouchDB(GLOBAL_DB) |
|||
const config = await authPkg.db.getScopedFullConfig(db, { |
|||
type: Configs.GOOGLE, |
|||
group: ctx.query.group, |
|||
}) |
|||
const strategy = await google.strategyFactory(config) |
|||
|
|||
return passport.authenticate(strategy, { |
|||
scope: ["profile", "email"], |
|||
})(ctx, next) |
|||
} |
|||
|
|||
exports.googleAuth = async (ctx, next) => { |
|||
const db = new CouchDB(GLOBAL_DB) |
|||
|
|||
const config = await authPkg.db.getScopedFullConfig(db, { |
|||
type: Configs.GOOGLE, |
|||
group: ctx.query.group, |
|||
}) |
|||
const strategy = await google.strategyFactory(config) |
|||
|
|||
return passport.authenticate( |
|||
strategy, |
|||
{ successRedirect: "/", failureRedirect: "/error" }, |
|||
async (err, user) => { |
|||
if (err) { |
|||
return ctx.throw(403, "Unauthorized") |
|||
} |
|||
|
|||
const expires = new Date() |
|||
expires.setDate(expires.getDate() + 1) |
|||
|
|||
if (!user) { |
|||
return ctx.throw(403, "Unauthorized") |
|||
} |
|||
|
|||
ctx.cookies.set(Cookies.Auth, user.token, { |
|||
expires, |
|||
path: "/", |
|||
httpOnly: false, |
|||
overwrite: true, |
|||
}) |
|||
|
|||
ctx.redirect("/") |
|||
} |
|||
)(ctx, next) |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
<tr> |
|||
<td class="email-body" width="570" cellpadding="0" cellspacing="0"> |
|||
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation"> |
|||
<!-- Body content --> |
|||
<tr> |
|||
<td class="content-cell"> |
|||
<div class="f-fallback"> |
|||
{{ contents }} |
|||
</div> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
</td> |
|||
</tr> |
|||
@ -1,408 +0,0 @@ |
|||
/* Based on templates: https://github.com/wildbit/postmark-templates/blob/master/templates/plain */ |
|||
/* Base ------------------------------ */ |
|||
|
|||
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap'); |
|||
body { |
|||
width: 100% !important; |
|||
height: 100%; |
|||
margin: 0; |
|||
-webkit-text-size-adjust: none; |
|||
} |
|||
|
|||
a { |
|||
color: #3869D4; |
|||
} |
|||
|
|||
a img { |
|||
border: none; |
|||
} |
|||
|
|||
td { |
|||
word-break: break-word; |
|||
} |
|||
|
|||
.preheader { |
|||
display: none !important; |
|||
visibility: hidden; |
|||
mso-hide: all; |
|||
font-size: 1px; |
|||
line-height: 1px; |
|||
max-height: 0; |
|||
max-width: 0; |
|||
opacity: 0; |
|||
overflow: hidden; |
|||
} |
|||
/* Type ------------------------------ */ |
|||
|
|||
body, |
|||
td, |
|||
th { |
|||
font-family: "Source Sans Pro", Helvetica, Arial, sans-serif; |
|||
} |
|||
|
|||
h1 { |
|||
margin-top: 0; |
|||
color: #333333; |
|||
font-size: 22px; |
|||
font-weight: bold; |
|||
text-align: left; |
|||
} |
|||
|
|||
h2 { |
|||
margin-top: 0; |
|||
color: #333333; |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
text-align: left; |
|||
} |
|||
|
|||
h3 { |
|||
margin-top: 0; |
|||
color: #333333; |
|||
font-size: 14px; |
|||
font-weight: bold; |
|||
text-align: left; |
|||
} |
|||
|
|||
td, |
|||
th { |
|||
font-size: 16px; |
|||
} |
|||
|
|||
p, |
|||
ul, |
|||
ol, |
|||
blockquote { |
|||
margin: .4em 0 1.1875em; |
|||
font-size: 16px; |
|||
line-height: 1.625; |
|||
} |
|||
|
|||
p.sub { |
|||
font-size: 13px; |
|||
} |
|||
/* Utilities ------------------------------ */ |
|||
|
|||
.align-right { |
|||
text-align: right; |
|||
} |
|||
|
|||
.align-left { |
|||
text-align: left; |
|||
} |
|||
|
|||
.align-center { |
|||
text-align: center; |
|||
} |
|||
/* Buttons ------------------------------ */ |
|||
|
|||
.button { |
|||
background-color: #3869D4; |
|||
border-top: 10px solid #3869D4; |
|||
border-right: 18px solid #3869D4; |
|||
border-bottom: 10px solid #3869D4; |
|||
border-left: 18px solid #3869D4; |
|||
display: inline-block; |
|||
color: #FFF; |
|||
text-decoration: none; |
|||
border-radius: 3px; |
|||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); |
|||
-webkit-text-size-adjust: none; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.button--green { |
|||
background-color: #22BC66; |
|||
border-top: 10px solid #22BC66; |
|||
border-right: 18px solid #22BC66; |
|||
border-bottom: 10px solid #22BC66; |
|||
border-left: 18px solid #22BC66; |
|||
} |
|||
|
|||
.button--red { |
|||
background-color: #FF6136; |
|||
border-top: 10px solid #FF6136; |
|||
border-right: 18px solid #FF6136; |
|||
border-bottom: 10px solid #FF6136; |
|||
border-left: 18px solid #FF6136; |
|||
} |
|||
|
|||
@media only screen and (max-width: 500px) { |
|||
.button { |
|||
width: 100% !important; |
|||
text-align: center !important; |
|||
} |
|||
} |
|||
/* Attribute list ------------------------------ */ |
|||
|
|||
.attributes { |
|||
margin: 0 0 21px; |
|||
} |
|||
|
|||
.attributes_content { |
|||
background-color: #F4F4F7; |
|||
padding: 16px; |
|||
} |
|||
|
|||
.attributes_item { |
|||
padding: 0; |
|||
} |
|||
/* Related Items ------------------------------ */ |
|||
|
|||
.related { |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 25px 0 0 0; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
} |
|||
|
|||
.related_item { |
|||
padding: 10px 0; |
|||
color: #CBCCCF; |
|||
font-size: 15px; |
|||
line-height: 18px; |
|||
} |
|||
|
|||
.related_item-title { |
|||
display: block; |
|||
margin: .5em 0 0; |
|||
} |
|||
|
|||
.related_item-thumb { |
|||
display: block; |
|||
padding-bottom: 10px; |
|||
} |
|||
|
|||
.related_heading { |
|||
border-top: 1px solid #CBCCCF; |
|||
text-align: center; |
|||
padding: 25px 0 10px; |
|||
} |
|||
/* Discount Code ------------------------------ */ |
|||
|
|||
.discount { |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 24px; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
background-color: #F4F4F7; |
|||
border: 2px dashed #CBCCCF; |
|||
} |
|||
|
|||
.discount_heading { |
|||
text-align: center; |
|||
} |
|||
|
|||
.discount_body { |
|||
text-align: center; |
|||
font-size: 15px; |
|||
} |
|||
/* Social Icons ------------------------------ */ |
|||
|
|||
.social { |
|||
width: auto; |
|||
} |
|||
|
|||
.social td { |
|||
padding: 0; |
|||
width: auto; |
|||
} |
|||
|
|||
.social_icon { |
|||
height: 20px; |
|||
margin: 0 8px 10px 8px; |
|||
padding: 0; |
|||
} |
|||
/* Data table ------------------------------ */ |
|||
|
|||
.purchase { |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 35px 0; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
} |
|||
|
|||
.purchase_content { |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 25px 0 0 0; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
} |
|||
|
|||
.purchase_item { |
|||
padding: 10px 0; |
|||
color: #51545E; |
|||
font-size: 15px; |
|||
line-height: 18px; |
|||
} |
|||
|
|||
.purchase_heading { |
|||
padding-bottom: 8px; |
|||
border-bottom: 1px solid #EAEAEC; |
|||
} |
|||
|
|||
.purchase_heading p { |
|||
margin: 0; |
|||
color: #85878E; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.purchase_footer { |
|||
padding-top: 15px; |
|||
border-top: 1px solid #EAEAEC; |
|||
} |
|||
|
|||
.purchase_total { |
|||
margin: 0; |
|||
text-align: right; |
|||
font-weight: bold; |
|||
color: #333333; |
|||
} |
|||
|
|||
.purchase_total--label { |
|||
padding: 0 15px 0 0; |
|||
} |
|||
|
|||
body { |
|||
background-color: #FFF; |
|||
color: #333; |
|||
} |
|||
|
|||
p { |
|||
color: #333; |
|||
} |
|||
|
|||
.email-wrapper { |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 0; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
} |
|||
|
|||
.email-content { |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 0; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
} |
|||
/* Masthead ----------------------- */ |
|||
|
|||
.email-masthead { |
|||
padding: 25px 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
.email-masthead_logo { |
|||
width: 94px; |
|||
} |
|||
|
|||
.email-masthead_name { |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
color: #A8AAAF; |
|||
text-decoration: none; |
|||
text-shadow: 0 1px 0 white; |
|||
} |
|||
/* Body ------------------------------ */ |
|||
|
|||
.email-body { |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 0; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
} |
|||
|
|||
.email-body_inner { |
|||
width: 570px; |
|||
margin: 0 auto; |
|||
padding: 0; |
|||
-premailer-width: 570px; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
} |
|||
|
|||
.email-footer { |
|||
width: 570px; |
|||
margin: 0 auto; |
|||
padding: 0; |
|||
-premailer-width: 570px; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
.email-footer p { |
|||
color: #A8AAAF; |
|||
} |
|||
|
|||
.body-action { |
|||
width: 100%; |
|||
margin: 30px auto; |
|||
padding: 0; |
|||
-premailer-width: 100%; |
|||
-premailer-cellpadding: 0; |
|||
-premailer-cellspacing: 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
.body-sub { |
|||
margin-top: 25px; |
|||
padding-top: 25px; |
|||
border-top: 1px solid #EAEAEC; |
|||
} |
|||
|
|||
.content-cell { |
|||
padding: 35px; |
|||
} |
|||
/*Media Queries ------------------------------ */ |
|||
|
|||
@media only screen and (max-width: 600px) { |
|||
.email-body_inner, |
|||
.email-footer { |
|||
width: 100% !important; |
|||
} |
|||
} |
|||
|
|||
@media (prefers-color-scheme: dark) { |
|||
body { |
|||
background-color: #333333 !important; |
|||
color: #FFF !important; |
|||
} |
|||
p, |
|||
ul, |
|||
ol, |
|||
blockquote, |
|||
h1, |
|||
h2, |
|||
h3, |
|||
span, |
|||
.purchase_item { |
|||
color: #FFF !important; |
|||
} |
|||
.attributes_content, |
|||
.discount { |
|||
background-color: #222 !important; |
|||
} |
|||
.email-masthead_name { |
|||
text-shadow: none !important; |
|||
} |
|||
} |
|||
|
|||
:root { |
|||
color-scheme: light dark; |
|||
supported-color-schemes: light dark; |
|||
} |
|||
Loading…
Reference in new issue