Merge branch 'master' of github.com:Budibase/budibase into feature/app-updated-at
This commit is contained in:
commit
a0b0ddf0cf
|
@ -52,7 +52,7 @@
|
||||||
"@spectrum-css/icon": "^3.0.1",
|
"@spectrum-css/icon": "^3.0.1",
|
||||||
"@spectrum-css/illustratedmessage": "^3.0.2",
|
"@spectrum-css/illustratedmessage": "^3.0.2",
|
||||||
"@spectrum-css/inputgroup": "^3.0.2",
|
"@spectrum-css/inputgroup": "^3.0.2",
|
||||||
"@spectrum-css/label": "^2.0.9",
|
"@spectrum-css/label": "^2.0.10",
|
||||||
"@spectrum-css/link": "^3.1.1",
|
"@spectrum-css/link": "^3.1.1",
|
||||||
"@spectrum-css/menu": "^3.0.1",
|
"@spectrum-css/menu": "^3.0.1",
|
||||||
"@spectrum-css/modal": "^3.0.1",
|
"@spectrum-css/modal": "^3.0.1",
|
||||||
|
@ -64,6 +64,7 @@
|
||||||
"@spectrum-css/radio": "^3.0.2",
|
"@spectrum-css/radio": "^3.0.2",
|
||||||
"@spectrum-css/search": "^3.0.2",
|
"@spectrum-css/search": "^3.0.2",
|
||||||
"@spectrum-css/sidenav": "^3.0.2",
|
"@spectrum-css/sidenav": "^3.0.2",
|
||||||
|
"@spectrum-css/statuslight": "^3.0.2",
|
||||||
"@spectrum-css/switch": "^1.0.2",
|
"@spectrum-css/switch": "^1.0.2",
|
||||||
"@spectrum-css/table": "^3.0.1",
|
"@spectrum-css/table": "^3.0.1",
|
||||||
"@spectrum-css/tabs": "^3.0.1",
|
"@spectrum-css/tabs": "^3.0.1",
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/label/dist/index-vars.css"
|
||||||
|
|
||||||
|
export let size = "M"
|
||||||
|
export let grey = false
|
||||||
|
export let red = false
|
||||||
|
export let orange = false
|
||||||
|
export let yellow = false
|
||||||
|
export let seafoam = false
|
||||||
|
export let active = false
|
||||||
|
export let inactive = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="spectrum-Label"
|
||||||
|
class:spectrum-Label--small={size === "S"}
|
||||||
|
class:spectrum-Label--large={size === "L"}
|
||||||
|
class:spectrum-Label--grey={grey}
|
||||||
|
class:spectrum-Label--red={red}
|
||||||
|
class:spectrum-Label--orange={orange}
|
||||||
|
class:spectrum-Label--yellow={yellow}
|
||||||
|
class:spectrum-Label--seafoam={seafoam}
|
||||||
|
class:spectrum-Label--active={active}
|
||||||
|
class:spectrum-Label--inactive={inactive}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/statuslight"
|
||||||
|
|
||||||
|
export let size = "M"
|
||||||
|
export let celery = false
|
||||||
|
export let yellow = false
|
||||||
|
export let fuchsia = false
|
||||||
|
export let indigo = false
|
||||||
|
export let seafoam = false
|
||||||
|
export let chartreuse = false
|
||||||
|
export let magenta = false
|
||||||
|
export let purple = false
|
||||||
|
export let neutral = false
|
||||||
|
export let info = false
|
||||||
|
export let positive = false
|
||||||
|
export let notice = false
|
||||||
|
export let negative = false
|
||||||
|
export let disabled = false
|
||||||
|
export let active = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="spectrum-StatusLight spectrum-StatusLight--size{size}"
|
||||||
|
class:spectrum-StatusLight--celery={celery}
|
||||||
|
class:spectrum-StatusLight--yellow={yellow}
|
||||||
|
class:spectrum-StatusLight--fuchsia={fuchsia}
|
||||||
|
class:spectrum-StatusLight--indigo={indigo}
|
||||||
|
class:spectrum-StatusLight--seafoam={seafoam}
|
||||||
|
class:spectrum-StatusLight--chartreuse={chartreuse}
|
||||||
|
class:spectrum-StatusLight--magenta={magenta}
|
||||||
|
class:spectrum-StatusLight--purple={purple}
|
||||||
|
class:spectrum-StatusLight--neutral={neutral}
|
||||||
|
class:spectrum-StatusLight--info={info}
|
||||||
|
class:spectrum-StatusLight--positive={positive}
|
||||||
|
class:spectrum-StatusLight--notice={notice}
|
||||||
|
class:spectrum-StatusLight--negative={negative}
|
||||||
|
class:spectrum-StatusLight--disabled={disabled}
|
||||||
|
class:spectrum-StatusLight--active={active}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
|
@ -1,6 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
|
import "@spectrum-css/tags/dist/index-vars.css"
|
||||||
import Avatar from "../Avatar/Avatar.svelte"
|
import Avatar from "../Avatar/Avatar.svelte"
|
||||||
import ClearButton from "../ClearButton/ClearButton.svelte"
|
import ClearButton from "../ClearButton/ClearButton.svelte"
|
||||||
|
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let avatar = ""
|
export let avatar = ""
|
||||||
export let invalid = false
|
export let invalid = false
|
||||||
|
@ -32,3 +34,10 @@
|
||||||
<ClearButton on:click />
|
<ClearButton on:click />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spectrum-Tags-item {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -52,6 +52,8 @@ export { default as TreeItem } from "./TreeView/Item.svelte"
|
||||||
export { default as Divider } from "./Divider/Divider.svelte"
|
export { default as Divider } from "./Divider/Divider.svelte"
|
||||||
export { default as Search } from "./Form/Search.svelte"
|
export { default as Search } from "./Form/Search.svelte"
|
||||||
export { default as Pagination } from "./Pagination/Pagination.svelte"
|
export { default as Pagination } from "./Pagination/Pagination.svelte"
|
||||||
|
export { default as Badge } from "./Badge/Badge.svelte"
|
||||||
|
export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
|
||||||
|
|
||||||
// Typography
|
// Typography
|
||||||
export { default as Body } from "./Typography/Body.svelte"
|
export { default as Body } from "./Typography/Body.svelte"
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.2.tgz#f1b13603832cbd22394f3d898af13203961f8691"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.2.tgz#f1b13603832cbd22394f3d898af13203961f8691"
|
||||||
integrity sha512-O0G3Lw9gxsh8gTLQWIAKkN1O8cWhjpEUl+oR1PguIKFni72uNr2ikU9piOwy/r0gJG2Q/TVs6hAshoAAkmsSzw==
|
integrity sha512-O0G3Lw9gxsh8gTLQWIAKkN1O8cWhjpEUl+oR1PguIKFni72uNr2ikU9piOwy/r0gJG2Q/TVs6hAshoAAkmsSzw==
|
||||||
|
|
||||||
"@spectrum-css/label@^2.0.9":
|
"@spectrum-css/label@^2.0.10":
|
||||||
version "2.0.10"
|
version "2.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001"
|
||||||
integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ==
|
integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ==
|
||||||
|
@ -201,6 +201,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.2.tgz#9d70f408d588ee79c69857751010333671f32713"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.2.tgz#9d70f408d588ee79c69857751010333671f32713"
|
||||||
integrity sha512-YpIdH/F0jEICYmoduGrnkTmxwJq1kfKxEp0wOs+ZkQOsvKMv1an7nyhsfOKCQqcGNfYzJ9mJAk7/u5+vsxHa8g==
|
integrity sha512-YpIdH/F0jEICYmoduGrnkTmxwJq1kfKxEp0wOs+ZkQOsvKMv1an7nyhsfOKCQqcGNfYzJ9mJAk7/u5+vsxHa8g==
|
||||||
|
|
||||||
|
"@spectrum-css/statuslight@^3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5"
|
||||||
|
integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w==
|
||||||
|
|
||||||
"@spectrum-css/switch@^1.0.2":
|
"@spectrum-css/switch@^1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
const updatePassword = async () => {
|
const updatePassword = async () => {
|
||||||
try {
|
try {
|
||||||
await auth.updateSelf({ ...$auth.user, password })
|
await auth.updateSelf({ ...$auth.user, password })
|
||||||
notifications.success("Information updated successfully")
|
notifications.success("Password changed successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Failed to update password")
|
notifications.error("Failed to update password")
|
||||||
}
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
<script>
|
|
||||||
import { themeStore } from "builderStore"
|
|
||||||
import { Select } from "@budibase/bbui"
|
|
||||||
import { capitalise } from "../../helpers"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Select
|
|
||||||
options={$themeStore.options}
|
|
||||||
bind:value={$themeStore.theme}
|
|
||||||
placeholder={null}
|
|
||||||
getOptionLabel={capitalise}
|
|
||||||
/>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Icon, Label, Modal, ModalContent } from "@budibase/bbui"
|
|
||||||
import ThemeEditor from "./ThemeEditor.svelte"
|
|
||||||
|
|
||||||
let modal
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="topnavitemright" on:click={modal.show}>
|
|
||||||
<Icon hoverable name="ColorFill" />
|
|
||||||
</div>
|
|
||||||
<Modal bind:this={modal}>
|
|
||||||
<ModalContent
|
|
||||||
title="Builder Theme"
|
|
||||||
confirmText="Done"
|
|
||||||
showCancelButton={false}
|
|
||||||
>
|
|
||||||
<ThemeEditor />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.topnavitemright {
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--grey-7);
|
|
||||||
margin: 0 12px 0 0;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -6,51 +6,67 @@
|
||||||
Layout,
|
Layout,
|
||||||
ActionMenu,
|
ActionMenu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
StatusLight,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
|
import { AppStatus } from "constants"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
export let exportApp
|
export let exportApp
|
||||||
export let openApp
|
export let viewApp
|
||||||
|
export let editApp
|
||||||
export let deleteApp
|
export let deleteApp
|
||||||
|
export let unpublishApp
|
||||||
export let releaseLock
|
export let releaseLock
|
||||||
export let deletable
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<Layout noPadding gap="XS" alignContent="start">
|
<Layout noPadding gap="XS" alignContent="start">
|
||||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="name" on:click={() => openApp(app)}>
|
{#if app.lockedBy}
|
||||||
|
<Icon name="LockClosed" />
|
||||||
|
{/if}
|
||||||
|
<div class="name" on:click={() => editApp(app)}>
|
||||||
<Heading size="XS">
|
<Heading size="XS">
|
||||||
{app.name}
|
{app.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<Icon slot="control" name="More" hoverable />
|
<Icon slot="control" name="More" hoverable />
|
||||||
|
{#if app.deployed}
|
||||||
|
<MenuItem on:click={() => viewApp(app)} icon="GlobeOutline">
|
||||||
|
View published app
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
{#if app.lockedYou}
|
||||||
|
<MenuItem on:click={() => releaseLock(app)} icon="LockOpen">
|
||||||
|
Release lock
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
<MenuItem on:click={() => exportApp(app)} icon="Download">
|
<MenuItem on:click={() => exportApp(app)} icon="Download">
|
||||||
Export
|
Export
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{#if deletable}
|
{#if app.deployed}
|
||||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
|
<MenuItem on:click={() => unpublishApp(app)} icon="GlobeRemove">
|
||||||
Delete
|
Unpublish
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
{#if !app.deployed}
|
||||||
<MenuItem on:click={() => releaseLock(app.appId)} icon="LockOpen">
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
|
||||||
Release Lock
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Edited {Math.floor(1 + Math.random() * 10)} months ago
|
Updated {Math.floor(1 + Math.random() * 10)} months ago
|
||||||
</Body>
|
</Body>
|
||||||
{#if app.lockedBy}
|
<StatusLight active={app.deployed} neutral={!app.deployed}>
|
||||||
<Icon name="LockClosed" />
|
{#if app.deployed}Published{:else}Unpublished{/if}
|
||||||
{/if}
|
</StatusLight>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,13 +75,17 @@
|
||||||
.wrapper {
|
.wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.wrapper :global(.spectrum-StatusLight) {
|
||||||
|
padding: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
height: 135px;
|
height: 135px;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title,
|
|
||||||
.status {
|
.status {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -74,12 +94,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
text-decoration: none;
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: 0;
|
}
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
.title {
|
||||||
margin-right: var(--spacing-m);
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.title :global(.spectrum-Icon) {
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
.title :global(h1) {
|
.title :global(h1) {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -1,55 +1,83 @@
|
||||||
<script>
|
<script>
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
import { Heading, Button, Icon, ActionMenu, MenuItem } from "@budibase/bbui"
|
import {
|
||||||
|
Heading,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
ActionMenu,
|
||||||
|
MenuItem,
|
||||||
|
StatusLight,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
export let openApp
|
|
||||||
export let exportApp
|
export let exportApp
|
||||||
|
export let viewApp
|
||||||
|
export let editApp
|
||||||
export let deleteApp
|
export let deleteApp
|
||||||
|
export let unpublishApp
|
||||||
export let releaseLock
|
export let releaseLock
|
||||||
export let last
|
|
||||||
export let deletable
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="title" class:last>
|
<div class="title">
|
||||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<div class="name" on:click={() => openApp(app)}>
|
<div class="name" on:click={() => editApp(app)}>
|
||||||
<Heading size="XS">
|
<Heading size="XS">
|
||||||
{app.name}
|
{app.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class:last>
|
<div>
|
||||||
Edited {Math.round(Math.random() * 10 + 1)} months ago
|
Updated {Math.round(Math.random() * 10 + 1)} months ago
|
||||||
</div>
|
</div>
|
||||||
<div class:last>
|
<div>
|
||||||
{#if app.lockedBy}
|
<StatusLight
|
||||||
{#if app.lockedBy.email === $auth.user.email}
|
positive={!app.lockedYou && !app.lockedOther}
|
||||||
<div class="status status--locked-you" />
|
notice={app.lockedYou}
|
||||||
|
negative={app.lockedOther}
|
||||||
|
>
|
||||||
|
{#if app.lockedYou}
|
||||||
Locked by you
|
Locked by you
|
||||||
{:else}
|
{:else if app.lockedOther}
|
||||||
<div class="status status--locked-other" />
|
|
||||||
Locked by {app.lockedBy.email}
|
Locked by {app.lockedBy.email}
|
||||||
|
{:else}
|
||||||
|
Open
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
</StatusLight>
|
||||||
<div class="status status--open" />
|
|
||||||
Open
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<div class:last>
|
<div>
|
||||||
<Button on:click={() => openApp(app)} size="S" secondary>Open</Button>
|
<StatusLight active={app.deployed} neutral={!app.deployed}>
|
||||||
|
{#if app.deployed}Published{:else}Unpublished{/if}
|
||||||
|
</StatusLight>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
disabled={app.lockedOther}
|
||||||
|
on:click={() => editApp(app)}
|
||||||
|
size="S"
|
||||||
|
secondary>Open</Button
|
||||||
|
>
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<Icon hoverable slot="control" name="More" />
|
<Icon hoverable slot="control" name="More" />
|
||||||
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
|
{#if app.deployed}
|
||||||
{#if deletable}
|
<MenuItem on:click={() => viewApp(app)} icon="GlobeOutline">
|
||||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
View published app
|
||||||
{/if}
|
|
||||||
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
|
||||||
<MenuItem on:click={() => releaseLock(app.appId)} icon="LockOpen">
|
|
||||||
Release Lock
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if app.lockedYou}
|
||||||
|
<MenuItem on:click={() => releaseLock(app)} icon="LockOpen">
|
||||||
|
Release lock
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
|
||||||
|
{#if app.deployed}
|
||||||
|
<MenuItem on:click={() => unpublishApp(app)} icon="GlobeRemove">
|
||||||
|
Unpublish
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
{#if !app.deployed}
|
||||||
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
||||||
|
{/if}
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -61,24 +89,16 @@
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.name :global(.spectrum-Heading) {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.title :global(h1:hover) {
|
.title :global(h1:hover) {
|
||||||
color: var(--spectrum-global-color-blue-600);
|
color: var(--spectrum-global-color-blue-600);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 130ms ease;
|
transition: color 130ms ease;
|
||||||
}
|
}
|
||||||
.status {
|
|
||||||
height: 10px;
|
|
||||||
width: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.status--locked-you {
|
|
||||||
background-color: var(--spectrum-global-color-orange-600);
|
|
||||||
}
|
|
||||||
.status--locked-other {
|
|
||||||
background-color: var(--spectrum-global-color-red-600);
|
|
||||||
}
|
|
||||||
.status--open {
|
|
||||||
background-color: var(--spectrum-global-color-green-600);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
Heading,
|
|
||||||
Divider,
|
|
||||||
notifications,
|
|
||||||
ModalContent,
|
|
||||||
Toggle,
|
|
||||||
Body,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import ThemeEditor from "components/settings/ThemeEditor.svelte"
|
|
||||||
import analytics from "analytics"
|
|
||||||
|
|
||||||
$: analyticsDisabled = analytics.disabled()
|
|
||||||
|
|
||||||
async function save() {
|
|
||||||
notifications.success(`Settings saved.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAnalytics() {
|
|
||||||
if (analyticsDisabled) {
|
|
||||||
analytics.optIn()
|
|
||||||
} else {
|
|
||||||
analytics.optOut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent title="Builder settings" confirmText="Save" onConfirm={save}>
|
|
||||||
<Heading size="XS">Theme</Heading>
|
|
||||||
<ThemeEditor />
|
|
||||||
<Divider noMargin noGrid />
|
|
||||||
<Heading size="XS">Analytics</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
If you would like to send analytics that help us make budibase better,
|
|
||||||
please let us know below.
|
|
||||||
</Body>
|
|
||||||
<Toggle
|
|
||||||
text="Send Analytics To Budibase"
|
|
||||||
value={!analyticsDisabled}
|
|
||||||
on:change={toggleAnalytics}
|
|
||||||
/>
|
|
||||||
</ModalContent>
|
|
|
@ -10,8 +10,9 @@ export const FrontendTypes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppStatus = {
|
export const AppStatus = {
|
||||||
DEV: "dev",
|
ALL: "all",
|
||||||
PUBLISHED: "published",
|
DEV: "development",
|
||||||
|
DEPLOYED: "published",
|
||||||
}
|
}
|
||||||
|
|
||||||
// fields on the user table that cannot be edited
|
// fields on the user table that cannot be edited
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
import UpdateUserInfoModal from "./_components/UpdateUserInfoModal.svelte"
|
import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte"
|
||||||
import ChangePasswordModal from "./_components/ChangePasswordModal.svelte"
|
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let userInfoModal
|
let userInfoModal
|
||||||
|
@ -26,12 +26,14 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
await apps.load(AppStatus.DEV)
|
await apps.load()
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$: publishedApps = $apps.filter(app => app.status === AppStatus.DEPLOYED)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if $auth.user && loaded}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Page>
|
<Page>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -71,17 +73,17 @@
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
{#if $apps.length}
|
{#if publishedApps.length}
|
||||||
<Heading>Apps</Heading>
|
<Heading>Apps</Heading>
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="S" noPadding>
|
||||||
{#each $apps as app, idx (app.appId)}
|
{#each publishedApps as app, idx (app.appId)}
|
||||||
<a class="app" target="_blank" href={`/${app.appId}`}>
|
<a class="app" target="_blank" href={`/${app.appId}`}>
|
||||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<div class="app-info">
|
<div class="app-info">
|
||||||
<Heading size="XS">{app.name}</Heading>
|
<Heading size="XS">{app.name}</Heading>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Edited {Math.round(Math.random() * 10 + 1)} months ago
|
Updated {Math.round(Math.random() * 10 + 1)} months ago
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
<Icon name="ChevronRight" />
|
<Icon name="ChevronRight" />
|
||||||
|
|
|
@ -13,26 +13,25 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
|
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
|
||||||
import { organisation, auth } from "stores/portal"
|
import { organisation, auth } from "stores/portal"
|
||||||
import BuilderSettingsModal from "components/start/BuilderSettingsModal.svelte"
|
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte"
|
||||||
|
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
|
||||||
|
|
||||||
let oldSettingsModal
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
let userInfoModal
|
||||||
|
let changePasswordModal
|
||||||
|
|
||||||
const menu = [
|
const menu = [
|
||||||
{ title: "Apps", href: "/builder/portal/apps" },
|
{ title: "Apps", href: "/builder/portal/apps" },
|
||||||
{ title: "Drafts", href: "/builder/portal/drafts" },
|
|
||||||
{ title: "Users", href: "/builder/portal/manage/users", heading: "Manage" },
|
{ title: "Users", href: "/builder/portal/manage/users", heading: "Manage" },
|
||||||
{ title: "Groups", href: "/builder/portal/manage/groups" },
|
|
||||||
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
||||||
{ title: "Email", href: "/builder/portal/manage/email" },
|
{ title: "Email", href: "/builder/portal/manage/email" },
|
||||||
{
|
{
|
||||||
title: "General",
|
title: "Organisation",
|
||||||
href: "/builder/portal/settings/general",
|
href: "/builder/portal/settings/organisation",
|
||||||
heading: "Settings",
|
heading: "Settings",
|
||||||
},
|
},
|
||||||
{ title: "Theming", href: "/builder/portal/theming" },
|
{ title: "Theming", href: "/builder/portal/settings/theming" },
|
||||||
{ title: "Account", href: "/builder/portal/account" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -48,7 +47,7 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if $auth.user && loaded}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<Layout paddingX="L" paddingY="L">
|
<Layout paddingX="L" paddingY="L">
|
||||||
|
@ -75,14 +74,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<Search placeholder="Global search" />
|
<div />
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<div slot="control" class="avatar">
|
<div slot="control" class="avatar">
|
||||||
<Avatar size="M" name="John Doe" />
|
<Avatar size="M" name="John Doe" />
|
||||||
<Icon size="XL" name="ChevronDown" />
|
<Icon size="XL" name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="Settings" on:click={oldSettingsModal.show}>
|
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
||||||
Old settings
|
Update user information
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon="LockClosed"
|
||||||
|
on:click={() => changePasswordModal.show()}
|
||||||
|
>
|
||||||
|
Update password
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
|
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
|
||||||
Close developer mode
|
Close developer mode
|
||||||
|
@ -95,8 +100,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Modal bind:this={oldSettingsModal} width="30%">
|
<Modal bind:this={userInfoModal}>
|
||||||
<BuilderSettingsModal />
|
<UpdateUserInfoModal />
|
||||||
|
</Modal>
|
||||||
|
<Modal bind:this={changePasswordModal}>
|
||||||
|
<ChangePasswordModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -26,15 +26,39 @@
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
|
|
||||||
let layout = "grid"
|
let layout = "grid"
|
||||||
let appStatus = AppStatus.PUBLISHED
|
let sortBy = "name"
|
||||||
let template
|
let template
|
||||||
let appToDelete
|
let selectedApp
|
||||||
let creationModal
|
let creationModal
|
||||||
let deletionModal
|
let deletionModal
|
||||||
|
let unpublishModal
|
||||||
let creatingApp = false
|
let creatingApp = false
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
$: appStatus && apps.load(appStatus)
|
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
||||||
|
|
||||||
|
const enrichApps = (apps, user, sortBy) => {
|
||||||
|
const enrichedApps = apps.map(app => ({
|
||||||
|
...app,
|
||||||
|
deployed: app.status === AppStatus.DEPLOYED,
|
||||||
|
lockedYou: app.lockedBy?.email === user.email,
|
||||||
|
lockedOther: app.lockedBy && app.lockedBy.email !== user.email,
|
||||||
|
}))
|
||||||
|
if (sortBy === "status") {
|
||||||
|
return enrichedApps.sort((a, b) => {
|
||||||
|
if (a.status === b.status) {
|
||||||
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1
|
||||||
|
}
|
||||||
|
return a.status === AppStatus.DEPLOYED ? -1 : 1
|
||||||
|
})
|
||||||
|
} else if (sortBy === "name") {
|
||||||
|
return enrichedApps.sort((a, b) => {
|
||||||
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return enrichedApps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const checkKeys = async () => {
|
const checkKeys = async () => {
|
||||||
const response = await api.get(`/api/keys/`)
|
const response = await api.get(`/api/keys/`)
|
||||||
|
@ -60,19 +84,19 @@
|
||||||
creatingApp = false
|
creatingApp = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const openApp = app => {
|
const viewApp = app => {
|
||||||
if (app.lockedBy && app.lockedBy?.email !== $auth.user?.email) {
|
const id = app.deployed ? app.prodId : app.devId
|
||||||
|
window.open(`/${id}`, "_blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
const editApp = app => {
|
||||||
|
if (app.lockedOther) {
|
||||||
notifications.error(
|
notifications.error(
|
||||||
`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
$goto(`../../app/${app.devId}`)
|
||||||
if (appStatus === AppStatus.DEV) {
|
|
||||||
$goto(`../../app/${app.appId}`)
|
|
||||||
} else {
|
|
||||||
window.open(`/${app.appId}`, "_blank")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportApp = app => {
|
const exportApp = app => {
|
||||||
|
@ -82,36 +106,66 @@
|
||||||
app.name
|
app.name
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
notifications.success("App export complete")
|
notifications.success("App exported successfully")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
notifications.error(`Error exporting app: ${err}`)
|
||||||
notifications.error("App export failed")
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpublishApp = app => {
|
||||||
|
selectedApp = app
|
||||||
|
unpublishModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmUnpublishApp = async () => {
|
||||||
|
if (!selectedApp) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await del(`/api/applications/${selectedApp.prodId}`)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
const json = await response.json()
|
||||||
|
throw json.message
|
||||||
|
}
|
||||||
|
await apps.load()
|
||||||
|
notifications.success("App unpublished successfully")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Error unpublishing app: ${err}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteApp = app => {
|
const deleteApp = app => {
|
||||||
appToDelete = app
|
selectedApp = app
|
||||||
deletionModal.show()
|
deletionModal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmDeleteApp = async () => {
|
const confirmDeleteApp = async () => {
|
||||||
if (!appToDelete) {
|
if (!selectedApp) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await del(`/api/applications/${appToDelete?.appId}`)
|
try {
|
||||||
await apps.load()
|
const response = await del(`/api/applications/${selectedApp?.devId}`)
|
||||||
appToDelete = null
|
if (response.status !== 200) {
|
||||||
notifications.success("App deleted successfully.")
|
const json = await response.json()
|
||||||
|
throw json.message
|
||||||
|
}
|
||||||
|
await apps.load()
|
||||||
|
notifications.success("App deleted successfully")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Error deleting app: ${err}`)
|
||||||
|
}
|
||||||
|
selectedApp = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const releaseLock = async appId => {
|
const releaseLock = async app => {
|
||||||
try {
|
try {
|
||||||
const response = await del(`/api/dev/${appId}/lock`)
|
const response = await del(`/api/dev/${app.devId}/lock`)
|
||||||
const json = await response.json()
|
if (response.status !== 200) {
|
||||||
if (response.status !== 200) throw json.message
|
const json = await response.json()
|
||||||
|
throw json.message
|
||||||
notifications.success("Lock released")
|
}
|
||||||
await apps.load(appStatus)
|
await apps.load()
|
||||||
|
notifications.success("Lock released successfully")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error releasing lock: ${err}`)
|
notifications.error(`Error releasing lock: ${err}`)
|
||||||
}
|
}
|
||||||
|
@ -119,66 +173,68 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
checkKeys()
|
checkKeys()
|
||||||
await apps.load(appStatus)
|
await apps.load()
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page wide>
|
<Page wide>
|
||||||
<Layout noPadding>
|
{#if loaded && enrichedApps.length}
|
||||||
<div class="title">
|
<Layout noPadding>
|
||||||
<Heading>Apps</Heading>
|
<div class="title">
|
||||||
<ButtonGroup>
|
<Heading>Apps</Heading>
|
||||||
<Button secondary on:click={initiateAppImport}>Import app</Button>
|
<ButtonGroup>
|
||||||
<Button cta on:click={initiateAppCreation}>Create new app</Button>
|
<Button secondary on:click={initiateAppImport}>Import app</Button>
|
||||||
</ButtonGroup>
|
<Button cta on:click={initiateAppCreation}>Create new app</Button>
|
||||||
</div>
|
</ButtonGroup>
|
||||||
<div class="filter">
|
</div>
|
||||||
<div class="select">
|
<div class="filter">
|
||||||
<Select
|
<div class="select">
|
||||||
bind:value={appStatus}
|
<Select
|
||||||
options={[
|
bind:value={sortBy}
|
||||||
{ label: "Published", value: AppStatus.PUBLISHED },
|
placeholder={null}
|
||||||
{ label: "In Development", value: AppStatus.DEV },
|
options={[
|
||||||
]}
|
{ label: "Sort by name", value: "name" },
|
||||||
/>
|
{ label: "Sort by recently updated", value: "updated" },
|
||||||
|
{ label: "Sort by status", value: "status" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ActionGroup>
|
||||||
|
<ActionButton
|
||||||
|
on:click={() => (layout = "grid")}
|
||||||
|
selected={layout === "grid"}
|
||||||
|
quiet
|
||||||
|
icon="ClassicGridView"
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
on:click={() => (layout = "table")}
|
||||||
|
selected={layout === "table"}
|
||||||
|
quiet
|
||||||
|
icon="ViewRow"
|
||||||
|
/>
|
||||||
|
</ActionGroup>
|
||||||
</div>
|
</div>
|
||||||
<ActionGroup>
|
|
||||||
<ActionButton
|
|
||||||
on:click={() => (layout = "grid")}
|
|
||||||
selected={layout === "grid"}
|
|
||||||
quiet
|
|
||||||
icon="ClassicGridView"
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
on:click={() => (layout = "table")}
|
|
||||||
selected={layout === "table"}
|
|
||||||
quiet
|
|
||||||
icon="ViewRow"
|
|
||||||
/>
|
|
||||||
</ActionGroup>
|
|
||||||
</div>
|
|
||||||
{#if loaded && $apps.length}
|
|
||||||
<div
|
<div
|
||||||
class:appGrid={layout === "grid"}
|
class:appGrid={layout === "grid"}
|
||||||
class:appTable={layout === "table"}
|
class:appTable={layout === "table"}
|
||||||
>
|
>
|
||||||
{#each $apps as app, idx (app.appId)}
|
{#each enrichedApps as app (app.appId)}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={layout === "grid" ? AppCard : AppRow}
|
this={layout === "grid" ? AppCard : AppRow}
|
||||||
deletable={appStatus === AppStatus.PUBLISHED}
|
|
||||||
{releaseLock}
|
{releaseLock}
|
||||||
{app}
|
{app}
|
||||||
{openApp}
|
{unpublishApp}
|
||||||
|
{viewApp}
|
||||||
|
{editApp}
|
||||||
{exportApp}
|
{exportApp}
|
||||||
{deleteApp}
|
{deleteApp}
|
||||||
last={idx === $apps.length - 1}
|
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</Layout>
|
||||||
</Layout>
|
{/if}
|
||||||
{#if !$apps.length && !creatingApp && loaded}
|
{#if !enrichedApps.length && !creatingApp && loaded}
|
||||||
<div class="empty-wrapper">
|
<div class="empty-wrapper">
|
||||||
<Modal inline>
|
<Modal inline>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
@ -215,7 +271,15 @@
|
||||||
okText="Delete app"
|
okText="Delete app"
|
||||||
onOk={confirmDeleteApp}
|
onOk={confirmDeleteApp}
|
||||||
>
|
>
|
||||||
Are you sure you want to delete the app <b>{appToDelete?.name}</b>?
|
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
||||||
|
</ConfirmDialog>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={unpublishModal}
|
||||||
|
title="Confirm unpublish"
|
||||||
|
okText="Unpublish app"
|
||||||
|
onOk={confirmUnpublishApp}
|
||||||
|
>
|
||||||
|
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -228,7 +292,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.select {
|
.select {
|
||||||
width: 150px;
|
width: 190px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appGrid {
|
.appGrid {
|
||||||
|
@ -239,7 +303,7 @@
|
||||||
.appTable {
|
.appTable {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
grid-template-columns: 1fr 1fr 1fr auto;
|
grid-template-columns: 1fr 1fr 1fr 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.appTable :global(> div) {
|
.appTable :global(> div) {
|
||||||
|
@ -253,10 +317,9 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding: 0 var(--spacing-s);
|
padding: 0 var(--spacing-s);
|
||||||
}
|
}
|
||||||
.appTable :global(> div:not(.last)) {
|
.appTable :global(> div) {
|
||||||
border-bottom: var(--border-light);
|
border-bottom: var(--border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-wrapper {
|
.empty-wrapper {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -66,10 +66,11 @@
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="M">General</Heading>
|
<Heading size="M">Organisation</Heading>
|
||||||
<Body>
|
<Body>
|
||||||
General is the place where you edit your organisation name, logo. You can
|
Organisation settings is where you can edit your organisation name and
|
||||||
also configure your platform URL as well as turn on or off analytics.
|
logo. You can also configure your platform URL and enable or disable
|
||||||
|
analytics.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script>
|
||||||
|
import { Layout, Heading, Body, Divider, Label, Select } from "@budibase/bbui"
|
||||||
|
import { themeStore } from "builderStore"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="M">Theming</Heading>
|
||||||
|
<Body>Customize how Budibase looks and feels.</Body>
|
||||||
|
</Layout>
|
||||||
|
<Divider size="S" />
|
||||||
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">Builder theme</Label>
|
||||||
|
<Select
|
||||||
|
options={$themeStore.options}
|
||||||
|
bind:value={$themeStore.theme}
|
||||||
|
placeholder={null}
|
||||||
|
getOptionLabel={capitalise}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fields {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 33% 1fr;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,15 +1,49 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { get } from "builderStore/api"
|
import { get } from "builderStore/api"
|
||||||
|
import { AppStatus } from "../../constants"
|
||||||
|
|
||||||
export function createAppStore() {
|
export function createAppStore() {
|
||||||
const store = writable([])
|
const store = writable([])
|
||||||
|
|
||||||
async function load(status = "") {
|
async function load() {
|
||||||
try {
|
try {
|
||||||
const res = await get(`/api/applications?status=${status}`)
|
const res = await get(`/api/applications?status=all`)
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
if (res.ok && Array.isArray(json)) {
|
if (res.ok && Array.isArray(json)) {
|
||||||
store.set(json)
|
// Merge apps into one sensible list
|
||||||
|
let appMap = {}
|
||||||
|
let devApps = json.filter(app => app.status === AppStatus.DEV)
|
||||||
|
let deployedApps = json.filter(app => app.status === AppStatus.DEPLOYED)
|
||||||
|
|
||||||
|
// First append all dev app version
|
||||||
|
devApps.forEach(app => {
|
||||||
|
const id = app.appId.substring(8)
|
||||||
|
appMap[id] = {
|
||||||
|
...app,
|
||||||
|
devId: app.appId,
|
||||||
|
devRev: app._rev,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Then merge with all prod app versions
|
||||||
|
deployedApps.forEach(app => {
|
||||||
|
const id = app.appId.substring(4)
|
||||||
|
appMap[id] = {
|
||||||
|
...appMap[id],
|
||||||
|
...app,
|
||||||
|
prodId: app.appId,
|
||||||
|
prodRev: app._rev,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Transform into an array and clean up
|
||||||
|
const apps = Object.values(appMap)
|
||||||
|
apps.forEach(app => {
|
||||||
|
app.appId = app.devId.substring(8)
|
||||||
|
delete app._id
|
||||||
|
delete app._rev
|
||||||
|
})
|
||||||
|
store.set(apps)
|
||||||
} else {
|
} else {
|
||||||
store.set([])
|
store.set([])
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ const StaticDatabases = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppStatus = {
|
const AppStatus = {
|
||||||
DEV: "dev",
|
DEV: "development",
|
||||||
ALL: "all",
|
ALL: "all",
|
||||||
DEPLOYED: "PUBLISHED",
|
DEPLOYED: "published",
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
const { Client } = require("pg")
|
const { Pool } = require("pg")
|
||||||
|
|
||||||
|
let pool
|
||||||
|
|
||||||
const SCHEMA = {
|
const SCHEMA = {
|
||||||
docs: "https://node-postgres.com",
|
docs: "https://node-postgres.com",
|
||||||
|
@ -51,31 +53,39 @@ const SCHEMA = {
|
||||||
class PostgresIntegration {
|
class PostgresIntegration {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.client = new Client(config)
|
if (!pool) {
|
||||||
this.connect()
|
pool = new Pool(this.config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async query(sql) {
|
||||||
return this.client.connect()
|
try {
|
||||||
|
this.client = await pool.connect()
|
||||||
|
return await this.client.query(sql)
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(err)
|
||||||
|
} finally {
|
||||||
|
this.client.release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async create({ sql }) {
|
async create({ sql }) {
|
||||||
const response = await this.client.query(sql)
|
const response = await this.query(sql)
|
||||||
return response.rows.length ? response.rows : [{ created: true }]
|
return response.rows.length ? response.rows : [{ created: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async read({ sql }) {
|
async read({ sql }) {
|
||||||
const response = await this.client.query(sql)
|
const response = await this.query(sql)
|
||||||
return response.rows
|
return response.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
async update({ sql }) {
|
async update({ sql }) {
|
||||||
const response = await this.client.query(sql)
|
const response = await this.query(sql)
|
||||||
return response.rows.length ? response.rows : [{ updated: true }]
|
return response.rows.length ? response.rows : [{ updated: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete({ sql }) {
|
async delete({ sql }) {
|
||||||
const response = await this.client.query(sql)
|
const response = await this.query(sql)
|
||||||
return response.rows.length ? response.rows : [{ deleted: true }]
|
return response.rows.length ? response.rows : [{ deleted: true }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue