Merge pull request #369 from Budibase/feature/settings-modal

Adds a Settings modal for the BB Apps
This commit is contained in:
Kevin Åberg Kultalahti 2020-06-29 16:01:20 +02:00 committed by GitHub
commit 2f255c70a4
15 changed files with 527 additions and 9 deletions

View File

@ -55,7 +55,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.13.0", "@budibase/bbui": "^1.14.0",
"@budibase/client": "^0.0.32", "@budibase/client": "^0.0.32",
"@nx-js/compiler-util": "^2.0.0", "@nx-js/compiler-util": "^2.0.0",
"codemirror": "^5.51.0", "codemirror": "^5.51.0",
@ -108,4 +108,4 @@
"svelte-jester": "^1.0.6" "svelte-jester": "^1.0.6"
}, },
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072" "gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
} }

View File

@ -0,0 +1,56 @@
<script>
import { Button } from "@budibase/bbui"
export let remove = false
</script>
<div class="container">
<div class="content">
<div class="img">
<img src="https://picsum.photos/60/60" alt="zoom" />
</div>
<div class="body">
<div class="title">Zoom</div>
<div class="description">
Lorem, ipsum dolor sit amet consectetur adipisicing elit
</div>
</div>
</div>
<div class="footer">
<Button wide error={remove} secondary={!remove} on:click>
<span>{remove ? 'Remove' : 'Add'}</span>
</Button>
</div>
</div>
<style>
.container {
display: grid;
padding: 12px;
background: var(--light-grey);
grid-gap: 20px;
}
span {
font-size: 12px;
font-weight: bold;
}
.content {
display: grid;
grid-gap: 12px;
grid-template-columns: 60px auto;
}
.title {
font-size: 14px;
font-weight: bold;
}
.description {
font-size: 12px;
}
.img {
border-radius: 3px;
overflow: hidden;
}
img {
height: 60px;
width: 60px;
}
</style>

View File

@ -0,0 +1,48 @@
<script>
import Modal from "./Modal.svelte"
import { SettingsIcon } from "components/common/Icons/"
import { getContext } from "svelte"
import { isActive, goto, layout } from "@sveltech/routify"
// Handle create app modal
const { open } = getContext("simple-modal")
const showSettingsModal = () => {
open(
Modal,
{
name: "Placeholder App Name",
description: "This is a hardcoded description that needs to change",
},
{
closeButton: false,
closeOnEsc: true,
styleContent: { padding: 0 },
closeOnOuterClick: true,
}
)
}
</script>
<span class="topnavitemright" on:click={showSettingsModal}>
<SettingsIcon />
</span>
<style>
span:first-letter {
text-transform: capitalize;
}
.topnavitemright {
cursor: pointer;
color: var(--ink-light);
margin: 0px 20px 0px 0px;
padding-top: 4px;
font-weight: 500;
font-size: 1rem;
height: 100%;
display: flex;
flex: 1;
align-items: center;
box-sizing: border-box;
}
</style>

View File

@ -0,0 +1,92 @@
<script>
import { General, Users, DangerZone } from "./tabs"
import { Input, TextArea, Button, Switcher } from "@budibase/bbui"
import { SettingsIcon, CloseIcon } from "components/common/Icons/"
import { getContext } from "svelte"
import { post } from "builderStore/api"
const { open, close } = getContext("simple-modal")
export let name = ""
export let description = ""
const tabs = [
{
title: "General",
key: "GENERAL",
component: General,
},
{
title: "Users",
key: "USERS",
component: Users,
},
{
title: "Danger Zone",
key: "DANGERZONE",
component: DangerZone,
},
]
let value = "GENERAL"
$: selectedTab = tabs.find(tab => tab.key === value).component
</script>
<div class="container">
<div class="body">
<div class="heading">
<span class="icon">
<SettingsIcon />
</span>
<h3>Settings</h3>
</div>
<Switcher headings={tabs} bind:value>
<svelte:component this={selectedTab} />
</Switcher>
</div>
<div class="close-button" on:click={close}>
<CloseIcon />
</div>
</div>
<style>
.container {
position: relative;
}
.close-button {
cursor: pointer;
position: absolute;
top: 20px;
right: 20px;
}
.close-button :global(svg) {
width: 24px;
height: 24px;
}
.heading {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 20px;
}
h3 {
margin: 0;
font-size: 24px;
font-weight: bold;
}
.icon {
display: grid;
border-radius: 3px;
align-content: center;
justify-content: center;
margin-right: 12px;
height: 20px;
width: 20px;
padding: 10px;
background-color: var(--blue-light);
}
.body {
padding: 40px 40px 80px 40px;
display: grid;
grid-gap: 20px;
}
</style>

View File

@ -0,0 +1,13 @@
<h3>
<slot />
</h3>
<style>
h3 {
font-size: 18px;
font-weight: bold;
color: var(--ink);
margin-top: 40px;
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,42 @@
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
import { Input, Select, Button } from "@budibase/bbui"
export let user
let editMode = false
</script>
<div class="inputs">
<Input
disabled
thin
bind:value={user.username}
name="Name"
placeholder="Username" />
<Select disabled={!editMode} bind:value={user.accessLevelId} thin>
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</Select>
{#if editMode}
<Button
blue
on:click={() => {
dispatch('save', user)
editMode = false
}}>
Save
</Button>
{:else}
<Button secondary on:click={() => (editMode = true)}>Edit</Button>
{/if}
</div>
<style>
.inputs {
display: grid;
justify-items: stretch;
grid-gap: 18px;
grid-template-columns: 1fr 1fr 1fr;
}
</style>

View File

@ -0,0 +1,46 @@
<script>
import { Input, TextArea, Button } from "@budibase/bbui"
import Title from "../TabTitle.svelte"
let value = ""
let loading = false
const deleteApp = () => {
loading = true
// Do stuff here to delete app!
// Navigate to start
}
</script>
<Title>Danger Zone</Title>
<div class="background">
<Input
on:change={e => (value = e.target.value)}
on:input={e => (value = e.target.value)}
thin
disabled={loading}
placeholder="Enter your name"
label="Type DELETE into the textbox, then click the following button to
delete your web app:" />
<Button
disabled={value !== 'DELETE' || loading}
primary
wide
on:click={deleteApp}>
Delete Entire Web App
</Button>
</div>
<style>
.background {
display: grid;
grid-gap: var(--space);
border-radius: 5px;
background-color: var(--light-grey);
padding: 12px 12px 18px 12px;
}
.background :global(button) {
max-width: 100%;
}
</style>

View File

@ -0,0 +1,26 @@
<script>
import { Input, TextArea, Button } from "@budibase/bbui"
import Title from "../TabTitle.svelte"
</script>
<Title>General</Title>
<div class="container">
<div class="background">
<Input thin edit placeholder="Enter your name" label="Name" />
</div>
<div class="background">
<TextArea thin edit placeholder="Enter your name" label="Name" />
</div>
</div>
<style>
.container {
display: grid;
grid-gap: var(--space);
}
.background {
border-radius: 5px;
background-color: var(--light-grey);
padding: 12px 12px 18px 12px;
}
</style>

View File

@ -0,0 +1,45 @@
<script>
import Integration from "../Integration.svelte"
</script>
<div class="container">
<div class="title">Your Integrations</div>
<div class="integrations">
<Integration remove />
<Integration remove />
<Integration remove />
<Integration remove />
</div>
<div class="apps">Recommended apps</div>
<div class="integrations">
<Integration />
<Integration />
<Integration />
<Integration />
</div>
</div>
<style>
.container {
display: grid;
grid-gap: 16px;
}
.integrations {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
}
.title {
font-size: 18px;
font-weight: bold;
}
.apps {
font-size: 14px;
font-weight: bold;
}
.background {
border-radius: 5px;
background-color: var(--light-grey);
padding: 12px 12px 18px 12px;
}
</style>

View File

@ -0,0 +1 @@
Permissions

View File

@ -0,0 +1,135 @@
<script>
import { Input, Select, Button } from "@budibase/bbui"
import Title from "../TabTitle.svelte"
import UserRow from "../UserRow.svelte"
import { store, backendUiStore } from "builderStore"
import api from "builderStore/api"
// import * as api from "../api"
let username = ""
let password = ""
let accessLevelId
$: valid = username && password && accessLevelId
$: appId = $store.appId
// Create user!
async function createUser() {
if (valid) {
const user = { name: username, username, password, accessLevelId }
const response = await api.post(`/api/users`, user)
const json = await response.json()
backendUiStore.actions.users.create(json)
fetchUsersPromise = fetchUsers()
}
}
// Update user!
async function updateUser(event) {
let data = event.detail
delete data.password
const response = await api.put(`/api/users`, data)
const users = await response.json()
backendUiStore.update(state => {
state.users = users
return state
})
fetchUsersPromise = fetchUsers()
}
// Get users
async function fetchUsers() {
const response = await api.get(`/api/users`)
const users = await response.json()
backendUiStore.update(state => {
state.users = users
return state
})
return users
}
let fetchUsersPromise = fetchUsers()
</script>
<Title>Users</Title>
<div class="container">
<div class="background create">
<div class="title">Create new user</div>
<div class="inputs">
<Input thin bind:value={username} name="Name" placeholder="Username" />
<Input
thin
bind:value={password}
name="Password"
placeholder="Password" />
<Select bind:value={accessLevelId} thin>
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</Select>
</div>
<div class="create-button">
<Button on:click={createUser} small blue>Create</Button>
</div>
</div>
<div class="background">
<div class="title">Current Users</div>
{#await fetchUsersPromise}
Loading state!
{:then users}
<ul>
{#each users as user}
<li>
<UserRow {user} on:save={updateUser} />
</li>
{:else}
<li>No Users found</li>
{/each}
</ul>
{:catch error}
err0r
{/await}
</div>
</div>
<style>
.container {
display: grid;
grid-gap: 14px;
}
.background {
position: relative;
display: grid;
grid-gap: 12px;
border-radius: 5px;
background-color: var(--grey-2);
padding: 12px 12px 18px 12px;
}
.background.create {
background-color: var(--blue-light);
}
.inputs :global(select) {
padding: 12px 9px;
height: initial;
}
.create-button {
position: absolute;
top: 12px;
right: 12px;
}
.title {
font-size: 14px;
font-weight: 500;
}
.inputs {
display: grid;
grid-gap: 18px;
grid-template-columns: 1fr 1fr 1fr;
}
ul {
list-style: none;
padding: 0;
display: grid;
grid-gap: 8px;
}
</style>

View File

@ -0,0 +1,5 @@
export { default as General } from "./General.svelte"
export { default as Integrations } from "./Integrations.svelte"
export { default as Permissions } from "./Permissions.svelte"
export { default as Users } from "./Users.svelte"
export { default as DangerZone } from "./DangerZone.svelte"

View File

@ -1,6 +1,7 @@
<script> <script>
import Modal from "svelte-simple-modal" import Modal from "svelte-simple-modal"
import { store, workflowStore } from "builderStore" import { store, workflowStore } from "builderStore"
import SettingsLink from "components/settings/Link.svelte"
import { get } from "builderStore/api" import { get } from "builderStore/api"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
@ -54,12 +55,7 @@
hoverColor="var(--secondary75)"/> --> hoverColor="var(--secondary75)"/> -->
</div> </div>
<div class="toprightnav"> <div class="toprightnav">
<span <SettingsLink />
class:active={$isActive(`/settings`)}
class="topnavitemright"
on:click={() => $goto(`/settings`)}>
<SettingsIcon />
</span>
<span <span
class:active={false} class:active={false}
class="topnavitemright" class="topnavitemright"

View File

@ -63,7 +63,19 @@ exports.create = async function(ctx) {
} }
} }
exports.update = async function() {} exports.update = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
const user = ctx.request.body
const dbUser = db.get(ctx.request.body._id)
const newData = { ...dbUser, ...user }
const response = await db.put(newData)
user._rev = response.rev
ctx.status = 200
ctx.message = `User ${ctx.request.body.username} updated successfully.`
ctx.body = response
}
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const database = new CouchDB(ctx.user.instanceId) const database = new CouchDB(ctx.user.instanceId)

View File

@ -8,6 +8,7 @@ const router = Router()
router router
.get("/api/users", authorized(LIST_USERS), controller.fetch) .get("/api/users", authorized(LIST_USERS), controller.fetch)
.get("/api/users/:username", authorized(USER_MANAGEMENT), controller.find) .get("/api/users/:username", authorized(USER_MANAGEMENT), controller.find)
.put("/api/users/", authorized(USER_MANAGEMENT), controller.update)
.post("/api/users", authorized(USER_MANAGEMENT), controller.create) .post("/api/users", authorized(USER_MANAGEMENT), controller.create)
.delete( .delete(
"/api/users/:username", "/api/users/:username",