Merge branch 'master' of github.com:Budibase/budibase into screen-updates
This commit is contained in:
commit
3de238c66a
|
@ -10,7 +10,7 @@
|
||||||
"eslint-plugin-svelte3": "^2.7.3",
|
"eslint-plugin-svelte3": "^2.7.3",
|
||||||
"lerna": "3.14.1",
|
"lerna": "3.14.1",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"prettier-plugin-svelte": "^0.7.0",
|
"prettier-plugin-svelte": "^1.4.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup-plugin-replace": "^2.2.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"svelte": "^3.28.0"
|
"svelte": "^3.28.0"
|
||||||
|
|
|
@ -27,7 +27,6 @@ context("Create a Table", () => {
|
||||||
cy.get(".actions input")
|
cy.get(".actions input")
|
||||||
.first()
|
.first()
|
||||||
.type("updated")
|
.type("updated")
|
||||||
cy.get("select").select("Text")
|
|
||||||
cy.contains("Save Column").click()
|
cy.contains("Save Column").click()
|
||||||
cy.contains("nameupdated").should("have.text", "nameupdated")
|
cy.contains("nameupdated").should("have.text", "nameupdated")
|
||||||
})
|
})
|
||||||
|
|
|
@ -53,6 +53,7 @@ export const getStore = () => {
|
||||||
|
|
||||||
store.saveScreen = saveScreen(store)
|
store.saveScreen = saveScreen(store)
|
||||||
store.setCurrentScreen = setCurrentScreen(store)
|
store.setCurrentScreen = setCurrentScreen(store)
|
||||||
|
store.deleteScreens = deleteScreens(store)
|
||||||
store.setCurrentPage = setCurrentPage(store)
|
store.setCurrentPage = setCurrentPage(store)
|
||||||
store.createScreen = createScreen(store)
|
store.createScreen = createScreen(store)
|
||||||
store.addStylesheet = addStylesheet(store)
|
store.addStylesheet = addStylesheet(store)
|
||||||
|
@ -185,6 +186,26 @@ const setCurrentScreen = store => screenName => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteScreens = store => (screens, pageName = null) => {
|
||||||
|
if (!(screens instanceof Array)) {
|
||||||
|
screens = [screens]
|
||||||
|
}
|
||||||
|
store.update(state => {
|
||||||
|
if (pageName == null) {
|
||||||
|
pageName = state.pages.main.name
|
||||||
|
}
|
||||||
|
for (let screen of screens) {
|
||||||
|
state.screens = state.screens.filter(c => c.name !== screen.name)
|
||||||
|
// Remove screen from current page as well
|
||||||
|
state.pages[pageName]._screens = state.pages[pageName]._screens.filter(
|
||||||
|
scr => scr.name !== screen.name
|
||||||
|
)
|
||||||
|
api.delete(`/_builder/api/pages/${pageName}/screens/${screen.name}`)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const savePage = store => async page => {
|
const savePage = store => async page => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
|
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
|
||||||
|
|
|
@ -23,9 +23,7 @@
|
||||||
class="automation-block hoverable"
|
class="automation-block hoverable"
|
||||||
on:click={addBlockToAutomation}
|
on:click={addBlockToAutomation}
|
||||||
data-cy={stepId}>
|
data-cy={stepId}>
|
||||||
<div>
|
<div><i class={blockDefinition.icon} /></div>
|
||||||
<i class={blockDefinition.icon} />
|
|
||||||
</div>
|
|
||||||
<div class="automation-text">
|
<div class="automation-text">
|
||||||
<h4>{blockDefinition.name}</h4>
|
<h4>{blockDefinition.name}</h4>
|
||||||
<p>{blockDefinition.description}</p>
|
<p>{blockDefinition.description}</p>
|
||||||
|
|
|
@ -62,10 +62,7 @@
|
||||||
{#if $automationStore.selectedBlock}
|
{#if $automationStore.selectedBlock}
|
||||||
<AutomationBlockSetup bind:block={$automationStore.selectedBlock} />
|
<AutomationBlockSetup bind:block={$automationStore.selectedBlock} />
|
||||||
{:else if $automationStore.selectedAutomation}
|
{:else if $automationStore.selectedAutomation}
|
||||||
<div class="block-label">
|
<div class="block-label">Automation <b>{automation.name}</b></div>
|
||||||
Automation
|
|
||||||
<b>{automation.name}</b>
|
|
||||||
</div>
|
|
||||||
<Button secondary wide on:click={testAutomation}>Test Automation</Button>
|
<Button secondary wide on:click={testAutomation}>Test Automation</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -108,7 +108,8 @@
|
||||||
<div
|
<div
|
||||||
class:link={row[header] && row[header].length}
|
class:link={row[header] && row[header].length}
|
||||||
on:click={() => selectRelationship(row, header)}>
|
on:click={() => selectRelationship(row, header)}>
|
||||||
{row[header] ? row[header].length : 0} related row(s)
|
{row[header] ? row[header].length : 0}
|
||||||
|
related row(s)
|
||||||
</div>
|
</div>
|
||||||
{:else if schema[header].type === 'attachment'}
|
{:else if schema[header].type === 'attachment'}
|
||||||
<AttachmentList files={row[header] || []} />
|
<AttachmentList files={row[header] || []} />
|
||||||
|
|
|
@ -49,9 +49,7 @@
|
||||||
<button class:selected={currentPage === 0} on:click={() => selectPage(0)}>
|
<button class:selected={currentPage === 0} on:click={() => selectPage(0)}>
|
||||||
1
|
1
|
||||||
</button>
|
</button>
|
||||||
{#if currentPage > 3}
|
{#if currentPage > 3}<button disabled>...</button>{/if}
|
||||||
<button disabled>...</button>
|
|
||||||
{/if}
|
|
||||||
{#each pagesAroundCurrent as idx}
|
{#each pagesAroundCurrent as idx}
|
||||||
<button
|
<button
|
||||||
class:selected={idx === currentPage}
|
class:selected={idx === currentPage}
|
||||||
|
@ -59,9 +57,7 @@
|
||||||
{idx + 1}
|
{idx + 1}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
{#if currentPage < numPages - 4}
|
{#if currentPage < numPages - 4}<button disabled>...</button>{/if}
|
||||||
<button disabled>...</button>
|
|
||||||
{/if}
|
|
||||||
<button
|
<button
|
||||||
class:selected={currentPage === numPages - 1}
|
class:selected={currentPage === numPages - 1}
|
||||||
on:click={() => selectPage(numPages - 1)}>
|
on:click={() => selectPage(numPages - 1)}>
|
||||||
|
@ -77,8 +73,13 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{#if numPages > 1}
|
{#if numPages > 1}
|
||||||
Showing {ITEMS_PER_PAGE * currentPage + 1} - {ITEMS_PER_PAGE * currentPage + pageItemCount}
|
Showing
|
||||||
of {data.length} rows
|
{ITEMS_PER_PAGE * currentPage + 1}
|
||||||
|
-
|
||||||
|
{ITEMS_PER_PAGE * currentPage + pageItemCount}
|
||||||
|
of
|
||||||
|
{data.length}
|
||||||
|
rows
|
||||||
{:else if numPages === 1}Showing all {data.length} row(s){/if}
|
{:else if numPages === 1}Showing all {data.length} row(s){/if}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -65,16 +65,18 @@
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<Input label="Name" thin bind:value={field.name} />
|
<Input label="Name" thin bind:value={field.name} />
|
||||||
|
|
||||||
<Select
|
{#if !originalName}
|
||||||
secondary
|
<Select
|
||||||
thin
|
secondary
|
||||||
label="Type"
|
thin
|
||||||
on:change={handleFieldConstraints}
|
label="Type"
|
||||||
bind:value={field.type}>
|
on:change={handleFieldConstraints}
|
||||||
{#each Object.values(fieldDefinitions) as field}
|
bind:value={field.type}>
|
||||||
<option value={field.type}>{field.name}</option>
|
{#each Object.values(fieldDefinitions) as field}
|
||||||
{/each}
|
<option value={field.type}>{field.name}</option>
|
||||||
</Select>
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if field.type !== 'link'}
|
{#if field.type !== 'link'}
|
||||||
<Toggle
|
<Toggle
|
||||||
|
|
|
@ -20,10 +20,21 @@
|
||||||
let modal
|
let modal
|
||||||
let name
|
let name
|
||||||
let dataImport
|
let dataImport
|
||||||
|
let error = ""
|
||||||
|
|
||||||
function resetState() {
|
function resetState() {
|
||||||
name = ""
|
name = ""
|
||||||
dataImport = undefined
|
dataImport = undefined
|
||||||
|
error = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValid(evt) {
|
||||||
|
const tableName = evt.target.value
|
||||||
|
if ($backendUiStore.models?.some(model => model.name === tableName)) {
|
||||||
|
error = `Table with name ${tableName} already exists. Please choose another name.`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
error = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveTable() {
|
async function saveTable() {
|
||||||
|
@ -61,12 +72,14 @@
|
||||||
title="Create Table"
|
title="Create Table"
|
||||||
confirmText="Create"
|
confirmText="Create"
|
||||||
onConfirm={saveTable}
|
onConfirm={saveTable}
|
||||||
disabled={!name || (dataImport && !dataImport.valid)}>
|
disabled={error || !name || (dataImport && !dataImport.valid)}>
|
||||||
<Input
|
<Input
|
||||||
data-cy="table-name-input"
|
data-cy="table-name-input"
|
||||||
thin
|
thin
|
||||||
label="Table Name"
|
label="Table Name"
|
||||||
bind:value={name} />
|
on:input={checkValid}
|
||||||
|
bind:value={name}
|
||||||
|
{error} />
|
||||||
<div>
|
<div>
|
||||||
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
||||||
<TableDataImport bind:dataImport />
|
<TableDataImport bind:dataImport />
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore, store } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import screenTemplates from "builderStore/store/screenTemplates"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
|
||||||
export let table
|
export let table
|
||||||
|
|
||||||
|
@ -11,6 +13,10 @@
|
||||||
let dropdown
|
let dropdown
|
||||||
let editing
|
let editing
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
|
let error = ""
|
||||||
|
let originalName = table.name
|
||||||
|
let templateScreens
|
||||||
|
let willBeDeleted
|
||||||
|
|
||||||
$: fields = Object.keys(table.schema)
|
$: fields = Object.keys(table.schema)
|
||||||
|
|
||||||
|
@ -24,12 +30,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
|
const screens = $store.allScreens
|
||||||
|
templateScreens = screens.filter(screen => screen.props.table === table._id)
|
||||||
|
willBeDeleted = ["All table data"].concat(
|
||||||
|
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
||||||
|
)
|
||||||
hideEditor()
|
hideEditor()
|
||||||
confirmDeleteDialog.show()
|
confirmDeleteDialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteTable() {
|
async function deleteTable() {
|
||||||
await backendUiStore.actions.tables.delete(table)
|
await backendUiStore.actions.tables.delete(table)
|
||||||
|
store.deleteScreens(templateScreens)
|
||||||
notifier.success("Table deleted")
|
notifier.success("Table deleted")
|
||||||
hideEditor()
|
hideEditor()
|
||||||
}
|
}
|
||||||
|
@ -39,6 +51,18 @@
|
||||||
notifier.success("Table renamed successfully")
|
notifier.success("Table renamed successfully")
|
||||||
hideEditor()
|
hideEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkValid(evt) {
|
||||||
|
const tableName = evt.target.value
|
||||||
|
if (
|
||||||
|
originalName !== tableName &&
|
||||||
|
$backendUiStore.models?.some(model => model.name === tableName)
|
||||||
|
) {
|
||||||
|
error = `Table with name ${tableName} already exists. Please choose another name.`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
error = ""
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor} class="icon" on:click={dropdown.show}>
|
<div bind:this={anchor} class="icon" on:click={dropdown.show}>
|
||||||
|
@ -48,7 +72,12 @@
|
||||||
{#if editing}
|
{#if editing}
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<h5>Edit Table</h5>
|
<h5>Edit Table</h5>
|
||||||
<Input label="Table Name" thin bind:value={table.name} />
|
<Input
|
||||||
|
label="Table Name"
|
||||||
|
thin
|
||||||
|
bind:value={table.name}
|
||||||
|
on:input={checkValid}
|
||||||
|
{error} />
|
||||||
<Select
|
<Select
|
||||||
label="Primary Display Column"
|
label="Primary Display Column"
|
||||||
thin
|
thin
|
||||||
|
@ -61,7 +90,7 @@
|
||||||
</Select>
|
</Select>
|
||||||
<footer>
|
<footer>
|
||||||
<Button secondary on:click={hideEditor}>Cancel</Button>
|
<Button secondary on:click={hideEditor}>Cancel</Button>
|
||||||
<Button primary on:click={save}>Save</Button>
|
<Button primary disabled={error} on:click={save}>Save</Button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -79,10 +108,21 @@
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={confirmDeleteDialog}
|
bind:this={confirmDeleteDialog}
|
||||||
body={`Are you sure you wish to delete the table '${table.name}'? Your data will be deleted and this action cannot be undone.`}
|
|
||||||
okText="Delete Table"
|
okText="Delete Table"
|
||||||
onOk={deleteTable}
|
onOk={deleteTable}
|
||||||
title="Confirm Delete" />
|
title="Confirm Delete">
|
||||||
|
Are you sure you wish to delete the table
|
||||||
|
<i>{table.name}?</i>
|
||||||
|
The following will also be deleted:
|
||||||
|
<b>
|
||||||
|
<div class="delete-items">
|
||||||
|
{#each willBeDeleted as item}
|
||||||
|
<div>{item}</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</b>
|
||||||
|
This action cannot be undone.
|
||||||
|
</ConfirmDialog>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div.icon {
|
div.icon {
|
||||||
|
@ -96,6 +136,17 @@
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.delete-items {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.delete-items div {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
transition: 0.2s ease transform, 0.2s ease background-color,
|
transition: 0.2s ease transform, 0.2s ease background-color,
|
||||||
0.2s ease box-shadow;
|
0.2s ease box-shadow;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 1;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,10 @@
|
||||||
|
|
||||||
<Modal bind:this={modal} on:hide={onCancel}>
|
<Modal bind:this={modal} on:hide={onCancel}>
|
||||||
<ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red>
|
<ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red>
|
||||||
<div class="body">{body}</div>
|
<div class="body">
|
||||||
|
{body}
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,7 @@
|
||||||
style="background: {themes[notification.type]};"
|
style="background: {themes[notification.type]};"
|
||||||
transition:fly={{ y: -30 }}>
|
transition:fly={{ y: -30 }}>
|
||||||
<div class="content">{notification.message}</div>
|
<div class="content">{notification.message}</div>
|
||||||
{#if notification.icon}
|
{#if notification.icon}<i class={notification.icon} />{/if}
|
||||||
<i class={notification.icon} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,15 +12,12 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
|
||||||
<div class="switcher">
|
<div class="switcher">
|
||||||
|
|
||||||
{#each tabs as tab}
|
{#each tabs as tab}
|
||||||
<button class:selected={selected === tab} on:click={() => selectTab(tab)}>
|
<button class:selected={selected === tab} on:click={() => selectTab(tab)}>
|
||||||
{tab}
|
{tab}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
|
@ -34,7 +31,6 @@
|
||||||
<slot name="3" />
|
<slot name="3" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="img">
|
<div class="img"><img src="https://picsum.photos/60/60" alt="zoom" /></div>
|
||||||
<img src="https://picsum.photos/60/60" alt="zoom" />
|
|
||||||
</div>
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="title">Zoom</div>
|
<div class="title">Zoom</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
||||||
<Input
|
<Input
|
||||||
thin
|
thin
|
||||||
|
type="password"
|
||||||
bind:value={password}
|
bind:value={password}
|
||||||
name="Password"
|
name="Password"
|
||||||
placeholder="Password" />
|
placeholder="Password" />
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
<Spacer medium />
|
<Spacer medium />
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<TextButton text medium blue href="/_builder/{_id}">
|
<TextButton text medium blue href="/_builder/{_id}">
|
||||||
Open {name} →
|
Open
|
||||||
|
{name}
|
||||||
|
→
|
||||||
</TextButton>
|
</TextButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,12 +28,11 @@
|
||||||
<Spacer small />
|
<Spacer small />
|
||||||
<Body medium grey>{template.category}</Body>
|
<Body medium grey>{template.category}</Body>
|
||||||
<Body lh small black>{template.description}</Body>
|
<Body lh small black>{template.description}</Body>
|
||||||
<div>
|
<div><img src={template.image} width="100%" /></div>
|
||||||
<img src={template.image} width="100%" />
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<Button secondary on:click={() => onSelect(template)}>
|
<Button secondary on:click={() => onSelect(template)}>
|
||||||
Create {template.name}
|
Create
|
||||||
|
{template.name}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
{category.name}
|
{category.name}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -99,9 +99,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor} on:click|stopPropagation={() => {}}>
|
<div bind:this={anchor} on:click|stopPropagation={() => {}}>
|
||||||
<div class="icon" on:click={dropdown.show}>
|
<div class="icon" on:click={dropdown.show}><i class="ri-more-line" /></div>
|
||||||
<i class="ri-more-line" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
bind:this={dropdown}
|
bind:this={dropdown}
|
||||||
|
|
|
@ -1,21 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { setContext, onMount } from "svelte"
|
|
||||||
|
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import {
|
|
||||||
LayoutIcon,
|
|
||||||
PaintIcon,
|
|
||||||
TerminalIcon,
|
|
||||||
CircleIndicator,
|
|
||||||
EventsIcon,
|
|
||||||
} from "components/common/Icons/"
|
|
||||||
import panelStructure from "./temporaryPanelStructure.js"
|
import panelStructure from "./temporaryPanelStructure.js"
|
||||||
import CategoryTab from "./CategoryTab.svelte"
|
import CategoryTab from "./CategoryTab.svelte"
|
||||||
import DesignView from "./DesignView.svelte"
|
import DesignView from "./DesignView.svelte"
|
||||||
import SettingsView from "./SettingsView.svelte"
|
import SettingsView from "./SettingsView.svelte"
|
||||||
|
|
||||||
let current_view = "design"
|
|
||||||
let codeEditor
|
|
||||||
let flattenedPanel = flattenComponents(panelStructure.categories)
|
let flattenedPanel = flattenComponents(panelStructure.categories)
|
||||||
let categories = [
|
let categories = [
|
||||||
{ value: "settings", name: "Settings" },
|
{ value: "settings", name: "Settings" },
|
||||||
|
@ -23,7 +12,6 @@
|
||||||
]
|
]
|
||||||
let selectedCategory = categories[0]
|
let selectedCategory = categories[0]
|
||||||
|
|
||||||
$: components = $store.components
|
|
||||||
$: componentInstance =
|
$: componentInstance =
|
||||||
$store.currentView !== "component"
|
$store.currentView !== "component"
|
||||||
? { ...$store.currentPreviewItem, ...$store.currentComponentInfo }
|
? { ...$store.currentPreviewItem, ...$store.currentComponentInfo }
|
||||||
|
@ -76,7 +64,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
|
||||||
<CategoryTab
|
<CategoryTab
|
||||||
onClick={category => (selectedCategory = category)}
|
onClick={category => (selectedCategory = category)}
|
||||||
{categories}
|
{categories}
|
||||||
|
@ -99,9 +86,7 @@
|
||||||
onScreenPropChange={store.setPageOrScreenProp}
|
onScreenPropChange={store.setPageOrScreenProp}
|
||||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,33 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
import { store } from "builderStore"
|
||||||
import components from "./temporaryPanelStructure.js"
|
import components from "./temporaryPanelStructure.js"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
|
||||||
import CategoryTab from "./CategoryTab.svelte"
|
import CategoryTab from "./CategoryTab.svelte"
|
||||||
import {
|
|
||||||
find,
|
|
||||||
sortBy,
|
|
||||||
groupBy,
|
|
||||||
values,
|
|
||||||
filter,
|
|
||||||
map,
|
|
||||||
uniqBy,
|
|
||||||
flatten,
|
|
||||||
} from "lodash/fp"
|
|
||||||
|
|
||||||
import { pipe } from "components/common/core"
|
|
||||||
|
|
||||||
import Tab from "./ItemTab/Tab.svelte"
|
import Tab from "./ItemTab/Tab.svelte"
|
||||||
import { store } from "builderStore"
|
|
||||||
|
|
||||||
export let toggleTab
|
export let toggleTab
|
||||||
|
|
||||||
let selectTemplateDialog
|
|
||||||
let selectedTemplateInstance
|
|
||||||
let templateInstances = []
|
|
||||||
|
|
||||||
let selectedComponent = null
|
|
||||||
|
|
||||||
const categories = components.categories
|
const categories = components.categories
|
||||||
let selectedCategory = categories[0]
|
let selectedCategory = categories[0]
|
||||||
|
|
||||||
|
@ -45,7 +25,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
|
||||||
<CategoryTab
|
<CategoryTab
|
||||||
onClick={category => (selectedCategory = category)}
|
onClick={category => (selectedCategory = category)}
|
||||||
{selectedCategory}
|
{selectedCategory}
|
||||||
|
|
|
@ -38,13 +38,11 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
|
||||||
{#each screens as screen}
|
{#each screens as screen}
|
||||||
<div
|
<div
|
||||||
class="budibase__nav-item screen-header-row"
|
class="budibase__nav-item screen-header-row"
|
||||||
class:selected={$store.currentComponentInfo._id === screen.props._id}
|
class:selected={$store.currentComponentInfo._id === screen.props._id}
|
||||||
on:click|stopPropagation={() => changeScreen(screen)}>
|
on:click|stopPropagation={() => changeScreen(screen)}>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
class:rotate={$store.currentPreviewItem.name !== screen.props._instanceName}>
|
class:rotate={$store.currentPreviewItem.name !== screen.props._instanceName}>
|
||||||
|
@ -69,7 +67,6 @@
|
||||||
{dragDropStore} />
|
{dragDropStore} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -129,7 +129,6 @@
|
||||||
<ul>
|
<ul>
|
||||||
{#each components as component, index (component._id)}
|
{#each components as component, index (component._id)}
|
||||||
<li on:click|stopPropagation={() => selectComponent(component)}>
|
<li on:click|stopPropagation={() => selectComponent(component)}>
|
||||||
|
|
||||||
{#if $dragDropStore && $dragDropStore.targetComponent === component && $dragDropStore.dropPosition === 'above'}
|
{#if $dragDropStore && $dragDropStore.targetComponent === component && $dragDropStore.dropPosition === 'above'}
|
||||||
<div
|
<div
|
||||||
on:drop={drop}
|
on:drop={drop}
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#if $store.currentFrontEndType === 'page' || $store.screens.length}
|
{#if $store.currentFrontEndType === 'page' || $store.screens.length}
|
||||||
<div class="switcher">
|
<div class="switcher">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class:selected={selected === COMPONENT_SELECTION_TAB}
|
class:selected={selected === COMPONENT_SELECTION_TAB}
|
||||||
on:click={() => selectTab(COMPONENT_SELECTION_TAB)}>
|
on:click={() => selectTab(COMPONENT_SELECTION_TAB)}>
|
||||||
|
@ -32,7 +31,6 @@
|
||||||
on:click={() => selectTab(PROPERTIES_TAB)}>
|
on:click={() => selectTab(PROPERTIES_TAB)}>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
|
@ -43,10 +41,8 @@
|
||||||
{#if selected === COMPONENT_SELECTION_TAB}
|
{#if selected === COMPONENT_SELECTION_TAB}
|
||||||
<ComponentSelectionList {toggleTab} />
|
<ComponentSelectionList {toggleTab} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="design-view-container">
|
<div class="design-view-container">
|
||||||
|
|
||||||
<div class="design-view-state-categories">
|
<div class="design-view-state-categories">
|
||||||
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
|
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,9 +18,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="handler-option">
|
<div class="handler-option">
|
||||||
{#if parameter.name === 'automation'}
|
{#if parameter.name === 'automation'}<span>{parameter.name}</span>{/if}
|
||||||
<span>{parameter.name}</span>
|
|
||||||
{/if}
|
|
||||||
{#if parameter.name === 'automation'}
|
{#if parameter.name === 'automation'}
|
||||||
<Select on:change bind:value={parameter.value}>
|
<Select on:change bind:value={parameter.value}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
|
|
|
@ -48,7 +48,6 @@
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
on:fieldschanged={onFieldsChanged} />
|
on:fieldschanged={onFieldsChanged} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -109,7 +109,6 @@
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
on:fieldschanged={onFieldsChanged} />
|
on:fieldschanged={onFieldsChanged} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -107,7 +107,6 @@
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
on:fieldschanged={onFieldsChanged} />
|
on:fieldschanged={onFieldsChanged} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -126,9 +126,7 @@
|
||||||
on:click={() => switchLetter(letter)}>
|
on:click={() => switchLetter(letter)}>
|
||||||
{letter}
|
{letter}
|
||||||
</span>
|
</span>
|
||||||
{#if idx !== alphabet.length - 1}
|
{#if idx !== alphabet.length - 1}<span>-</span>{/if}
|
||||||
<span>-</span>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="search-input">
|
<div class="search-input">
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div data-cy={item.name} class="item-item" on:click>
|
<div data-cy={item.name} class="item-item" on:click>
|
||||||
<div class="item-icon">
|
<div class="item-icon"><i class={item.icon} /></div>
|
||||||
<i class={item.icon} />
|
|
||||||
</div>
|
|
||||||
<div class="item-text">
|
<div class="item-text">
|
||||||
<div class="item-name">{item.name}</div>
|
<div class="item-name">{item.name}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import Item from "./Item.svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
import Item from "./Item.svelte"
|
|
||||||
export let list
|
export let list
|
||||||
|
|
||||||
let category = list
|
let category = list
|
||||||
|
@ -21,10 +23,12 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !list.isCategory}
|
{#if !list.isCategory}
|
||||||
<button class="back-button" on:click={() => (list = category)}>Back</button>
|
<button class="back-button" on:click={goBack}>Back</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#each list.children as item}
|
{#each list.children as item}
|
||||||
<Item {item} on:click={() => handleClick(item)} />
|
{#if !item.showOnPages || item.showOnPages.includes($store.currentPageName)}
|
||||||
|
<Item {item} on:click={() => handleClick(item)} />
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -89,7 +89,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label="Choose a Template"
|
label="Choose a Template"
|
||||||
bind:value={templateIndex}
|
bind:value={templateIndex}
|
||||||
|
@ -109,5 +108,4 @@
|
||||||
error={routeError}
|
error={routeError}
|
||||||
bind:value={route}
|
bind:value={route}
|
||||||
on:change={routeChanged} />
|
on:change={routeChanged} />
|
||||||
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -17,26 +17,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteScreen = () => {
|
const deleteScreen = () => {
|
||||||
|
store.deleteScreens(screen, $store.currentPageName)
|
||||||
|
// update the page if required
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
// Remove screen from screens
|
|
||||||
const screens = state.screens.filter(c => c.name !== screen.name)
|
|
||||||
state.screens = screens
|
|
||||||
|
|
||||||
// Remove screen from current page as well
|
|
||||||
const pageScreens = state.pages[state.currentPageName]._screens.filter(
|
|
||||||
scr => scr.name !== screen.name
|
|
||||||
)
|
|
||||||
state.pages[state.currentPageName]._screens = pageScreens
|
|
||||||
|
|
||||||
if (state.currentPreviewItem.name === screen.name) {
|
if (state.currentPreviewItem.name === screen.name) {
|
||||||
store.setCurrentPage($store.currentPageName)
|
store.setCurrentPage($store.currentPageName)
|
||||||
$goto(`./:page/page-layout`)
|
$goto(`./:page/page-layout`)
|
||||||
}
|
}
|
||||||
|
|
||||||
api.delete(
|
|
||||||
`/_builder/api/pages/${state.currentPageName}/screens/${screen.name}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1165,14 +1165,6 @@ export default {
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// name: "Map",
|
|
||||||
// _component: "@budibase/standard-components/datamap",
|
|
||||||
// description: "Shiny map",
|
|
||||||
// icon: "ri-map-pin-line",
|
|
||||||
// properties: { design: { ...all } },
|
|
||||||
// children: [],
|
|
||||||
// },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1208,6 +1200,7 @@ export default {
|
||||||
"A component that automatically generates a login screen for your app.",
|
"A component that automatically generates a login screen for your app.",
|
||||||
icon: "ri-login-box-line",
|
icon: "ri-login-box-line",
|
||||||
children: [],
|
children: [],
|
||||||
|
showOnPages: ["unauthenticated"],
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [
|
settings: [
|
||||||
|
|
|
@ -68,7 +68,10 @@
|
||||||
<span
|
<span
|
||||||
class:active={false}
|
class:active={false}
|
||||||
class="topnavitemright"
|
class="topnavitemright"
|
||||||
on:click={() => window.open(`/${application}`)}>
|
on:click={() => {
|
||||||
|
document.cookie = 'budibase:token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'
|
||||||
|
window.open(`/${application}`)
|
||||||
|
}}>
|
||||||
<PreviewIcon />
|
<PreviewIcon />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,7 +80,7 @@
|
||||||
{#await promise}
|
{#await promise}
|
||||||
<!-- This should probably be some kind of loading state? -->
|
<!-- This should probably be some kind of loading state? -->
|
||||||
<div />
|
<div />
|
||||||
{:then _}
|
{:then results}
|
||||||
<slot />
|
<slot />
|
||||||
{:catch error}
|
{:catch error}
|
||||||
<p>Something went wrong: {error.message}</p>
|
<p>Something went wrong: {error.message}</p>
|
||||||
|
|
|
@ -7,9 +7,7 @@
|
||||||
|
|
||||||
{#if $backendUiStore.selectedDatabase._id && selectedTable.name}
|
{#if $backendUiStore.selectedDatabase._id && selectedTable.name}
|
||||||
<TableDataTable />
|
<TableDataTable />
|
||||||
{:else}
|
{:else}<i>Create your first table to start building</i>{/if}
|
||||||
<i>Create your first table to start building</i>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
i {
|
i {
|
||||||
|
|
|
@ -23,9 +23,7 @@
|
||||||
|
|
||||||
{#if $backendUiStore.tables.length === 0}
|
{#if $backendUiStore.tables.length === 0}
|
||||||
<i>Create your first table to start building</i>
|
<i>Create your first table to start building</i>
|
||||||
{:else}
|
{:else}<i>Select a table to edit</i>{/if}
|
||||||
<i>Select a table to edit</i>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
i {
|
i {
|
||||||
|
|
|
@ -7,9 +7,7 @@
|
||||||
|
|
||||||
{#if $backendUiStore.selectedDatabase._id && selectedView}
|
{#if $backendUiStore.selectedDatabase._id && selectedView}
|
||||||
<ViewDataTable view={selectedView} />
|
<ViewDataTable view={selectedView} />
|
||||||
{:else}
|
{:else}<i>Create your first table to start building</i>{/if}
|
||||||
<i>Create your first table to start building</i>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
i {
|
i {
|
||||||
|
|
|
@ -3,7 +3,7 @@ const ClientDb = require("../../db/clientDb")
|
||||||
const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
|
const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const instanceController = require("./instance")
|
const instanceController = require("./instance")
|
||||||
const { copy, exists, readFile, writeFile } = require("fs-extra")
|
const { copy, existsSync, readFile, writeFile } = require("fs-extra")
|
||||||
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
||||||
const sqrl = require("squirrelly")
|
const sqrl = require("squirrelly")
|
||||||
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
||||||
|
@ -116,6 +116,12 @@ exports.delete = async function(ctx) {
|
||||||
const db = new CouchDB(ClientDb.name(getClientId(ctx)))
|
const db = new CouchDB(ClientDb.name(getClientId(ctx)))
|
||||||
const app = await db.get(ctx.params.applicationId)
|
const app = await db.get(ctx.params.applicationId)
|
||||||
const result = await db.remove(app)
|
const result = await db.remove(app)
|
||||||
|
for (let instance of app.instances) {
|
||||||
|
const instanceDb = new CouchDB(instance._id)
|
||||||
|
await instanceDb.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove top level directory
|
||||||
await fs.rmdir(join(budibaseAppsDir(), ctx.params.applicationId), {
|
await fs.rmdir(join(budibaseAppsDir(), ctx.params.applicationId), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
})
|
})
|
||||||
|
@ -137,7 +143,7 @@ const createEmptyAppPackage = async (ctx, app) => {
|
||||||
const appsFolder = budibaseAppsDir()
|
const appsFolder = budibaseAppsDir()
|
||||||
const newAppFolder = resolve(appsFolder, app._id)
|
const newAppFolder = resolve(appsFolder, app._id)
|
||||||
|
|
||||||
if (await exists(newAppFolder)) {
|
if (existsSync(newAppFolder)) {
|
||||||
ctx.throw(400, "App folder already exists for this application")
|
ctx.throw(400, "App folder already exists for this application")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ exports.authenticate = async ctx => {
|
||||||
dbUser = await instanceDb.get(generateUserID(username))
|
dbUser = await instanceDb.get(generateUserID(username))
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// do not want to throw a 404 - as this could be
|
// do not want to throw a 404 - as this could be
|
||||||
// used to dtermine valid usernames
|
// used to determine valid usernames
|
||||||
ctx.throw(401, "Invalid Credentials")
|
ctx.throw(401, "Invalid Credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,12 @@ exports.save = async function(ctx) {
|
||||||
let row = ctx.request.body
|
let row = ctx.request.body
|
||||||
row.tableId = ctx.params.tableId
|
row.tableId = ctx.params.tableId
|
||||||
|
|
||||||
|
if (ctx.request.body.type === "delete") {
|
||||||
|
await bulkDelete(ctx)
|
||||||
|
ctx.body = ctx.request.body.rows
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!row._rev && !row._id) {
|
if (!row._rev && !row._id) {
|
||||||
row._id = generateRowID(row.tableId)
|
row._id = generateRowID(row.tableId)
|
||||||
}
|
}
|
||||||
|
@ -348,3 +354,25 @@ const TYPE_TRANSFORM_MAP = {
|
||||||
false: false,
|
false: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function bulkDelete(ctx) {
|
||||||
|
const instanceId = ctx.user.instanceId
|
||||||
|
const { rows } = ctx.request.body
|
||||||
|
const db = new CouchDB(instanceId)
|
||||||
|
|
||||||
|
const linkUpdates = rows.map(row =>
|
||||||
|
linkRows.updateLinks({
|
||||||
|
instanceId,
|
||||||
|
eventType: linkRows.EventType.ROW_DELETE,
|
||||||
|
row,
|
||||||
|
tableId: row.tableId,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true })))
|
||||||
|
await Promise.all(linkUpdates)
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, instanceId, row)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* This controller is not currently fully implemented. Screens are
|
||||||
|
* currently managed as part of the pages API, please look in api/routes/page.js
|
||||||
|
* for routes and controllers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.fetch = async ctx => {
|
||||||
|
ctx.throw(501)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.save = async ctx => {
|
||||||
|
ctx.throw(501)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.destroy = async ctx => {
|
||||||
|
ctx.throw(501)
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
const send = require("koa-send")
|
const send = require("koa-send")
|
||||||
const { resolve, join } = require("../../utilities/centralPath")
|
const { resolve, join } = require("../../utilities/centralPath")
|
||||||
const jwt = require("jsonwebtoken")
|
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const fs = require("fs-extra")
|
const fs = require("fs-extra")
|
||||||
const uuid = require("uuid")
|
const uuid = require("uuid")
|
||||||
|
@ -13,8 +12,8 @@ const {
|
||||||
} = require("../../utilities/budibaseDir")
|
} = require("../../utilities/budibaseDir")
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
||||||
const { ANON_LEVEL_ID } = require("../../utilities/accessLevels")
|
|
||||||
const fileProcessor = require("../../utilities/fileProcessor")
|
const fileProcessor = require("../../utilities/fileProcessor")
|
||||||
|
const { AuthTypes } = require("../../constants")
|
||||||
|
|
||||||
exports.serveBuilder = async function(ctx) {
|
exports.serveBuilder = async function(ctx) {
|
||||||
let builderPath = resolve(__dirname, "../../../builder")
|
let builderPath = resolve(__dirname, "../../../builder")
|
||||||
|
@ -136,7 +135,8 @@ exports.performLocalFileProcessing = async function(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.serveApp = async function(ctx) {
|
exports.serveApp = async function(ctx) {
|
||||||
const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
|
const mainOrAuth =
|
||||||
|
ctx.auth.authenticated === AuthTypes.APP ? "main" : "unauthenticated"
|
||||||
|
|
||||||
// default to homedir
|
// default to homedir
|
||||||
const appPath = resolve(
|
const appPath = resolve(
|
||||||
|
@ -146,26 +146,7 @@ exports.serveApp = async function(ctx) {
|
||||||
mainOrAuth
|
mainOrAuth
|
||||||
)
|
)
|
||||||
|
|
||||||
let appId = ctx.params.appId
|
const appId = ctx.user.appId
|
||||||
if (process.env.CLOUD) {
|
|
||||||
appId = ctx.subdomains[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// only set the appId cookie for /appId .. we COULD check for valid appIds
|
|
||||||
// but would like to avoid that DB hit
|
|
||||||
const looksLikeAppId = /^(app_)?[0-9a-f]{32}$/.test(appId)
|
|
||||||
if (looksLikeAppId && !ctx.auth.authenticated) {
|
|
||||||
const anonUser = {
|
|
||||||
userId: "ANON",
|
|
||||||
accessLevelId: ANON_LEVEL_ID,
|
|
||||||
appId,
|
|
||||||
}
|
|
||||||
const anonToken = jwt.sign(anonUser, ctx.config.jwtSecret)
|
|
||||||
ctx.cookies.set("budibase:token", anonToken, {
|
|
||||||
path: "/",
|
|
||||||
httpOnly: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.CLOUD) {
|
if (process.env.CLOUD) {
|
||||||
const S3_URL = `https://${appId}.app.budi.live/assets/${appId}/${mainOrAuth}/${ctx.file ||
|
const S3_URL = `https://${appId}.app.budi.live/assets/${appId}/${mainOrAuth}/${ctx.file ||
|
||||||
|
@ -200,7 +181,8 @@ exports.serveAttachment = async function(ctx) {
|
||||||
|
|
||||||
exports.serveAppAsset = async function(ctx) {
|
exports.serveAppAsset = async function(ctx) {
|
||||||
// default to homedir
|
// default to homedir
|
||||||
const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
|
const mainOrAuth =
|
||||||
|
ctx.auth.authenticated === AuthTypes.APP ? "main" : "unauthenticated"
|
||||||
|
|
||||||
const appPath = resolve(
|
const appPath = resolve(
|
||||||
budibaseAppsDir(),
|
budibaseAppsDir(),
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
const AuthTypes = {
|
||||||
|
APP: "app",
|
||||||
|
BUILDER: "builder",
|
||||||
|
EXTERNAL: "external",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.AuthTypes = AuthTypes
|
|
@ -7,6 +7,8 @@ const {
|
||||||
BUILDER_LEVEL_ID,
|
BUILDER_LEVEL_ID,
|
||||||
ANON_LEVEL_ID,
|
ANON_LEVEL_ID,
|
||||||
} = require("../utilities/accessLevels")
|
} = require("../utilities/accessLevels")
|
||||||
|
const environment = require("../environment")
|
||||||
|
const { AuthTypes } = require("../constants")
|
||||||
|
|
||||||
module.exports = async (ctx, next) => {
|
module.exports = async (ctx, next) => {
|
||||||
if (ctx.path === "/_builder") {
|
if (ctx.path === "/_builder") {
|
||||||
|
@ -17,36 +19,32 @@ module.exports = async (ctx, next) => {
|
||||||
const appToken = ctx.cookies.get("budibase:token")
|
const appToken = ctx.cookies.get("budibase:token")
|
||||||
const builderToken = ctx.cookies.get("builder:token")
|
const builderToken = ctx.cookies.get("builder:token")
|
||||||
|
|
||||||
if (builderToken) {
|
let token
|
||||||
try {
|
// if running locally in the builder itself
|
||||||
const jwtPayload = jwt.verify(builderToken, ctx.config.jwtSecret)
|
if (!environment.CLOUD && !appToken) {
|
||||||
ctx.auth = {
|
token = builderToken
|
||||||
apiKey: jwtPayload.apiKey,
|
ctx.auth.authenticated = AuthTypes.BUILDER
|
||||||
authenticated: jwtPayload.accessLevelId === BUILDER_LEVEL_ID,
|
} else {
|
||||||
}
|
token = appToken
|
||||||
ctx.user = {
|
ctx.auth.authenticated = AuthTypes.APP
|
||||||
...jwtPayload,
|
|
||||||
accessLevel: await getAccessLevel(
|
|
||||||
jwtPayload.instanceId,
|
|
||||||
jwtPayload.accessLevelId
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
// empty: do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
await next()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appToken) {
|
if (!token) {
|
||||||
ctx.auth.authenticated = false
|
ctx.auth.authenticated = false
|
||||||
|
|
||||||
|
const appId = process.env.CLOUD ? ctx.subdomains[1] : ctx.params.appId
|
||||||
|
|
||||||
|
ctx.user = {
|
||||||
|
// if appId can't be determined from path param or subdomain
|
||||||
|
appId: appId || ctx.referer.split("/").pop(),
|
||||||
|
}
|
||||||
await next()
|
await next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jwtPayload = jwt.verify(appToken, ctx.config.jwtSecret)
|
const jwtPayload = jwt.verify(token, ctx.config.jwtSecret)
|
||||||
|
ctx.auth.apiKey = jwtPayload.apiKey
|
||||||
ctx.user = {
|
ctx.user = {
|
||||||
...jwtPayload,
|
...jwtPayload,
|
||||||
accessLevel: await getAccessLevel(
|
accessLevel: await getAccessLevel(
|
||||||
|
@ -54,10 +52,6 @@ module.exports = async (ctx, next) => {
|
||||||
jwtPayload.accessLevelId
|
jwtPayload.accessLevelId
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
ctx.auth = {
|
|
||||||
authenticated: ctx.user.accessLevelId !== ANON_LEVEL_ID,
|
|
||||||
apiKey: jwtPayload.apiKey,
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
|
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ const {
|
||||||
} = require("../utilities/accessLevels")
|
} = require("../utilities/accessLevels")
|
||||||
const environment = require("../environment")
|
const environment = require("../environment")
|
||||||
const { apiKeyTable } = require("../db/dynamoClient")
|
const { apiKeyTable } = require("../db/dynamoClient")
|
||||||
|
const { AuthTypes } = require("../constants")
|
||||||
|
|
||||||
module.exports = (permName, getItemId) => async (ctx, next) => {
|
module.exports = (permName, getItemId) => async (ctx, next) => {
|
||||||
if (
|
if (
|
||||||
|
@ -21,8 +22,7 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
||||||
|
|
||||||
if (apiKeyInfo) {
|
if (apiKeyInfo) {
|
||||||
ctx.auth = {
|
ctx.auth = {
|
||||||
authenticated: true,
|
authenticated: AuthTypes.EXTERNAL,
|
||||||
external: true,
|
|
||||||
apiKey: ctx.headers["x-api-key"],
|
apiKey: ctx.headers["x-api-key"],
|
||||||
}
|
}
|
||||||
ctx.user = {
|
ctx.user = {
|
||||||
|
@ -34,6 +34,9 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
||||||
ctx.throw(403, "API key invalid")
|
ctx.throw(403, "API key invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't expose builder endpoints in the cloud
|
||||||
|
if (environment.CLOUD && permName === BUILDER) return
|
||||||
|
|
||||||
if (!ctx.auth.authenticated) {
|
if (!ctx.auth.authenticated) {
|
||||||
ctx.throw(403, "Session not authenticated")
|
ctx.throw(403, "Session not authenticated")
|
||||||
}
|
}
|
||||||
|
@ -42,6 +45,10 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
||||||
ctx.throw(403, "User not found")
|
ctx.throw(403, "User not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx.user.accessLevel._id === BUILDER_LEVEL_ID) {
|
if (ctx.user.accessLevel._id === BUILDER_LEVEL_ID) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
@ -53,10 +60,6 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
||||||
|
|
||||||
const permissionId = ({ name, itemId }) => name + (itemId ? `-${itemId}` : "")
|
const permissionId = ({ name, itemId }) => name + (itemId ? `-${itemId}` : "")
|
||||||
|
|
||||||
if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
const thisPermissionId = permissionId({
|
const thisPermissionId = permissionId({
|
||||||
name: permName,
|
name: permName,
|
||||||
itemId: getItemId && getItemId(ctx),
|
itemId: getItemId && getItemId(ctx),
|
||||||
|
|
|
@ -21,7 +21,6 @@ module.exports.PRETTY_ACCESS_LEVELS = {
|
||||||
[module.exports.ADMIN_LEVEL_ID]: "Admin",
|
[module.exports.ADMIN_LEVEL_ID]: "Admin",
|
||||||
[module.exports.POWERUSER_LEVEL_ID]: "Power user",
|
[module.exports.POWERUSER_LEVEL_ID]: "Power user",
|
||||||
[module.exports.BUILDER_LEVEL_ID]: "Builder",
|
[module.exports.BUILDER_LEVEL_ID]: "Builder",
|
||||||
[module.exports.ANON_LEVEL_ID]: "Anonymous",
|
|
||||||
}
|
}
|
||||||
module.exports.adminPermissions = [
|
module.exports.adminPermissions = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,7 @@ const VALIDATORS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const PARSERS = {
|
const PARSERS = {
|
||||||
|
number: attribute => Number(attribute),
|
||||||
datetime: attribute => new Date(attribute).toISOString(),
|
datetime: attribute => new Date(attribute).toISOString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ function parse(path, parsers) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
result.fromFile(path).subscribe(row => {
|
result.subscribe(row => {
|
||||||
// For each CSV row parse all the columns that need parsed
|
// For each CSV row parse all the columns that need parsed
|
||||||
for (let key in parsers) {
|
for (let key in parsers) {
|
||||||
if (!schema[key] || schema[key].success) {
|
if (!schema[key] || schema[key].success) {
|
||||||
|
|
|
@ -10,7 +10,6 @@ async function processImage(file) {
|
||||||
const imgMeta = await sharp(file.path)
|
const imgMeta = await sharp(file.path)
|
||||||
.resize(300)
|
.resize(300)
|
||||||
.toFile(file.outputPath)
|
.toFile(file.outputPath)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...file,
|
...file,
|
||||||
...imgMeta,
|
...imgMeta,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { exists, readFile, writeFile, ensureDir } = require("fs-extra")
|
const { existsSync, readFile, writeFile, ensureDir } = require("fs-extra")
|
||||||
const { join, resolve } = require("./centralPath")
|
const { join, resolve } = require("./centralPath")
|
||||||
const Sqrl = require("squirrelly")
|
const Sqrl = require("squirrelly")
|
||||||
const uuid = require("uuid")
|
const uuid = require("uuid")
|
||||||
|
@ -28,7 +28,7 @@ const setCouchDbUrl = async opts => {
|
||||||
|
|
||||||
const createDevEnvFile = async opts => {
|
const createDevEnvFile = async opts => {
|
||||||
const destConfigFile = join(opts.dir, "./.env")
|
const destConfigFile = join(opts.dir, "./.env")
|
||||||
let createConfig = !(await exists(destConfigFile)) || opts.quiet
|
let createConfig = !existsSync(destConfigFile) || opts.quiet
|
||||||
if (createConfig) {
|
if (createConfig) {
|
||||||
const template = await readFile(
|
const template = await readFile(
|
||||||
resolve(__dirname, "..", "..", ".env.template"),
|
resolve(__dirname, "..", "..", ".env.template"),
|
||||||
|
|
|
@ -23,9 +23,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:cssVars={cssVariables} class="container">
|
<div use:cssVars={cssVariables} class="container">
|
||||||
{#if showImage}
|
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||||
<img class="image" src={imageUrl} alt="" />
|
|
||||||
{/if}
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h2 class="heading">{heading}</h2>
|
<h2 class="heading">{heading}</h2>
|
||||||
<h4 class="text">{description}</h4>
|
<h4 class="text">{description}</h4>
|
||||||
|
|
|
@ -26,9 +26,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:cssVars={cssVariables} class="container">
|
<div use:cssVars={cssVariables} class="container">
|
||||||
{#if showImage}
|
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||||
<img class="image" src={imageUrl} alt="" />
|
|
||||||
{/if}
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<main>
|
<main>
|
||||||
<h2 class="heading">{heading}</h2>
|
<h2 class="heading">{heading}</h2>
|
||||||
|
|
|
@ -27,15 +27,4 @@
|
||||||
on:newRow={() => dispatch('newRow')} />
|
on:newRow={() => dispatch('newRow')} />
|
||||||
</DropdownMenu> -->
|
</DropdownMenu> -->
|
||||||
|
|
||||||
<!-- <style>
|
<!--<style ✂prettier:content✂="CiAgZGl2IHsKICAgIGRpc3BsYXk6IGdyaWQ7CiAgICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IGF1dG8gYXV0bzsKICAgIHBsYWNlLWl0ZW1zOiBzdGFydCBjZW50ZXI7CiAgfQogIGg1IHsKICAgIHBhZGRpbmc6IHZhcigtLXNwYWNpbmcteGwpIDAgMCB2YXIoLS1zcGFjaW5nLXhsKTsKICAgIG1hcmdpbjogMDsKICAgIGZvbnQtd2VpZ2h0OiA1MDA7CiAgfQo=" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂=""></style>-->
|
||||||
div {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
place-items: start center;
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style> -->
|
|
||||||
|
|
|
@ -135,7 +135,9 @@
|
||||||
{#if selectedRows.length > 0}
|
{#if selectedRows.length > 0}
|
||||||
<DeleteButton text small on:click={deleteRows}>
|
<DeleteButton text small on:click={deleteRows}>
|
||||||
<Icon name="addrow" />
|
<Icon name="addrow" />
|
||||||
Delete {selectedRows.length} row(s)
|
Delete
|
||||||
|
{selectedRows.length}
|
||||||
|
row(s)
|
||||||
</DeleteButton>
|
</DeleteButton>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,9 +42,7 @@
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{#if logo}
|
{#if logo}
|
||||||
<div class="logo-container">
|
<div class="logo-container"><img src={logo} alt="logo" /></div>
|
||||||
<img src={logo} alt="logo" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if title}
|
{#if title}
|
||||||
|
|
|
@ -33,6 +33,4 @@
|
||||||
<sub class={className}>{text}</sub>
|
<sub class={className}>{text}</sub>
|
||||||
{:else if isTag('sup')}
|
{:else if isTag('sup')}
|
||||||
<sup class={className}>{text}</sup>
|
<sup class={className}>{text}</sup>
|
||||||
{:else}
|
{:else}<span>{text}</span>{/if}
|
||||||
<span>{text}</span>
|
|
||||||
{/if}
|
|
||||||
|
|
|
@ -12,9 +12,7 @@
|
||||||
<div class="file">
|
<div class="file">
|
||||||
{#if FILE_TYPES.IMAGE.includes(file.extension.toLowerCase())}
|
{#if FILE_TYPES.IMAGE.includes(file.extension.toLowerCase())}
|
||||||
<img {width} {height} src={file.url} alt="preview of {file.name}" />
|
<img {width} {height} src={file.url} alt="preview of {file.name}" />
|
||||||
{:else}
|
{:else}<i class="far fa-file" />{/if}
|
||||||
<i class="far fa-file" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<span>{file.name}</span>
|
<span>{file.name}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
11
yarn.lock
11
yarn.lock
|
@ -3690,11 +3690,10 @@ prettier-linter-helpers@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-diff "^1.1.2"
|
fast-diff "^1.1.2"
|
||||||
|
|
||||||
prettier-plugin-svelte@^0.7.0:
|
prettier-plugin-svelte@^1.4.0:
|
||||||
version "0.7.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-0.7.0.tgz#5ac0b9f194e0450c88ff1e167cbf3b32d2642df2"
|
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-1.4.0.tgz#bb992759fb77ec2c3545d454a7c60f7a258cb745"
|
||||||
dependencies:
|
integrity sha512-KXO2He7Kql0Lz4DdlzVli1j2JTDUR9jPV/DqyfnJmY1pCeSV1qZkxgdsyYma35W6OLrCAr/G6yKdmzo+75u2Ng==
|
||||||
tslib "^1.9.3"
|
|
||||||
|
|
||||||
prettier@^1.19.1:
|
prettier@^1.19.1:
|
||||||
version "1.19.1"
|
version "1.19.1"
|
||||||
|
@ -4529,7 +4528,7 @@ trim-off-newlines@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
|
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
|
||||||
|
|
||||||
tslib@^1.9.0, tslib@^1.9.3:
|
tslib@^1.9.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue