Merge pull request #369 from Budibase/feature/settings-modal
Adds a Settings modal for the BB Apps
This commit is contained in:
commit
2f255c70a4
|
@ -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",
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
||||||
|
Permissions
|
|
@ -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>
|
|
@ -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"
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue