Merge pull request #903 from Budibase/feature/security-update
Security Update & Role-Based Screens
This commit is contained in:
commit
b582b7ac22
|
@ -115,22 +115,22 @@ Cypress.Commands.add("createUser", (email, password, role) => {
|
||||||
// Create User
|
// Create User
|
||||||
cy.contains("Users").click()
|
cy.contains("Users").click()
|
||||||
|
|
||||||
cy.contains("Create New Row").click()
|
cy.contains("Create New User").click()
|
||||||
|
|
||||||
cy.get(".modal").within(() => {
|
cy.get(".modal").within(() => {
|
||||||
cy.get("input")
|
cy.get("input")
|
||||||
.first()
|
.first()
|
||||||
.type(password)
|
.type(email)
|
||||||
cy.get("input")
|
cy.get("input")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.type(email)
|
.type(password)
|
||||||
cy.get("select")
|
cy.get("select")
|
||||||
.first()
|
.first()
|
||||||
.select(role)
|
.select(role)
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
cy.get(".buttons")
|
cy.get(".buttons")
|
||||||
.contains("Create Row")
|
.contains("Create User")
|
||||||
.click()
|
.click()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { getFrontendStore } from "./store/frontend"
|
||||||
import { getBackendUiStore } from "./store/backend"
|
import { getBackendUiStore } from "./store/backend"
|
||||||
import { getAutomationStore } from "./store/automation/"
|
import { getAutomationStore } from "./store/automation/"
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
import { derived } from "svelte/store"
|
import { derived, writable } from "svelte/store"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
import { LAYOUT_NAMES } from "../constants"
|
import { FrontendTypes, LAYOUT_NAMES } from "../constants"
|
||||||
import { makePropsSafe } from "components/userInterface/assetParsing/createProps"
|
import { makePropsSafe } from "components/userInterface/assetParsing/createProps"
|
||||||
|
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
|
@ -13,18 +13,12 @@ export const automationStore = getAutomationStore()
|
||||||
export const themeStore = getThemeStore()
|
export const themeStore = getThemeStore()
|
||||||
|
|
||||||
export const currentAsset = derived(store, $store => {
|
export const currentAsset = derived(store, $store => {
|
||||||
const layout = $store.layouts
|
const type = $store.currentFrontEndType
|
||||||
? $store.layouts.find(layout => layout._id === $store.currentAssetId)
|
if (type === FrontendTypes.SCREEN) {
|
||||||
: null
|
return $store.screens.find(screen => screen._id === $store.selectedScreenId)
|
||||||
|
} else if (type === FrontendTypes.LAYOUT) {
|
||||||
if (layout) return layout
|
return $store.layouts.find(layout => layout._id === $store.selectedLayoutId)
|
||||||
|
}
|
||||||
const screen = $store.screens
|
|
||||||
? $store.screens.find(screen => screen._id === $store.currentAssetId)
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (screen) return screen
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -59,8 +53,14 @@ export const selectedComponent = derived(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export const currentAssetName = derived(store, () => {
|
export const currentAssetId = derived(store, $store => {
|
||||||
return currentAsset.name
|
return $store.currentFrontEndType === FrontendTypes.SCREEN
|
||||||
|
? $store.selectedScreenId
|
||||||
|
: $store.selectedLayoutId
|
||||||
|
})
|
||||||
|
|
||||||
|
export const currentAssetName = derived(currentAsset, $currentAsset => {
|
||||||
|
return $currentAsset?.name
|
||||||
})
|
})
|
||||||
|
|
||||||
// leave this as before for consistency
|
// leave this as before for consistency
|
||||||
|
@ -74,6 +74,8 @@ export const mainLayout = derived(store, $store => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const selectedAccessRole = writable("BASIC")
|
||||||
|
|
||||||
export const initialise = async () => {
|
export const initialise = async () => {
|
||||||
try {
|
try {
|
||||||
await analytics.activate()
|
await analytics.activate()
|
||||||
|
|
|
@ -6,6 +6,7 @@ const INITIAL_BACKEND_UI_STATE = {
|
||||||
tables: [],
|
tables: [],
|
||||||
views: [],
|
views: [],
|
||||||
users: [],
|
users: [],
|
||||||
|
roles: [],
|
||||||
selectedDatabase: {},
|
selectedDatabase: {},
|
||||||
selectedTable: {},
|
selectedTable: {},
|
||||||
draftTable: {},
|
draftTable: {},
|
||||||
|
@ -177,6 +178,26 @@ export const getBackendUiStore = () => {
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
roles: {
|
||||||
|
fetch: async () => {
|
||||||
|
const response = await api.get("/api/roles")
|
||||||
|
const roles = await response.json()
|
||||||
|
store.update(state => {
|
||||||
|
state.roles = roles
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delete: async role => {
|
||||||
|
const response = await api.delete(`/api/roles/${role._id}/${role._rev}`)
|
||||||
|
await store.actions.roles.fetch()
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
save: async role => {
|
||||||
|
const response = await api.post("/api/roles", role)
|
||||||
|
await store.actions.roles.fetch()
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { cloneDeep } from "lodash/fp"
|
||||||
import {
|
import {
|
||||||
createProps,
|
createProps,
|
||||||
getBuiltin,
|
getBuiltin,
|
||||||
makePropsSafe,
|
|
||||||
} from "components/userInterface/assetParsing/createProps"
|
} from "components/userInterface/assetParsing/createProps"
|
||||||
import {
|
import {
|
||||||
allScreens,
|
allScreens,
|
||||||
|
@ -11,6 +10,7 @@ import {
|
||||||
currentAsset,
|
currentAsset,
|
||||||
mainLayout,
|
mainLayout,
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
|
selectedAccessRole,
|
||||||
} from "builderStore"
|
} from "builderStore"
|
||||||
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
||||||
import api from "../api"
|
import api from "../api"
|
||||||
|
@ -32,7 +32,8 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
screens: [],
|
screens: [],
|
||||||
components: [],
|
components: [],
|
||||||
currentFrontEndType: "none",
|
currentFrontEndType: "none",
|
||||||
currentAssetId: "",
|
selectedScreenId: "",
|
||||||
|
selectedLayoutId: "",
|
||||||
selectedComponentId: "",
|
selectedComponentId: "",
|
||||||
errors: [],
|
errors: [],
|
||||||
hasAppPackage: false,
|
hasAppPackage: false,
|
||||||
|
@ -83,28 +84,31 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
select: async screenId => {
|
select: screenId => {
|
||||||
let promise
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const screen = get(allScreens).find(screen => screen._id === screenId)
|
let screens = get(allScreens)
|
||||||
|
let screen =
|
||||||
|
screens.find(screen => screen._id === screenId) || screens[0]
|
||||||
if (!screen) return state
|
if (!screen) return state
|
||||||
|
|
||||||
state.currentFrontEndType = FrontendTypes.SCREEN
|
// Update role to the screen's role setting so that it will always
|
||||||
state.currentAssetId = screenId
|
// be visible
|
||||||
state.currentView = "detail"
|
selectedAccessRole.set(screen.routing.roleId)
|
||||||
|
|
||||||
promise = store.actions.screens.regenerateCss(screen)
|
state.currentFrontEndType = FrontendTypes.SCREEN
|
||||||
|
state.selectedScreenId = screen._id
|
||||||
|
state.currentView = "detail"
|
||||||
state.selectedComponentId = screen.props?._id
|
state.selectedComponentId = screen.props?._id
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
await promise
|
|
||||||
},
|
},
|
||||||
create: async screen => {
|
create: async screen => {
|
||||||
screen = await store.actions.screens.save(screen)
|
screen = await store.actions.screens.save(screen)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.currentAssetId = screen._id
|
state.selectedScreenId = screen._id
|
||||||
state.selectedComponentId = screen.props._id
|
state.selectedComponentId = screen.props._id
|
||||||
state.currentFrontEndType = FrontendTypes.SCREEN
|
state.currentFrontEndType = FrontendTypes.SCREEN
|
||||||
|
selectedAccessRole.set(screen.routing.roleId)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
return screen
|
return screen
|
||||||
|
@ -113,6 +117,7 @@ export const getFrontendStore = () => {
|
||||||
const creatingNewScreen = screen._id === undefined
|
const creatingNewScreen = screen._id === undefined
|
||||||
const response = await api.post(`/api/screens`, screen)
|
const response = await api.post(`/api/screens`, screen)
|
||||||
screen = await response.json()
|
screen = await response.json()
|
||||||
|
await store.actions.routing.fetch()
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const foundScreen = state.screens.findIndex(
|
const foundScreen = state.screens.findIndex(
|
||||||
|
@ -122,28 +127,14 @@ export const getFrontendStore = () => {
|
||||||
state.screens.splice(foundScreen, 1)
|
state.screens.splice(foundScreen, 1)
|
||||||
}
|
}
|
||||||
state.screens.push(screen)
|
state.screens.push(screen)
|
||||||
|
|
||||||
if (creatingNewScreen) {
|
|
||||||
const safeProps = makePropsSafe(
|
|
||||||
state.components[screen.props._component],
|
|
||||||
screen.props
|
|
||||||
)
|
|
||||||
state.selectedComponentId = safeProps._id
|
|
||||||
screen.props = safeProps
|
|
||||||
}
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
return screen
|
|
||||||
},
|
if (creatingNewScreen) {
|
||||||
regenerateCss: async asset => {
|
store.actions.screens.select(screen._id)
|
||||||
const response = await api.post("/api/css/generate", asset)
|
|
||||||
asset._css = (await response.json())?.css
|
|
||||||
},
|
|
||||||
regenerateCssForCurrentScreen: async () => {
|
|
||||||
const asset = get(currentAsset)
|
|
||||||
if (asset) {
|
|
||||||
await store.actions.screens.regenerateCss(asset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return screen
|
||||||
},
|
},
|
||||||
delete: async screens => {
|
delete: async screens => {
|
||||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||||
|
@ -159,8 +150,8 @@ export const getFrontendStore = () => {
|
||||||
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
|
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (screenToDelete._id === state.currentAssetId) {
|
if (screenToDelete._id === state.selectedScreenId) {
|
||||||
state.currentAssetId = ""
|
state.selectedScreenId = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
|
@ -181,50 +172,44 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
layouts: {
|
layouts: {
|
||||||
select: async layoutId => {
|
select: layoutId => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const layout = store.actions.layouts.find(layoutId)
|
const layout =
|
||||||
|
store.actions.layouts.find(layoutId) || get(store).layouts[0]
|
||||||
|
if (!layout) return
|
||||||
state.currentFrontEndType = FrontendTypes.LAYOUT
|
state.currentFrontEndType = FrontendTypes.LAYOUT
|
||||||
state.currentView = "detail"
|
state.currentView = "detail"
|
||||||
|
state.selectedLayoutId = layout._id
|
||||||
state.currentAssetId = layout._id
|
|
||||||
state.selectedComponentId = layout.props?._id
|
state.selectedComponentId = layout.props?._id
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
let cssPromises = []
|
|
||||||
cssPromises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
|
||||||
|
|
||||||
for (let screen of get(allScreens)) {
|
|
||||||
cssPromises.push(store.actions.screens.regenerateCss(screen))
|
|
||||||
}
|
|
||||||
await Promise.all(cssPromises)
|
|
||||||
},
|
},
|
||||||
save: async layout => {
|
save: async layout => {
|
||||||
const layoutToSave = cloneDeep(layout)
|
const layoutToSave = cloneDeep(layout)
|
||||||
delete layoutToSave._css
|
const creatingNewLayout = layoutToSave._id === undefined
|
||||||
|
|
||||||
const response = await api.post(`/api/layouts`, layoutToSave)
|
const response = await api.post(`/api/layouts`, layoutToSave)
|
||||||
|
const savedLayout = await response.json()
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const layoutIdx = state.layouts.findIndex(
|
const layoutIdx = state.layouts.findIndex(
|
||||||
stateLayout => stateLayout._id === json._id
|
stateLayout => stateLayout._id === savedLayout._id
|
||||||
)
|
)
|
||||||
|
|
||||||
if (layoutIdx >= 0) {
|
if (layoutIdx >= 0) {
|
||||||
// update existing layout
|
// update existing layout
|
||||||
state.layouts.splice(layoutIdx, 1, json)
|
state.layouts.splice(layoutIdx, 1, savedLayout)
|
||||||
} else {
|
} else {
|
||||||
// save new layout
|
// save new layout
|
||||||
state.layouts.push(json)
|
state.layouts.push(savedLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.currentAssetId = json._id
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Select layout if creating a new one
|
||||||
|
if (creatingNewLayout) {
|
||||||
|
store.actions.layouts.select(savedLayout._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return savedLayout
|
||||||
},
|
},
|
||||||
find: layoutId => {
|
find: layoutId => {
|
||||||
if (!layoutId) {
|
if (!layoutId) {
|
||||||
|
@ -237,16 +222,17 @@ export const getFrontendStore = () => {
|
||||||
const response = await api.delete(
|
const response = await api.delete(
|
||||||
`/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}`
|
`/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}`
|
||||||
)
|
)
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
throw new Error(json.message)
|
throw new Error(json.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.layouts = state.layouts.filter(
|
state.layouts = state.layouts.filter(
|
||||||
layout => layout._id !== layoutToDelete._id
|
layout => layout._id !== layoutToDelete._id
|
||||||
)
|
)
|
||||||
|
if (layoutToDelete._id === state.selectedLayoutId) {
|
||||||
|
state.selectedLayoutId = get(mainLayout)._id
|
||||||
|
}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -372,7 +358,6 @@ export const getFrontendStore = () => {
|
||||||
const index = mode === "above" ? targetIndex : targetIndex + 1
|
const index = mode === "above" ? targetIndex : targetIndex + 1
|
||||||
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
||||||
|
|
||||||
promises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
|
||||||
promises.push(store.actions.preview.saveSelected())
|
promises.push(store.actions.preview.saveSelected())
|
||||||
store.actions.components.select(componentToPaste)
|
store.actions.components.select(componentToPaste)
|
||||||
|
|
||||||
|
@ -390,8 +375,6 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
selected._styles[type][name] = value
|
selected._styles[type][name] = value
|
||||||
|
|
||||||
promises.push(store.actions.screens.regenerateCssForCurrentScreen())
|
|
||||||
|
|
||||||
// save without messing with the store
|
// save without messing with the store
|
||||||
promises.push(store.actions.preview.saveSelected())
|
promises.push(store.actions.preview.saveSelected())
|
||||||
return state
|
return state
|
||||||
|
@ -476,13 +459,8 @@ export const getFrontendStore = () => {
|
||||||
}).props
|
}).props
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save layout and regenerate all CSS because otherwise weird things happen
|
// Save layout
|
||||||
nav._children = [...nav._children, newLink]
|
nav._children = [...nav._children, newLink]
|
||||||
state.currentAssetId = layout._id
|
|
||||||
promises.push(store.actions.screens.regenerateCss(layout))
|
|
||||||
for (let screen of get(allScreens)) {
|
|
||||||
promises.push(store.actions.screens.regenerateCss(screen))
|
|
||||||
}
|
|
||||||
promises.push(store.actions.layouts.save(layout))
|
promises.push(store.actions.layouts.save(layout))
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
|
|
|
@ -4,12 +4,17 @@
|
||||||
import CreateColumnButton from "./buttons/CreateColumnButton.svelte"
|
import CreateColumnButton from "./buttons/CreateColumnButton.svelte"
|
||||||
import CreateViewButton from "./buttons/CreateViewButton.svelte"
|
import CreateViewButton from "./buttons/CreateViewButton.svelte"
|
||||||
import ExportButton from "./buttons/ExportButton.svelte"
|
import ExportButton from "./buttons/ExportButton.svelte"
|
||||||
|
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
||||||
import * as api from "./api"
|
import * as api from "./api"
|
||||||
import Table from "./Table.svelte"
|
import Table from "./Table.svelte"
|
||||||
|
import { TableNames } from "constants"
|
||||||
|
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
||||||
|
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||||
|
|
||||||
let data = []
|
let data = []
|
||||||
let loading = false
|
let loading = false
|
||||||
|
|
||||||
|
$: isUsersTable = $backendUiStore.selectedTable?._id === TableNames.USERS
|
||||||
$: title = $backendUiStore.selectedTable.name
|
$: title = $backendUiStore.selectedTable.name
|
||||||
$: schema = $backendUiStore.selectedTable.schema
|
$: schema = $backendUiStore.selectedTable.schema
|
||||||
$: tableView = {
|
$: tableView = {
|
||||||
|
@ -29,11 +34,22 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Table {title} {schema} {data} allowEditing={true} {loading}>
|
<Table
|
||||||
|
{title}
|
||||||
|
{schema}
|
||||||
|
tableId={$backendUiStore.selectedTable?._id}
|
||||||
|
{data}
|
||||||
|
allowEditing={true}
|
||||||
|
{loading}>
|
||||||
<CreateColumnButton />
|
<CreateColumnButton />
|
||||||
{#if schema && Object.keys(schema).length > 0}
|
{#if schema && Object.keys(schema).length > 0}
|
||||||
<CreateRowButton />
|
<CreateRowButton
|
||||||
|
title={isUsersTable ? 'Create New User' : 'Create New Row'}
|
||||||
|
modalContentComponent={isUsersTable ? CreateEditUser : CreateEditRow} />
|
||||||
<CreateViewButton />
|
<CreateViewButton />
|
||||||
<ExportButton view={tableView} />
|
<ExportButton view={tableView} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if isUsersTable}
|
||||||
|
<EditRolesButton />
|
||||||
|
{/if}
|
||||||
</Table>
|
</Table>
|
||||||
|
|
|
@ -7,20 +7,16 @@
|
||||||
Toggle,
|
Toggle,
|
||||||
RichText,
|
RichText,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import { TableNames } from "constants"
|
|
||||||
import Dropzone from "components/common/Dropzone.svelte"
|
import Dropzone from "components/common/Dropzone.svelte"
|
||||||
import { capitalise } from "../../../helpers"
|
import { capitalise } from "../../../helpers"
|
||||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
|
|
||||||
export let meta
|
export let meta
|
||||||
export let creating
|
|
||||||
export let value = meta.type === "boolean" ? false : ""
|
export let value = meta.type === "boolean" ? false : ""
|
||||||
|
export let readonly
|
||||||
|
|
||||||
$: type = meta.type
|
$: type = meta.type
|
||||||
$: label = capitalise(meta.name)
|
$: label = capitalise(meta.name)
|
||||||
$: editingUser =
|
|
||||||
!creating && $backendUiStore.selectedTable?._id === TableNames.USERS
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === 'options'}
|
{#if type === 'options'}
|
||||||
|
@ -53,5 +49,5 @@
|
||||||
data-cy="{meta.name}-input"
|
data-cy="{meta.name}-input"
|
||||||
{type}
|
{type}
|
||||||
bind:value
|
bind:value
|
||||||
disabled={editingUser} />
|
disabled={readonly} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -7,10 +7,15 @@
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
|
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
|
||||||
import { getRenderer, editRowRenderer } from "./cells/cellRenderers"
|
import {
|
||||||
|
getRenderer,
|
||||||
|
editRowRenderer,
|
||||||
|
userRowRenderer,
|
||||||
|
} from "./cells/cellRenderers"
|
||||||
import TableLoadingOverlay from "./TableLoadingOverlay"
|
import TableLoadingOverlay from "./TableLoadingOverlay"
|
||||||
import TableHeader from "./TableHeader"
|
import TableHeader from "./TableHeader"
|
||||||
import "@budibase/svelte-ag-grid/dist/index.css"
|
import "@budibase/svelte-ag-grid/dist/index.css"
|
||||||
|
import { TableNames } from "constants"
|
||||||
|
|
||||||
export let schema = {}
|
export let schema = {}
|
||||||
export let data = []
|
export let data = []
|
||||||
|
@ -42,7 +47,18 @@
|
||||||
animateRows: true,
|
animateRows: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: isUsersTable = tableId === TableNames.USERS
|
||||||
$: {
|
$: {
|
||||||
|
if (isUsersTable) {
|
||||||
|
schema.email.displayFieldName = "Email"
|
||||||
|
schema.roleId.displayFieldName = "Role"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
// Reset selection every time data changes
|
||||||
|
selectedRows = []
|
||||||
|
|
||||||
let result = []
|
let result = []
|
||||||
if (allowEditing) {
|
if (allowEditing) {
|
||||||
result = [
|
result = [
|
||||||
|
@ -57,23 +73,34 @@
|
||||||
suppressMenu: true,
|
suppressMenu: true,
|
||||||
minWidth: 114,
|
minWidth: 114,
|
||||||
width: 114,
|
width: 114,
|
||||||
cellRenderer: editRowRenderer,
|
cellRenderer: isUsersTable ? userRowRenderer : editRowRenderer,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(schema || {}).forEach((key, idx) => {
|
const canEditColumn = key => {
|
||||||
|
if (!allowEditing) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !(isUsersTable && ["email", "roleId"].includes(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(schema || {}).forEach(([key, value]) => {
|
||||||
result.push({
|
result.push({
|
||||||
headerCheckboxSelection: false,
|
headerCheckboxSelection: false,
|
||||||
headerComponent: TableHeader,
|
headerComponent: TableHeader,
|
||||||
headerComponentParams: {
|
headerComponentParams: {
|
||||||
field: schema[key],
|
field: schema[key],
|
||||||
editable: allowEditing,
|
editable: canEditColumn(key),
|
||||||
},
|
},
|
||||||
headerName: key,
|
headerName: value.displayFieldName || key,
|
||||||
field: key,
|
field: key,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
cellRenderer: getRenderer(schema[key], true),
|
cellRenderer: getRenderer({
|
||||||
|
schema: schema[key],
|
||||||
|
editable: true,
|
||||||
|
isUsersTable,
|
||||||
|
}),
|
||||||
cellRendererParams: {
|
cellRendererParams: {
|
||||||
selectRelationship,
|
selectRelationship,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { TextButton as Button, Icon, Modal } from "@budibase/bbui"
|
import { TextButton as Button, Icon, Modal } from "@budibase/bbui"
|
||||||
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
|
import CreateEditRow from "../modals/CreateEditRow.svelte"
|
||||||
|
|
||||||
|
export let modalContentComponent = CreateEditRow
|
||||||
|
export let title = "Create New Row"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
@ -8,9 +11,9 @@
|
||||||
<div>
|
<div>
|
||||||
<Button text small on:click={modal.show}>
|
<Button text small on:click={modal.show}>
|
||||||
<Icon name="addrow" />
|
<Icon name="addrow" />
|
||||||
Create New Row
|
{title}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<CreateEditRowModal />
|
<svelte:component this={modalContentComponent} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
import { TextButton as Button, Modal } from "@budibase/bbui"
|
||||||
|
import EditRolesModal from "../modals/EditRoles.svelte"
|
||||||
|
|
||||||
|
let modal
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button text small on:click={modal.show}>
|
||||||
|
<i class="ri-lock-line" />
|
||||||
|
Edit Roles
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<EditRolesModal />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
i {
|
||||||
|
margin-right: var(--spacing-xs);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
export let roleId
|
||||||
|
|
||||||
|
$: role = $backendUiStore.roles.find(role => role._id === roleId)
|
||||||
|
$: roleName = role?.name ?? "Unknown role"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{roleName}</div>
|
|
@ -1,16 +1,25 @@
|
||||||
import AttachmentList from "./AttachmentCell.svelte"
|
import AttachmentList from "./AttachmentCell.svelte"
|
||||||
import EditRow from "../modals/EditRow.svelte"
|
import EditRow from "../modals/EditRow.svelte"
|
||||||
|
import CreateEditUser from "../modals/CreateEditUser.svelte"
|
||||||
import DeleteRow from "../modals/DeleteRow.svelte"
|
import DeleteRow from "../modals/DeleteRow.svelte"
|
||||||
import RelationshipDisplay from "./RelationshipCell.svelte"
|
import RelationshipDisplay from "./RelationshipCell.svelte"
|
||||||
|
import RoleCell from "./RoleCell.svelte"
|
||||||
|
|
||||||
const renderers = {
|
const renderers = {
|
||||||
attachment: attachmentRenderer,
|
attachment: attachmentRenderer,
|
||||||
link: linkedRowRenderer,
|
link: linkedRowRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRenderer(schema, editable) {
|
export function getRenderer({ schema, editable, isUsersTable }) {
|
||||||
|
const rendererParams = {
|
||||||
|
options: schema.options,
|
||||||
|
constraints: schema.constraints,
|
||||||
|
editable,
|
||||||
|
}
|
||||||
if (renderers[schema.type]) {
|
if (renderers[schema.type]) {
|
||||||
return renderers[schema.type](schema.options, schema.constraints, editable)
|
return renderers[schema.type](rendererParams)
|
||||||
|
} else if (isUsersTable && schema.name === "roleId") {
|
||||||
|
return roleRenderer(rendererParams)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -45,15 +54,31 @@ export function editRowRenderer(params) {
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
export function userRowRenderer(params) {
|
||||||
function attachmentRenderer(options, constraints, editable) {
|
const container = document.createElement("div")
|
||||||
|
container.style.height = "100%"
|
||||||
|
container.style.display = "flex"
|
||||||
|
container.style.alignItems = "center"
|
||||||
|
|
||||||
|
new EditRow({
|
||||||
|
target: container,
|
||||||
|
props: {
|
||||||
|
row: params.data,
|
||||||
|
modalContentComponent: CreateEditUser,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachmentRenderer() {
|
||||||
return params => {
|
return params => {
|
||||||
const container = document.createElement("div")
|
const container = document.createElement("div")
|
||||||
container.style.height = "100%"
|
container.style.height = "100%"
|
||||||
container.style.display = "flex"
|
container.style.display = "flex"
|
||||||
container.style.alignItems = "center"
|
container.style.alignItems = "center"
|
||||||
|
|
||||||
const attachmentInstance = new AttachmentList({
|
new AttachmentList({
|
||||||
target: container,
|
target: container,
|
||||||
props: {
|
props: {
|
||||||
files: params.value || [],
|
files: params.value || [],
|
||||||
|
@ -64,7 +89,6 @@ function attachmentRenderer(options, constraints, editable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
function linkedRowRenderer() {
|
function linkedRowRenderer() {
|
||||||
return params => {
|
return params => {
|
||||||
let container = document.createElement("div")
|
let container = document.createElement("div")
|
||||||
|
@ -84,3 +108,21 @@ function linkedRowRenderer() {
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function roleRenderer() {
|
||||||
|
return params => {
|
||||||
|
let container = document.createElement("div")
|
||||||
|
container.style.display = "grid"
|
||||||
|
container.style.height = "100%"
|
||||||
|
container.style.alignItems = "center"
|
||||||
|
|
||||||
|
new RoleCell({
|
||||||
|
target: container,
|
||||||
|
props: {
|
||||||
|
roleId: params.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { TableNames } from "constants"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import RowFieldControl from "../RowFieldControl.svelte"
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
|
@ -40,15 +39,9 @@
|
||||||
confirmText={creating ? 'Create Row' : 'Save Row'}
|
confirmText={creating ? 'Create Row' : 'Save Row'}
|
||||||
onConfirm={saveRow}>
|
onConfirm={saveRow}>
|
||||||
<ErrorsBox {errors} />
|
<ErrorsBox {errors} />
|
||||||
{#if creating && table._id === TableNames.USERS}
|
|
||||||
<RowFieldControl
|
|
||||||
{creating}
|
|
||||||
meta={{ name: 'password', type: 'password' }}
|
|
||||||
bind:value={row.password} />
|
|
||||||
{/if}
|
|
||||||
{#each tableSchema as [key, meta]}
|
{#each tableSchema as [key, meta]}
|
||||||
<div>
|
<div>
|
||||||
<RowFieldControl {meta} bind:value={row[key]} {creating} />
|
<RowFieldControl {meta} bind:value={row[key]} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</ModalContent>
|
</ModalContent>
|
|
@ -0,0 +1,85 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
|
import * as backendApi from "../api"
|
||||||
|
import { ModalContent, Select } from "@budibase/bbui"
|
||||||
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
|
|
||||||
|
export let row = {}
|
||||||
|
|
||||||
|
let errors = []
|
||||||
|
|
||||||
|
$: creating = row?._id == null
|
||||||
|
$: table = row.tableId
|
||||||
|
? $backendUiStore.tables.find(table => table._id === row?.tableId)
|
||||||
|
: $backendUiStore.selectedTable
|
||||||
|
$: tableSchema = getUserSchema(table)
|
||||||
|
$: customSchemaKeys = getCustomSchemaKeys(tableSchema)
|
||||||
|
|
||||||
|
const getUserSchema = table => {
|
||||||
|
let schema = table?.schema ?? {}
|
||||||
|
if (schema.username) {
|
||||||
|
schema.username.name = "Username"
|
||||||
|
}
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCustomSchemaKeys = schema => {
|
||||||
|
let customSchema = { ...schema }
|
||||||
|
delete customSchema["email"]
|
||||||
|
delete customSchema["roleId"]
|
||||||
|
return Object.entries(customSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveRow = async () => {
|
||||||
|
const rowResponse = await backendApi.saveRow(
|
||||||
|
{ ...row, tableId: table._id },
|
||||||
|
table._id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rowResponse.errors) {
|
||||||
|
if (Array.isArray(rowResponse.errors)) {
|
||||||
|
errors = rowResponse.errors.map(error => ({ message: error }))
|
||||||
|
} else {
|
||||||
|
errors = Object.entries(rowResponse.errors)
|
||||||
|
.map(([key, error]) => ({ dataPath: key, message: error }))
|
||||||
|
.flat()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
notifier.success("User saved successfully.")
|
||||||
|
backendUiStore.actions.rows.save(rowResponse)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title={creating ? 'Create User' : 'Edit User'}
|
||||||
|
confirmText={creating ? 'Create User' : 'Save User'}
|
||||||
|
onConfirm={saveRow}>
|
||||||
|
<ErrorsBox {errors} />
|
||||||
|
<RowFieldControl
|
||||||
|
meta={{ ...tableSchema.email, name: 'Email' }}
|
||||||
|
bind:value={row.email}
|
||||||
|
readonly={!creating} />
|
||||||
|
<RowFieldControl
|
||||||
|
meta={{ name: 'password', type: 'password' }}
|
||||||
|
bind:value={row.password} />
|
||||||
|
<!-- Defer rendering this select until roles load, otherwise the initial
|
||||||
|
selection is always undefined -->
|
||||||
|
<Select
|
||||||
|
thin
|
||||||
|
secondary
|
||||||
|
label="Role"
|
||||||
|
data-cy="roleId-select"
|
||||||
|
bind:value={row.roleId}>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each $backendUiStore.roles as role}
|
||||||
|
<option value={role._id}>{role.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{#each customSchemaKeys as [key, meta]}
|
||||||
|
<RowFieldControl {meta} bind:value={row[key]} {creating} />
|
||||||
|
{/each}
|
||||||
|
</ModalContent>
|
|
@ -0,0 +1,132 @@
|
||||||
|
<script>
|
||||||
|
import { ModalContent, Select, Input, Button } from "@budibase/bbui"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
let permissions = []
|
||||||
|
let selectedRole = {}
|
||||||
|
let errors = []
|
||||||
|
$: selectedRoleId = selectedRole._id
|
||||||
|
$: otherRoles = $backendUiStore.roles.filter(
|
||||||
|
role => role._id !== selectedRoleId
|
||||||
|
)
|
||||||
|
$: isCreating = selectedRoleId == null || selectedRoleId === ""
|
||||||
|
|
||||||
|
const fetchPermissions = async () => {
|
||||||
|
const permissionsResponse = await api.get("/api/permissions")
|
||||||
|
permissions = await permissionsResponse.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes the selected role
|
||||||
|
const changeRole = event => {
|
||||||
|
const id = event?.target?.value
|
||||||
|
const role = $backendUiStore.roles.find(role => role._id === id)
|
||||||
|
if (role) {
|
||||||
|
selectedRole = {
|
||||||
|
...role,
|
||||||
|
inherits: role.inherits ?? "",
|
||||||
|
permissionId: role.permissionId ?? "",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedRole = { _id: "", inherits: "", permissionId: "" }
|
||||||
|
}
|
||||||
|
errors = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves or creates the selected role
|
||||||
|
const saveRole = async () => {
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
// Clean up empty strings
|
||||||
|
const keys = ["_id", "inherits", "permissionId"]
|
||||||
|
keys.forEach(key => {
|
||||||
|
if (selectedRole[key] === "") {
|
||||||
|
delete selectedRole[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!selectedRole.name || selectedRole.name.trim() === "") {
|
||||||
|
errors.push({ message: "Please enter a role name" })
|
||||||
|
}
|
||||||
|
if (!selectedRole.permissionId) {
|
||||||
|
errors.push({ message: "Please choose permissions" })
|
||||||
|
}
|
||||||
|
if (errors.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save/create the role
|
||||||
|
const response = await backendUiStore.actions.roles.save(selectedRole)
|
||||||
|
if (response.status === 200) {
|
||||||
|
notifier.success("Role saved successfully.")
|
||||||
|
} else {
|
||||||
|
notifier.danger("Error saving role.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes the selected role
|
||||||
|
const deleteRole = async () => {
|
||||||
|
const response = await backendUiStore.actions.roles.delete(selectedRole)
|
||||||
|
if (response.status === 200) {
|
||||||
|
changeRole()
|
||||||
|
notifier.success("Role deleted successfully.")
|
||||||
|
} else {
|
||||||
|
notifier.danger("Error deleting role.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(fetchPermissions)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title="Edit Roles"
|
||||||
|
confirmText={isCreating ? 'Create' : 'Save'}
|
||||||
|
onConfirm={saveRole}>
|
||||||
|
{#if errors.length}
|
||||||
|
<ErrorsBox {errors} />
|
||||||
|
{/if}
|
||||||
|
<Select
|
||||||
|
thin
|
||||||
|
secondary
|
||||||
|
label="Role"
|
||||||
|
value={selectedRoleId}
|
||||||
|
on:change={changeRole}>
|
||||||
|
<option value="">Create new role</option>
|
||||||
|
{#each $backendUiStore.roles as role}
|
||||||
|
<option value={role._id}>{role.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{#if selectedRole}
|
||||||
|
<Input label="Name" bind:value={selectedRole.name} thin />
|
||||||
|
<Select
|
||||||
|
thin
|
||||||
|
secondary
|
||||||
|
label="Inherits Role"
|
||||||
|
bind:value={selectedRole.inherits}>
|
||||||
|
<option value="">None</option>
|
||||||
|
{#each otherRoles as role}
|
||||||
|
<option value={role._id}>{role.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
thin
|
||||||
|
secondary
|
||||||
|
label="Permissions"
|
||||||
|
bind:value={selectedRole.permissionId}>
|
||||||
|
<option value="">Choose permissions</option>
|
||||||
|
{#each permissions as permission}
|
||||||
|
<option value={permission._id}>{permission.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{/if}
|
||||||
|
<div slot="footer">
|
||||||
|
{#if !isCreating}
|
||||||
|
<Button red on:click={deleteRole}>Delete</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Modal, Button } from "@budibase/bbui"
|
import { Modal, Button } from "@budibase/bbui"
|
||||||
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
|
import CreateEditRow from "../modals/CreateEditRow.svelte"
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
|
export let modalContentComponent = CreateEditRow
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
@ -14,5 +15,5 @@
|
||||||
|
|
||||||
<Button data-cy="edit-row" secondary small on:click={showModal}>Edit</Button>
|
<Button data-cy="edit-row" secondary small on:click={showModal}>Edit</Button>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<CreateEditRowModal {row} />
|
<svelte:component this={modalContentComponent} {row} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
import ListItem from "./ListItem.svelte"
|
|
||||||
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
||||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||||
|
@ -47,7 +46,9 @@
|
||||||
text={table.name}
|
text={table.name}
|
||||||
selected={selectedView === `all_${table._id}`}
|
selected={selectedView === `all_${table._id}`}
|
||||||
on:click={() => selectTable(table)}>
|
on:click={() => selectTable(table)}>
|
||||||
<EditTablePopover {table} />
|
{#if table._id !== TableNames.USERS}
|
||||||
|
<EditTablePopover {table} />
|
||||||
|
{/if}
|
||||||
</NavItem>
|
</NavItem>
|
||||||
{#each Object.keys(table.views || {}) as viewName}
|
{#each Object.keys(table.views || {}) as viewName}
|
||||||
<NavItem
|
<NavItem
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasErrors}
|
{#if hasErrors}
|
||||||
<div class="container bb__alert bb__alert--danger">
|
<div class="container">
|
||||||
{#each errors as error}
|
{#each errors as error}
|
||||||
<div class="error">{error.dataPath} {error.message}</div>
|
<div class="error">{error.dataPath || ''} {error.message}</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -17,6 +17,8 @@
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: var(--border-radius-m);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
|
background-color: rgba(241, 165, 165, 0.2);
|
||||||
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
}
|
}
|
||||||
.nav-item:hover,
|
.nav-item:hover,
|
||||||
.nav-item.selected {
|
.nav-item.selected {
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: var(--border-radius-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
.component-container iframe {
|
.component-container iframe {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAssetId } from "builderStore"
|
||||||
import { getComponentDefinition } from "builderStore/storeUtils"
|
import { getComponentDefinition } from "builderStore/storeUtils"
|
||||||
import { DropEffect, DropPosition } from "./dragDropStore"
|
import { DropEffect, DropPosition } from "./dragDropStore"
|
||||||
import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte"
|
import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte"
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
const path = store.actions.components.findRoute(component)
|
const path = store.actions.components.findRoute(component)
|
||||||
|
|
||||||
// Go to correct URL
|
// Go to correct URL
|
||||||
$goto(`./${$store.currentAssetId}/${path}`)
|
$goto(`./${$currentAssetId}/${path}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragstart = component => e => {
|
const dragstart = component => e => {
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
on:drop={dragDropStore.actions.drop}
|
on:drop={dragDropStore.actions.drop}
|
||||||
text={isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
text={isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
||||||
withArrow
|
withArrow
|
||||||
indentLevel={level + 3}
|
indentLevel={level + 1}
|
||||||
selected={$store.selectedComponentId === component._id}>
|
selected={$store.selectedComponentId === component._id}>
|
||||||
<ComponentDropdownMenu {component} />
|
<ComponentDropdownMenu {component} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable } from "svelte/store"
|
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store, selectedComponent, currentAsset } from "builderStore"
|
import { store, selectedComponent, currentAsset } from "builderStore"
|
||||||
import instantiateStore from "./dragDropStore"
|
import instantiateStore from "./dragDropStore"
|
||||||
|
@ -20,6 +19,7 @@
|
||||||
export let route
|
export let route
|
||||||
export let path
|
export let path
|
||||||
export let indent
|
export let indent
|
||||||
|
export let border
|
||||||
|
|
||||||
$: selectedScreen = $currentAsset
|
$: selectedScreen = $currentAsset
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
icon="ri-folder-line"
|
icon="ri-folder-line"
|
||||||
text={path}
|
text={path}
|
||||||
opened={true}
|
opened={true}
|
||||||
|
{border}
|
||||||
withArrow={route.subpaths} />
|
withArrow={route.subpaths} />
|
||||||
|
|
||||||
{#each Object.entries(route.subpaths) as [url, subpath]}
|
{#each Object.entries(route.subpaths) as [url, subpath]}
|
||||||
|
@ -41,8 +42,8 @@
|
||||||
<NavItem
|
<NavItem
|
||||||
icon="ri-artboard-2-line"
|
icon="ri-artboard-2-line"
|
||||||
indentLevel={indent || 1}
|
indentLevel={indent || 1}
|
||||||
selected={$store.currentAssetId === screenId}
|
selected={$store.selectedScreenId === screenId}
|
||||||
opened={$store.currentAssetId === screenId}
|
opened={$store.selectedScreenId === screenId}
|
||||||
text={ROUTE_NAME_MAP[url]?.[role] || url}
|
text={ROUTE_NAME_MAP[url]?.[role] || url}
|
||||||
withArrow={route.subpaths}
|
withArrow={route.subpaths}
|
||||||
on:click={() => changeScreen(screenId)}>
|
on:click={() => changeScreen(screenId)}>
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
</NavItem>
|
</NavItem>
|
||||||
{#if selectedScreen?._id === screenId}
|
{#if selectedScreen?._id === screenId}
|
||||||
<ComponentTree
|
<ComponentTree
|
||||||
|
level={1}
|
||||||
components={selectedScreen.props._children}
|
components={selectedScreen.props._children}
|
||||||
currentComponent={$selectedComponent}
|
currentComponent={$selectedComponent}
|
||||||
{dragDropStore} />
|
{dragDropStore} />
|
||||||
|
|
|
@ -1,11 +1,76 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { store, selectedAccessRole } from "builderStore"
|
||||||
import { store } from "builderStore"
|
|
||||||
import PathTree from "./PathTree.svelte"
|
import PathTree from "./PathTree.svelte"
|
||||||
|
|
||||||
|
let routes = {}
|
||||||
|
$: paths = Object.keys(routes || {}).sort()
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const allRoutes = $store.routes
|
||||||
|
const sortedPaths = Object.keys(allRoutes || {}).sort()
|
||||||
|
const selectedRoleId = $selectedAccessRole
|
||||||
|
const selectedScreenId = $store.selectedScreenId
|
||||||
|
|
||||||
|
let found = false
|
||||||
|
let firstValidScreenId
|
||||||
|
let filteredRoutes = {}
|
||||||
|
let screenRoleId
|
||||||
|
|
||||||
|
// Filter all routes down to only those which match the current role
|
||||||
|
sortedPaths.forEach(path => {
|
||||||
|
const config = allRoutes[path]
|
||||||
|
Object.entries(config.subpaths).forEach(([subpath, pathConfig]) => {
|
||||||
|
Object.entries(pathConfig.screens).forEach(([roleId, screenId]) => {
|
||||||
|
if (screenId === selectedScreenId) {
|
||||||
|
screenRoleId = roleId
|
||||||
|
found = roleId === selectedRoleId
|
||||||
|
}
|
||||||
|
if (roleId === selectedRoleId) {
|
||||||
|
if (!firstValidScreenId) {
|
||||||
|
firstValidScreenId = screenId
|
||||||
|
}
|
||||||
|
if (!filteredRoutes[path]) {
|
||||||
|
filteredRoutes[path] = { subpaths: {} }
|
||||||
|
}
|
||||||
|
filteredRoutes[path].subpaths[subpath] = {
|
||||||
|
screens: {
|
||||||
|
[selectedRoleId]: screenId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
routes = filteredRoutes
|
||||||
|
|
||||||
|
// Select the correct role for the current screen ID
|
||||||
|
if (!found && screenRoleId) {
|
||||||
|
selectedAccessRole.set(screenRoleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the selected screen isn't in this filtered list, select the first one
|
||||||
|
else if (!found && firstValidScreenId) {
|
||||||
|
store.actions.screens.select(firstValidScreenId)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#each Object.keys($store.routes || {}) as path}
|
{#each paths as path, idx}
|
||||||
<PathTree {path} route={$store.routes[path]} />
|
<PathTree border={idx > 0} {path} route={routes[path]} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if !paths.length}
|
||||||
|
<div class="empty">
|
||||||
|
There aren't any screens configured with this access role.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div.empty {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--grey-5);
|
||||||
|
padding-top: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
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"
|
||||||
|
import { setWith } from "lodash"
|
||||||
|
|
||||||
let flattenedPanel = flattenComponents(panelStructure.categories)
|
let flattenedPanel = flattenComponents(panelStructure.categories)
|
||||||
let categories = [
|
let categories = [
|
||||||
|
@ -69,7 +70,7 @@
|
||||||
) {
|
) {
|
||||||
selectedAsset.props._instanceName = value
|
selectedAsset.props._instanceName = value
|
||||||
} else {
|
} else {
|
||||||
selectedAsset[name] = value
|
setWith(selectedAsset, name.split("."), value, Object)
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto, url } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store, currentAssetName, selectedComponent } from "builderStore"
|
import {
|
||||||
|
store,
|
||||||
|
currentAssetName,
|
||||||
|
selectedComponent,
|
||||||
|
currentAssetId,
|
||||||
|
} from "builderStore"
|
||||||
import components from "./temporaryPanelStructure.js"
|
import components from "./temporaryPanelStructure.js"
|
||||||
import { DropdownMenu } from "@budibase/bbui"
|
import { DropdownMenu } from "@budibase/bbui"
|
||||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||||
|
@ -27,7 +32,7 @@
|
||||||
const onComponentChosen = component => {
|
const onComponentChosen = component => {
|
||||||
store.actions.components.create(component._component, component.presetProps)
|
store.actions.components.create(component._component, component.presetProps)
|
||||||
const path = store.actions.components.findRoute($selectedComponent)
|
const path = store.actions.components.findRoute($selectedComponent)
|
||||||
$goto(`./${$store.currentAssetId}/${path}`)
|
$goto(`./${$currentAssetId}/${path}`)
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { goto, params, url } from "@sveltech/routify"
|
import { goto, params, url } from "@sveltech/routify"
|
||||||
import { store, currentAsset, selectedComponent } from "builderStore"
|
import {
|
||||||
|
store,
|
||||||
|
allScreens,
|
||||||
|
currentAsset,
|
||||||
|
backendUiStore,
|
||||||
|
selectedAccessRole,
|
||||||
|
} from "builderStore"
|
||||||
import { FrontendTypes } from "constants"
|
import { FrontendTypes } from "constants"
|
||||||
import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte"
|
import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte"
|
||||||
import Layout from "components/userInterface/Layout.svelte"
|
import Layout from "components/userInterface/Layout.svelte"
|
||||||
import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
|
import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
|
||||||
import NewLayoutModal from "components/userInterface/NewLayoutModal.svelte"
|
import NewLayoutModal from "components/userInterface/NewLayoutModal.svelte"
|
||||||
import { Modal, Switcher } from "@budibase/bbui"
|
import { Modal, Switcher, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
|
@ -24,11 +30,38 @@
|
||||||
let routes = {}
|
let routes = {}
|
||||||
let tab = $params.assetType
|
let tab = $params.assetType
|
||||||
|
|
||||||
function navigate({ detail }) {
|
const navigate = ({ detail }) => {
|
||||||
if (!detail) return
|
if (!detail) {
|
||||||
|
return
|
||||||
|
}
|
||||||
$goto(`../${detail.heading.key}`)
|
$goto(`../${detail.heading.key}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateAccessRole = event => {
|
||||||
|
const role = event.target.value
|
||||||
|
|
||||||
|
// Select a valid screen with this new role - otherwise we'll not be
|
||||||
|
// able to change role at all because ComponentNavigationTree will kick us
|
||||||
|
// back the current role again because the same screen ID is still selected
|
||||||
|
const firstValidScreenId = $allScreens.find(
|
||||||
|
screen => screen.routing.roleId === role
|
||||||
|
)?._id
|
||||||
|
if (firstValidScreenId) {
|
||||||
|
store.actions.screens.select(firstValidScreenId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise clear the selected screen ID so that the first new valid screen
|
||||||
|
// can be selected by ComponentNavigationTree
|
||||||
|
else {
|
||||||
|
store.update(state => {
|
||||||
|
state.selectedScreenId = null
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedAccessRole.set(role)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
store.actions.routing.fetch()
|
store.actions.routing.fetch()
|
||||||
})
|
})
|
||||||
|
@ -41,11 +74,21 @@
|
||||||
on:click={modal.show}
|
on:click={modal.show}
|
||||||
data-cy="new-screen"
|
data-cy="new-screen"
|
||||||
class="ri-add-circle-fill" />
|
class="ri-add-circle-fill" />
|
||||||
{#if $currentAsset}
|
<div class="role-select">
|
||||||
<div class="nav-items-container">
|
<Select
|
||||||
<ComponentNavigationTree />
|
extraThin
|
||||||
</div>
|
secondary
|
||||||
{/if}
|
on:change={updateAccessRole}
|
||||||
|
value={$selectedAccessRole}
|
||||||
|
label="Filter by Access">
|
||||||
|
{#each $backendUiStore.roles as role}
|
||||||
|
<option value={role._id}>{role.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div class="nav-items-container">
|
||||||
|
<ComponentNavigationTree />
|
||||||
|
</div>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<NewScreenModal />
|
<NewScreenModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -54,8 +97,8 @@
|
||||||
on:click={modal.show}
|
on:click={modal.show}
|
||||||
data-cy="new-layout"
|
data-cy="new-layout"
|
||||||
class="ri-add-circle-fill" />
|
class="ri-add-circle-fill" />
|
||||||
{#each $store.layouts as layout (layout._id)}
|
{#each $store.layouts as layout, idx (layout._id)}
|
||||||
<Layout {layout} />
|
<Layout {layout} border={idx > 0} />
|
||||||
{/each}
|
{/each}
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<NewLayoutModal />
|
<NewLayoutModal />
|
||||||
|
@ -82,4 +125,8 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.role-select {
|
||||||
|
margin-bottom: var(--spacing-m);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
export let layout
|
export let layout
|
||||||
|
export let border
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let componentToDelete = ""
|
let componentToDelete = ""
|
||||||
|
@ -23,17 +24,17 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NavItem
|
<NavItem
|
||||||
border={false}
|
{border}
|
||||||
icon="ri-layout-3-line"
|
icon="ri-layout-3-line"
|
||||||
text={layout.name}
|
text={layout.name}
|
||||||
withArrow
|
withArrow
|
||||||
selected={$store.currentAssetId === layout._id}
|
selected={$store.selectedLayoutId === layout._id}
|
||||||
opened={$store.currentAssetId === layout._id}
|
opened={$store.selectedLayoutId === layout._id}
|
||||||
on:click={selectLayout}>
|
on:click={selectLayout}>
|
||||||
<LayoutDropdownMenu {layout} />
|
<LayoutDropdownMenu {layout} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
||||||
{#if $store.currentAssetId === layout._id && layout.props?._children}
|
{#if $store.selectedLayoutId === layout._id && layout.props?._children}
|
||||||
<ComponentTree
|
<ComponentTree
|
||||||
components={layout.props._children}
|
components={layout.props._children}
|
||||||
currentComponent={$selectedComponent}
|
currentComponent={$selectedComponent}
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import api from "builderStore/api"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { store, backendUiStore, allScreens } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { Input, ModalContent } from "@budibase/bbui"
|
import { Input, ModalContent } from "@budibase/bbui"
|
||||||
import analytics from "analytics"
|
|
||||||
|
|
||||||
const CONTAINER = "@budibase/standard-components/container"
|
|
||||||
|
|
||||||
let name = ""
|
let name = ""
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
try {
|
try {
|
||||||
await store.actions.layouts.save({ name })
|
const layout = await store.actions.layouts.save({ name })
|
||||||
$goto(`./${$store.currentAssetId}`)
|
$goto(`./${layout._id}`)
|
||||||
notifier.success(`Layout ${name} created successfully`)
|
notifier.success(`Layout ${name} created successfully`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifier.danger(`Error creating layout ${name}.`)
|
notifier.danger(`Error creating layout ${name}.`)
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store, backendUiStore, allScreens } from "builderStore"
|
import { store, backendUiStore, allScreens } from "builderStore"
|
||||||
import {
|
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
|
||||||
Input,
|
|
||||||
Button,
|
|
||||||
Spacer,
|
|
||||||
Select,
|
|
||||||
ModalContent,
|
|
||||||
Toggle,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import getTemplates from "builderStore/store/screenTemplates"
|
import getTemplates from "builderStore/store/screenTemplates"
|
||||||
import { some } from "lodash/fp"
|
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
|
||||||
const CONTAINER = "@budibase/standard-components/container"
|
const CONTAINER = "@budibase/standard-components/container"
|
||||||
|
|
||||||
|
@ -21,15 +15,13 @@
|
||||||
let templateIndex
|
let templateIndex
|
||||||
let draftScreen
|
let draftScreen
|
||||||
let createLink = true
|
let createLink = true
|
||||||
|
let roleId = "BASIC"
|
||||||
|
|
||||||
$: templates = getTemplates($store, $backendUiStore.tables)
|
$: templates = getTemplates($store, $backendUiStore.tables)
|
||||||
|
|
||||||
$: route = !route && $allScreens.length === 0 ? "*" : route
|
$: route = !route && $allScreens.length === 0 ? "*" : route
|
||||||
|
|
||||||
$: baseComponents = Object.values($store.components)
|
$: baseComponents = Object.values($store.components)
|
||||||
.filter(componentDefinition => componentDefinition.baseComponent)
|
.filter(componentDefinition => componentDefinition.baseComponent)
|
||||||
.map(c => c._component)
|
.map(c => c._component)
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (templates && templateIndex === undefined) {
|
if (templates && templateIndex === undefined) {
|
||||||
templateIndex = 0
|
templateIndex = 0
|
||||||
|
@ -56,10 +48,10 @@
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
if (!route) {
|
if (!route) {
|
||||||
routeError = "Url is required"
|
routeError = "URL is required"
|
||||||
} else {
|
} else {
|
||||||
if (routeNameExists(route)) {
|
if (routeExists(route, roleId)) {
|
||||||
routeError = "This url is already taken"
|
routeError = "This URL is already taken for this access role"
|
||||||
} else {
|
} else {
|
||||||
routeError = ""
|
routeError = ""
|
||||||
}
|
}
|
||||||
|
@ -69,8 +61,7 @@
|
||||||
|
|
||||||
draftScreen.props._instanceName = name
|
draftScreen.props._instanceName = name
|
||||||
draftScreen.props._component = baseComponent
|
draftScreen.props._component = baseComponent
|
||||||
// TODO: need to fix this up correctly
|
draftScreen.routing = { route, roleId }
|
||||||
draftScreen.routing = { route, roleId: "ADMIN" }
|
|
||||||
|
|
||||||
const createdScreen = await store.actions.screens.create(draftScreen)
|
const createdScreen = await store.actions.screens.create(draftScreen)
|
||||||
if (createLink) {
|
if (createLink) {
|
||||||
|
@ -85,12 +76,14 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$goto(`./screen/${createdScreen._id}`)
|
$goto(`./${createdScreen._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeNameExists = route => {
|
const routeExists = (route, roleId) => {
|
||||||
return $allScreens.some(
|
return $allScreens.some(
|
||||||
screen => screen.routing.route.toLowerCase() === route.toLowerCase()
|
screen =>
|
||||||
|
screen.routing.route.toLowerCase() === route.toLowerCase() &&
|
||||||
|
screen.routing.roleId === roleId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,14 +106,16 @@
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Input label="Name" bind:value={name} />
|
<Input label="Name" bind:value={name} />
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label="Url"
|
label="Url"
|
||||||
error={routeError}
|
error={routeError}
|
||||||
bind:value={route}
|
bind:value={route}
|
||||||
on:change={routeChanged} />
|
on:change={routeChanged} />
|
||||||
|
<Select label="Access" bind:value={roleId} secondary>
|
||||||
|
{#each $backendUiStore.roles as role}
|
||||||
|
<option value={role._id}>{role.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
<Toggle text="Create link in navigation bar" bind:checked={createLink} />
|
<Toggle text="Create link in navigation bar" bind:checked={createLink} />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script>
|
||||||
|
import { Select } from "@budibase/bbui"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
|
||||||
|
let roles = []
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select bind:value extraThin secondary on:change>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each $backendUiStore.roles as role}
|
||||||
|
<option value={role._id}>{role.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
|
@ -1,8 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { get } from "lodash"
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
import { FrontendTypes } from "constants"
|
import { FrontendTypes } from "constants"
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
import LayoutSelect from "./LayoutSelect.svelte"
|
import LayoutSelect from "./LayoutSelect.svelte"
|
||||||
|
import RoleSelect from "./RoleSelect.svelte"
|
||||||
import Input from "./PropertyPanelControls/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import { excludeProps } from "./propertyCategories.js"
|
import { excludeProps } from "./propertyCategories.js"
|
||||||
import { store, allScreens, currentAsset } from "builderStore"
|
import { store, allScreens, currentAsset } from "builderStore"
|
||||||
|
@ -16,7 +18,13 @@
|
||||||
export let displayNameField = false
|
export let displayNameField = false
|
||||||
export let assetInstance
|
export let assetInstance
|
||||||
|
|
||||||
let assetProps = ["title", "description", "route", "layoutId"]
|
let assetProps = [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"routing.route",
|
||||||
|
"layoutId",
|
||||||
|
"routing.roleId",
|
||||||
|
]
|
||||||
let duplicateName = false
|
let duplicateName = false
|
||||||
|
|
||||||
const propExistsOnComponentDef = prop =>
|
const propExistsOnComponentDef = prop =>
|
||||||
|
@ -28,7 +36,8 @@
|
||||||
|
|
||||||
const screenDefinition = [
|
const screenDefinition = [
|
||||||
{ key: "description", label: "Description", control: Input },
|
{ key: "description", label: "Description", control: Input },
|
||||||
{ key: "route", label: "Route", control: Input },
|
{ key: "routing.route", label: "Route", control: Input },
|
||||||
|
{ key: "routing.roleId", label: "Access", control: RoleSelect },
|
||||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -92,7 +101,7 @@
|
||||||
control={def.control}
|
control={def.control}
|
||||||
label={def.label}
|
label={def.label}
|
||||||
key={def.key}
|
key={def.key}
|
||||||
value={assetInstance[def.key]}
|
value={get(assetInstance, def.key)}
|
||||||
onChange={onScreenPropChange}
|
onChange={onScreenPropChange}
|
||||||
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
backendUiStore.actions.reset()
|
backendUiStore.actions.reset()
|
||||||
await store.actions.initialise(pkg)
|
await store.actions.initialise(pkg)
|
||||||
await automationStore.actions.fetch()
|
await automationStore.actions.fetch()
|
||||||
|
await backendUiStore.actions.roles.fetch()
|
||||||
return pkg
|
return pkg
|
||||||
} else {
|
} else {
|
||||||
throw new Error(pkg)
|
throw new Error(pkg)
|
||||||
|
@ -217,5 +218,6 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: var(--spacing-m);
|
bottom: var(--spacing-m);
|
||||||
left: var(--spacing-m);
|
left: var(--spacing-m);
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -26,11 +26,12 @@
|
||||||
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
||||||
if ($leftover) {
|
if ($leftover) {
|
||||||
// Get the correct screen children.
|
// Get the correct screen children.
|
||||||
const assetChildren = assetList.find(
|
const assetChildren =
|
||||||
asset =>
|
assetList.find(
|
||||||
asset._id === $params.asset ||
|
asset =>
|
||||||
asset._id === decodeURIComponent($params.asset)
|
asset._id === $params.asset ||
|
||||||
).props._children
|
asset._id === decodeURIComponent($params.asset)
|
||||||
|
)?.props._children ?? []
|
||||||
findComponent(componentIds, assetChildren)
|
findComponent(componentIds, assetChildren)
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { FrontendTypes } from "constants"
|
import { FrontendTypes } from "constants"
|
||||||
import CurrentItemPreview from "components/userInterface/AppPreview"
|
import CurrentItemPreview from "components/userInterface/AppPreview"
|
||||||
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
|
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
|
||||||
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
|
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
|
||||||
import { last } from "lodash/fp"
|
|
||||||
import FrontendNavigatePane from "components/userInterface/FrontendNavigatePane.svelte"
|
import FrontendNavigatePane from "components/userInterface/FrontendNavigatePane.svelte"
|
||||||
|
|
||||||
$: instance = $store.appInstance
|
$: instance = $store.appInstance
|
||||||
|
@ -36,7 +35,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-pane">
|
<div class="preview-pane">
|
||||||
{#if $store.currentAssetId && $store.currentAssetId.length > 0}
|
{#if $currentAsset}
|
||||||
<ComponentSelectionList />
|
<ComponentSelectionList />
|
||||||
<div class="preview-content">
|
<div class="preview-content">
|
||||||
<CurrentItemPreview />
|
<CurrentItemPreview />
|
||||||
|
|
|
@ -5,12 +5,32 @@
|
||||||
|
|
||||||
// Go to first layout
|
// Go to first layout
|
||||||
if ($params.assetType === FrontendTypes.LAYOUT) {
|
if ($params.assetType === FrontendTypes.LAYOUT) {
|
||||||
$goto(`../${$store.layouts[0]?._id}`)
|
// Try to use previously selected layout first
|
||||||
|
let id
|
||||||
|
if (
|
||||||
|
$store.selectedLayoutId &&
|
||||||
|
$store.layouts.find(layout => layout._id === $store.selectedLayoutId)
|
||||||
|
) {
|
||||||
|
id = $store.selectedLayoutId
|
||||||
|
} else {
|
||||||
|
id = $store.layouts[0]?._id
|
||||||
|
}
|
||||||
|
$goto(`../${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go to first screen
|
// Go to first screen
|
||||||
if ($params.assetType === FrontendTypes.SCREEN) {
|
if ($params.assetType === FrontendTypes.SCREEN) {
|
||||||
$goto(`../${$allScreens[0]?._id}`)
|
// Try to use previously selected layout first
|
||||||
|
let id
|
||||||
|
if (
|
||||||
|
$store.selectedScreenId &&
|
||||||
|
$allScreens.find(screen => screen._id === $store.selectedScreenId)
|
||||||
|
) {
|
||||||
|
id = $store.selectedScreenId
|
||||||
|
} else {
|
||||||
|
id = $allScreens[0]?._id
|
||||||
|
}
|
||||||
|
$goto(`../${id}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { setContext, onMount } from "svelte"
|
import { setContext, onMount } from "svelte"
|
||||||
import Component from "./Component.svelte"
|
import Component from "./Component.svelte"
|
||||||
import SDK from "../sdk"
|
import SDK from "../sdk"
|
||||||
import { createDataStore, routeStore, screenStore } from "../store"
|
import { createDataStore, initialise, screenStore } from "../store"
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
setContext("sdk", SDK)
|
setContext("sdk", SDK)
|
||||||
|
@ -14,13 +14,11 @@
|
||||||
|
|
||||||
// Load app config
|
// Load app config
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await routeStore.actions.fetchRoutes()
|
await initialise()
|
||||||
await screenStore.actions.fetchScreens()
|
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded && $screenStore.activeLayout}
|
{#if loaded && $screenStore.activeLayout}
|
||||||
<!-- // TODO: need to get the active screen as well -->
|
|
||||||
<Component definition={$screenStore.activeLayout.props} />
|
<Component definition={$screenStore.activeLayout.props} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -7,7 +7,15 @@
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
$: routerConfig = getRouterConfig($routeStore.routes)
|
// Only wrap this as an array to take advantage of svelte keying,
|
||||||
|
// to ensure the svelte-spa-router is fully remounted when route config
|
||||||
|
// changes
|
||||||
|
$: configs = [
|
||||||
|
{
|
||||||
|
routes: getRouterConfig($routeStore.routes),
|
||||||
|
id: $routeStore.routeSessionId,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const getRouterConfig = routes => {
|
const getRouterConfig = routes => {
|
||||||
let config = {}
|
let config = {}
|
||||||
|
@ -25,11 +33,11 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if routerConfig}
|
{#each configs as config (config.id)}
|
||||||
<div use:styleable={$component.styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<Router on:routeLoading={onRouteLoading} routes={routerConfig} />
|
<Router on:routeLoading={onRouteLoading} routes={config.routes} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/each}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
|
|
|
@ -1,18 +1,29 @@
|
||||||
import * as API from "../api"
|
import * as API from "../api"
|
||||||
import { getAppId } from "../utils/getAppId"
|
import { getAppId } from "../utils/getAppId"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
import { initialise } from "./initialise"
|
||||||
|
import { routeStore } from "./routes"
|
||||||
|
|
||||||
const createAuthStore = () => {
|
const createAuthStore = () => {
|
||||||
const store = writable("")
|
const store = writable("")
|
||||||
|
|
||||||
|
const goToDefaultRoute = () => {
|
||||||
|
// Setting the active route forces an update of the active screen ID,
|
||||||
|
// even if we're on the same URL
|
||||||
|
routeStore.actions.setActiveRoute("/")
|
||||||
|
|
||||||
|
// Navigating updates the URL to reflect this route
|
||||||
|
routeStore.actions.navigate("/")
|
||||||
|
}
|
||||||
const logIn = async ({ email, password }) => {
|
const logIn = async ({ email, password }) => {
|
||||||
const user = await API.logIn({ email, password })
|
const user = await API.logIn({ email, password })
|
||||||
if (!user.error) {
|
if (!user.error) {
|
||||||
store.set(user.token)
|
store.set(user.token)
|
||||||
location.reload()
|
await initialise()
|
||||||
|
goToDefaultRoute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const logOut = () => {
|
const logOut = async () => {
|
||||||
store.set("")
|
store.set("")
|
||||||
const appId = getAppId()
|
const appId = getAppId()
|
||||||
if (appId) {
|
if (appId) {
|
||||||
|
@ -20,7 +31,8 @@ const createAuthStore = () => {
|
||||||
window.document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
|
window.document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
location.reload()
|
await initialise()
|
||||||
|
goToDefaultRoute()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -6,3 +6,6 @@ export { bindingStore } from "./binding"
|
||||||
|
|
||||||
// Data stores are layered and duplicated, so it is not a singleton
|
// Data stores are layered and duplicated, so it is not a singleton
|
||||||
export { createDataStore, dataStore } from "./data"
|
export { createDataStore, dataStore } from "./data"
|
||||||
|
|
||||||
|
// Initialises an app by loading screens and routes
|
||||||
|
export { initialise } from "./initialise"
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { routeStore } from "./routes"
|
||||||
|
import { screenStore } from "./screens"
|
||||||
|
|
||||||
|
export async function initialise() {
|
||||||
|
await routeStore.actions.fetchRoutes()
|
||||||
|
await screenStore.actions.fetchScreens()
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ const createRouteStore = () => {
|
||||||
routes: [],
|
routes: [],
|
||||||
routeParams: {},
|
routeParams: {},
|
||||||
activeRoute: null,
|
activeRoute: null,
|
||||||
|
routeSessionId: Math.random(),
|
||||||
}
|
}
|
||||||
const store = writable(initialState)
|
const store = writable(initialState)
|
||||||
|
|
||||||
|
@ -21,8 +22,15 @@ const createRouteStore = () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Sort route by paths so that the router matches correctly
|
||||||
|
routes.sort((a, b) => {
|
||||||
|
return a.path > b.path ? -1 : 1
|
||||||
|
})
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.routes = routes
|
state.routes = routes
|
||||||
|
state.routeSessionId = Math.random()
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ const {
|
||||||
} = require("../../constants/screens")
|
} = require("../../constants/screens")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
const { recurseMustache } = require("../../utilities/mustache")
|
const { recurseMustache } = require("../../utilities/mustache")
|
||||||
const { generateAssetCss } = require("../../utilities/builder/generateCss")
|
|
||||||
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
@ -131,12 +130,6 @@ exports.fetchAppPackage = async function(ctx) {
|
||||||
const application = await db.get(ctx.params.appId)
|
const application = await db.get(ctx.params.appId)
|
||||||
const [layouts, screens] = await Promise.all([getLayouts(db), getScreens(db)])
|
const [layouts, screens] = await Promise.all([getLayouts(db), getScreens(db)])
|
||||||
|
|
||||||
for (let layout of layouts) {
|
|
||||||
layout._css = generateAssetCss([layout.props])
|
|
||||||
}
|
|
||||||
for (let screen of screens) {
|
|
||||||
screen._css = generateAssetCss([screen.props])
|
|
||||||
}
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
application,
|
application,
|
||||||
screens,
|
screens,
|
||||||
|
@ -230,10 +223,6 @@ const createEmptyAppPackage = async (ctx, app) => {
|
||||||
screensAndLayouts.push(loginScreen)
|
screensAndLayouts.push(loginScreen)
|
||||||
|
|
||||||
await db.bulkDocs(screensAndLayouts)
|
await db.bulkDocs(screensAndLayouts)
|
||||||
// at the end add CSS to all the structures
|
await compileStaticAssets(app._id)
|
||||||
for (let asset of screensAndLayouts) {
|
|
||||||
asset._css = generateAssetCss([asset.props])
|
|
||||||
}
|
|
||||||
await compileStaticAssets(app._id, screensAndLayouts)
|
|
||||||
return newAppFolder
|
return newAppFolder
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ exports.authenticate = async ctx => {
|
||||||
userId: dbUser._id,
|
userId: dbUser._id,
|
||||||
roleId: dbUser.roleId,
|
roleId: dbUser.roleId,
|
||||||
version: app.version,
|
version: app.version,
|
||||||
permissions: dbUser.permissions || [],
|
|
||||||
}
|
}
|
||||||
// if in cloud add the user api key
|
// if in cloud add the user api key
|
||||||
if (env.CLOUD) {
|
if (env.CLOUD) {
|
||||||
|
|
|
@ -7,8 +7,6 @@ const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||||
const PouchDB = require("../../../db")
|
const PouchDB = require("../../../db")
|
||||||
const env = require("../../../environment")
|
const env = require("../../../environment")
|
||||||
|
|
||||||
const EXCLUDED_DIRECTORIES = ["css"]
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finalises the deployment, updating the quota for the user API key
|
* Finalises the deployment, updating the quota for the user API key
|
||||||
* The verification process returns the levels to update to.
|
* The verification process returns the levels to update to.
|
||||||
|
@ -140,15 +138,9 @@ exports.uploadAppAssets = async function({ appId, bucket, accountId }) {
|
||||||
|
|
||||||
let uploads = []
|
let uploads = []
|
||||||
|
|
||||||
// Upload HTML, CSS and JS of the web app
|
// Upload HTML and JS of the web app
|
||||||
walkDir(appAssetsPath, function(filePath) {
|
walkDir(appAssetsPath, function(filePath) {
|
||||||
const filePathParts = filePath.split("/")
|
const filePathParts = filePath.split("/")
|
||||||
const publicIndex = filePathParts.indexOf("public")
|
|
||||||
const directory = filePathParts[publicIndex + 1]
|
|
||||||
// don't include these top level directories
|
|
||||||
if (EXCLUDED_DIRECTORIES.indexOf(directory) !== -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const appAssetUpload = prepareUploadForS3({
|
const appAssetUpload = prepareUploadForS3({
|
||||||
file: {
|
file: {
|
||||||
path: filePath,
|
path: filePath,
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
const { BUILTIN_PERMISSIONS } = require("../../utilities/security/permissions")
|
||||||
|
|
||||||
|
exports.fetch = async function(ctx) {
|
||||||
|
// TODO: need to build out custom permissions
|
||||||
|
ctx.body = Object.values(BUILTIN_PERMISSIONS)
|
||||||
|
}
|
|
@ -4,7 +4,40 @@ const {
|
||||||
Role,
|
Role,
|
||||||
getRole,
|
getRole,
|
||||||
} = require("../../utilities/security/roles")
|
} = require("../../utilities/security/roles")
|
||||||
const { generateRoleID, getRoleParams } = require("../../db/utils")
|
const {
|
||||||
|
generateRoleID,
|
||||||
|
getRoleParams,
|
||||||
|
getUserParams,
|
||||||
|
ViewNames,
|
||||||
|
} = require("../../db/utils")
|
||||||
|
|
||||||
|
const UpdateRolesOptions = {
|
||||||
|
CREATED: "created",
|
||||||
|
REMOVED: "removed",
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateRolesOnUserTable(db, roleId, updateOption) {
|
||||||
|
const table = await db.get(ViewNames.USERS)
|
||||||
|
const schema = table.schema
|
||||||
|
const remove = updateOption === UpdateRolesOptions.REMOVED
|
||||||
|
let updated = false
|
||||||
|
for (let prop of Object.keys(schema)) {
|
||||||
|
if (prop === "roleId") {
|
||||||
|
updated = true
|
||||||
|
const constraints = schema[prop].constraints
|
||||||
|
const indexOf = constraints.inclusion.indexOf(roleId)
|
||||||
|
if (remove && indexOf !== -1) {
|
||||||
|
constraints.inclusion.splice(indexOf, 1)
|
||||||
|
} else if (!remove && indexOf === -1) {
|
||||||
|
constraints.inclusion.push(roleId)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updated) {
|
||||||
|
await db.put(table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
exports.fetch = async function(ctx) {
|
exports.fetch = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.appId)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
|
@ -15,7 +48,13 @@ exports.fetch = async function(ctx) {
|
||||||
)
|
)
|
||||||
const customRoles = body.rows.map(row => row.doc)
|
const customRoles = body.rows.map(row => row.doc)
|
||||||
|
|
||||||
const staticRoles = [BUILTIN_ROLES.ADMIN, BUILTIN_ROLES.POWER]
|
// exclude internal roles like builder
|
||||||
|
const staticRoles = [
|
||||||
|
BUILTIN_ROLES.ADMIN,
|
||||||
|
BUILTIN_ROLES.POWER,
|
||||||
|
BUILTIN_ROLES.BASIC,
|
||||||
|
BUILTIN_ROLES.PUBLIC,
|
||||||
|
]
|
||||||
ctx.body = [...staticRoles, ...customRoles]
|
ctx.body = [...staticRoles, ...customRoles]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,13 +64,18 @@ exports.find = async function(ctx) {
|
||||||
|
|
||||||
exports.save = async function(ctx) {
|
exports.save = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.appId)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
|
let { _id, name, inherits, permissionId } = ctx.request.body
|
||||||
let id = ctx.request.body._id || generateRoleID()
|
if (!_id) {
|
||||||
const role = new Role(id, ctx.request.body.name, ctx.request.body.inherits)
|
_id = generateRoleID()
|
||||||
|
}
|
||||||
|
const role = new Role(_id, name)
|
||||||
|
.addPermission(permissionId)
|
||||||
|
.addInheritance(inherits)
|
||||||
if (ctx.request.body._rev) {
|
if (ctx.request.body._rev) {
|
||||||
role._rev = ctx.request.body._rev
|
role._rev = ctx.request.body._rev
|
||||||
}
|
}
|
||||||
const result = await db.put(role)
|
const result = await db.put(role)
|
||||||
|
await updateRolesOnUserTable(db, _id, UpdateRolesOptions.CREATED)
|
||||||
role._rev = result.rev
|
role._rev = result.rev
|
||||||
ctx.body = role
|
ctx.body = role
|
||||||
ctx.message = `Role '${role.name}' created successfully.`
|
ctx.message = `Role '${role.name}' created successfully.`
|
||||||
|
@ -39,7 +83,26 @@ exports.save = async function(ctx) {
|
||||||
|
|
||||||
exports.destroy = async function(ctx) {
|
exports.destroy = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.appId)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
await db.remove(ctx.params.roleId, ctx.params.rev)
|
const roleId = ctx.params.roleId
|
||||||
ctx.message = `Role ${ctx.params.id} deleted successfully`
|
// first check no users actively attached to role
|
||||||
|
const users = (
|
||||||
|
await db.allDocs(
|
||||||
|
getUserParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).rows.map(row => row.doc)
|
||||||
|
const usersWithRole = users.filter(user => user.roleId === roleId)
|
||||||
|
if (usersWithRole.length !== 0) {
|
||||||
|
ctx.throw("Cannot delete role when it is in use.")
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.remove(roleId, ctx.params.rev)
|
||||||
|
await updateRolesOnUserTable(
|
||||||
|
db,
|
||||||
|
ctx.params.roleId,
|
||||||
|
UpdateRolesOptions.REMOVED
|
||||||
|
)
|
||||||
|
ctx.message = `Role ${ctx.params.roleId} deleted successfully`
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const {
|
||||||
ViewNames,
|
ViewNames,
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
const usersController = require("./user")
|
const usersController = require("./user")
|
||||||
const { cloneDeep } = require("lodash")
|
const { coerceRowValues } = require("../../utilities")
|
||||||
|
|
||||||
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
|
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
|
||||||
|
|
||||||
|
@ -29,6 +29,28 @@ validateJs.extend(validateJs.validators.datetime, {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function findRow(db, appId, tableId, rowId) {
|
||||||
|
let row
|
||||||
|
if (tableId === ViewNames.USERS) {
|
||||||
|
let ctx = {
|
||||||
|
params: {
|
||||||
|
userId: rowId,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
appId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await usersController.find(ctx)
|
||||||
|
row = ctx.body
|
||||||
|
} else {
|
||||||
|
row = await db.get(rowId)
|
||||||
|
}
|
||||||
|
if (row.tableId !== tableId) {
|
||||||
|
throw "Supplied tableId does not match the rows tableId"
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
exports.patch = async function(ctx) {
|
exports.patch = async function(ctx) {
|
||||||
const appId = ctx.user.appId
|
const appId = ctx.user.appId
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
|
@ -64,6 +86,13 @@ exports.patch = async function(ctx) {
|
||||||
tableId: row.tableId,
|
tableId: row.tableId,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Creation of a new user goes to the user controller
|
||||||
|
if (row.tableId === ViewNames.USERS) {
|
||||||
|
await usersController.update(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const response = await db.put(row)
|
const response = await db.put(row)
|
||||||
row._rev = response.rev
|
row._rev = response.rev
|
||||||
row.type = "row"
|
row.type = "row"
|
||||||
|
@ -80,19 +109,25 @@ 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
|
||||||
|
|
||||||
|
// TODO: find usage of this and break out into own endpoint
|
||||||
if (ctx.request.body.type === "delete") {
|
if (ctx.request.body.type === "delete") {
|
||||||
await bulkDelete(ctx)
|
await bulkDelete(ctx)
|
||||||
ctx.body = ctx.request.body.rows
|
ctx.body = ctx.request.body.rows
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the row obj had an _id then it will have been retrieved
|
||||||
|
const existingRow = ctx.preExisting
|
||||||
|
if (existingRow) {
|
||||||
|
ctx.params.id = row._id
|
||||||
|
await exports.patch(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!row._rev && !row._id) {
|
if (!row._rev && !row._id) {
|
||||||
row._id = generateRowID(row.tableId)
|
row._id = generateRowID(row.tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the row obj had an _id then it will have been retrieved
|
|
||||||
const existingRow = ctx.preExisting
|
|
||||||
|
|
||||||
const table = await db.get(row.tableId)
|
const table = await db.get(row.tableId)
|
||||||
|
|
||||||
row = coerceRowValues(row, table)
|
row = coerceRowValues(row, table)
|
||||||
|
@ -121,39 +156,22 @@ exports.save = async function(ctx) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Creation of a new user goes to the user controller
|
// Creation of a new user goes to the user controller
|
||||||
if (!existingRow && row.tableId === ViewNames.USERS) {
|
if (row.tableId === ViewNames.USERS) {
|
||||||
try {
|
await usersController.create(ctx)
|
||||||
await usersController.create(ctx)
|
|
||||||
} catch (err) {
|
|
||||||
ctx.body = { errors: [err.message] }
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingRow) {
|
|
||||||
const response = await db.put(row)
|
|
||||||
row._rev = response.rev
|
|
||||||
row.type = "row"
|
|
||||||
ctx.body = row
|
|
||||||
ctx.status = 200
|
|
||||||
ctx.message = `${table.name} updated successfully.`
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
row.type = "row"
|
row.type = "row"
|
||||||
const response = await db.post(row)
|
const response = await db.put(row)
|
||||||
row._rev = response.rev
|
row._rev = response.rev
|
||||||
|
|
||||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
|
||||||
ctx.body = row
|
ctx.body = row
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = `${table.name} created successfully`
|
ctx.message = `${table.name} saved successfully`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchView = async function(ctx) {
|
exports.fetchView = async function(ctx) {
|
||||||
const appId = ctx.user.appId
|
const appId = ctx.user.appId
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const { calculation, group, field } = ctx.query
|
|
||||||
const viewName = ctx.params.viewName
|
const viewName = ctx.params.viewName
|
||||||
|
|
||||||
// if this is a table view being looked for just transfer to that
|
// if this is a table view being looked for just transfer to that
|
||||||
|
@ -163,6 +181,8 @@ exports.fetchView = async function(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const db = new CouchDB(appId)
|
||||||
|
const { calculation, group, field } = ctx.query
|
||||||
const response = await db.query(`database/${viewName}`, {
|
const response = await db.query(`database/${viewName}`, {
|
||||||
include_docs: !calculation,
|
include_docs: !calculation,
|
||||||
group,
|
group,
|
||||||
|
@ -197,41 +217,32 @@ exports.fetchView = async function(ctx) {
|
||||||
|
|
||||||
exports.fetchTableRows = async function(ctx) {
|
exports.fetchTableRows = async function(ctx) {
|
||||||
const appId = ctx.user.appId
|
const appId = ctx.user.appId
|
||||||
const db = new CouchDB(appId)
|
// special case for users, fetch through the user controller
|
||||||
const response = await db.allDocs(
|
let rows
|
||||||
getRowParams(ctx.params.tableId, null, {
|
if (ctx.params.tableId === ViewNames.USERS) {
|
||||||
include_docs: true,
|
await usersController.fetch(ctx)
|
||||||
})
|
rows = ctx.body
|
||||||
)
|
} else {
|
||||||
ctx.body = response.rows.map(row => row.doc)
|
const db = new CouchDB(appId)
|
||||||
ctx.body = await linkRows.attachLinkInfo(
|
const response = await db.allDocs(
|
||||||
appId,
|
getRowParams(ctx.params.tableId, null, {
|
||||||
response.rows.map(row => row.doc)
|
include_docs: true,
|
||||||
)
|
})
|
||||||
}
|
)
|
||||||
|
rows = response.rows.map(row => row.doc)
|
||||||
exports.search = async function(ctx) {
|
}
|
||||||
const appId = ctx.user.appId
|
ctx.body = await linkRows.attachLinkInfo(appId, rows)
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const response = await db.allDocs({
|
|
||||||
include_docs: true,
|
|
||||||
...ctx.request.body,
|
|
||||||
})
|
|
||||||
ctx.body = await linkRows.attachLinkInfo(
|
|
||||||
appId,
|
|
||||||
response.rows.map(row => row.doc)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function(ctx) {
|
exports.find = async function(ctx) {
|
||||||
const appId = ctx.user.appId
|
const appId = ctx.user.appId
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const row = await db.get(ctx.params.rowId)
|
try {
|
||||||
if (row.tableId !== ctx.params.tableId) {
|
const row = await findRow(db, appId, ctx.params.tableId, ctx.params.rowId)
|
||||||
ctx.throw(400, "Supplied tableId does not match the rows tableId")
|
ctx.body = await linkRows.attachLinkInfo(appId, row)
|
||||||
return
|
} catch (err) {
|
||||||
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
ctx.body = await linkRows.attachLinkInfo(appId, row)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function(ctx) {
|
exports.destroy = async function(ctx) {
|
||||||
|
@ -297,7 +308,10 @@ exports.fetchEnrichedRow = async function(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// need table to work out where links go in row
|
// need table to work out where links go in row
|
||||||
const [table, row] = await Promise.all([db.get(tableId), db.get(rowId)])
|
let [table, row] = await Promise.all([
|
||||||
|
db.get(tableId),
|
||||||
|
findRow(db, appId, tableId, rowId),
|
||||||
|
])
|
||||||
// get the link docs
|
// get the link docs
|
||||||
const linkVals = await linkRows.getLinkDocuments({
|
const linkVals = await linkRows.getLinkDocuments({
|
||||||
appId,
|
appId,
|
||||||
|
@ -327,68 +341,6 @@ exports.fetchEnrichedRow = async function(ctx) {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
function coerceRowValues(record, table) {
|
|
||||||
const row = cloneDeep(record)
|
|
||||||
for (let [key, value] of Object.entries(row)) {
|
|
||||||
const field = table.schema[key]
|
|
||||||
if (!field) continue
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) {
|
|
||||||
row[key] = TYPE_TRANSFORM_MAP[field.type][value]
|
|
||||||
} else if (TYPE_TRANSFORM_MAP[field.type].parse) {
|
|
||||||
row[key] = TYPE_TRANSFORM_MAP[field.type].parse(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
const TYPE_TRANSFORM_MAP = {
|
|
||||||
link: {
|
|
||||||
"": [],
|
|
||||||
[null]: [],
|
|
||||||
[undefined]: undefined,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
"": "",
|
|
||||||
[null]: "",
|
|
||||||
[undefined]: undefined,
|
|
||||||
},
|
|
||||||
string: {
|
|
||||||
"": "",
|
|
||||||
[null]: "",
|
|
||||||
[undefined]: undefined,
|
|
||||||
},
|
|
||||||
longform: {
|
|
||||||
"": "",
|
|
||||||
[null]: "",
|
|
||||||
[undefined]: undefined,
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
"": null,
|
|
||||||
[null]: null,
|
|
||||||
[undefined]: undefined,
|
|
||||||
parse: n => parseFloat(n),
|
|
||||||
},
|
|
||||||
datetime: {
|
|
||||||
"": null,
|
|
||||||
[undefined]: undefined,
|
|
||||||
[null]: null,
|
|
||||||
},
|
|
||||||
attachment: {
|
|
||||||
"": [],
|
|
||||||
[null]: [],
|
|
||||||
[undefined]: undefined,
|
|
||||||
},
|
|
||||||
boolean: {
|
|
||||||
"": null,
|
|
||||||
[null]: null,
|
|
||||||
[undefined]: undefined,
|
|
||||||
true: true,
|
|
||||||
false: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bulkDelete(ctx) {
|
async function bulkDelete(ctx) {
|
||||||
const appId = ctx.user.appId
|
const appId = ctx.user.appId
|
||||||
const { rows } = ctx.request.body
|
const { rows } = ctx.request.body
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
||||||
const { AccessController } = require("../../utilities/security/roles")
|
const { AccessController } = require("../../utilities/security/roles")
|
||||||
const { generateAssetCss } = require("../../utilities/builder/generateCss")
|
|
||||||
const compileStaticAssets = require("../../utilities/builder/compileStaticAssets")
|
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const appId = ctx.user.appId
|
const appId = ctx.user.appId
|
||||||
|
@ -32,10 +30,6 @@ exports.save = async ctx => {
|
||||||
}
|
}
|
||||||
const response = await db.put(screen)
|
const response = await db.put(screen)
|
||||||
|
|
||||||
// update CSS so client doesn't need to make a call directly after
|
|
||||||
screen._css = generateAssetCss([screen.props])
|
|
||||||
await compileStaticAssets(appId, screen)
|
|
||||||
|
|
||||||
ctx.message = `Screen ${screen.name} saved.`
|
ctx.message = `Screen ${screen.name} saved.`
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
...screen,
|
...screen,
|
||||||
|
|
|
@ -16,19 +16,10 @@ const CouchDB = require("../../../db")
|
||||||
const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
|
const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
|
||||||
const fileProcessor = require("../../../utilities/fileProcessor")
|
const fileProcessor = require("../../../utilities/fileProcessor")
|
||||||
const env = require("../../../environment")
|
const env = require("../../../environment")
|
||||||
const { generateAssetCss } = require("../../../utilities/builder/generateCss")
|
|
||||||
const compileStaticAssets = require("../../../utilities/builder/compileStaticAssets")
|
|
||||||
|
|
||||||
// this was the version before we started versioning the component library
|
// this was the version before we started versioning the component library
|
||||||
const COMP_LIB_BASE_APP_VERSION = "0.2.5"
|
const COMP_LIB_BASE_APP_VERSION = "0.2.5"
|
||||||
|
|
||||||
exports.generateCss = async function(ctx) {
|
|
||||||
const structure = ctx.request.body
|
|
||||||
structure._css = generateAssetCss([structure.props])
|
|
||||||
await compileStaticAssets(ctx.appId, structure)
|
|
||||||
ctx.body = { css: structure._css }
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.serveBuilder = async function(ctx) {
|
exports.serveBuilder = async function(ctx) {
|
||||||
let builderPath = resolve(__dirname, "../../../../builder")
|
let builderPath = resolve(__dirname, "../../../../builder")
|
||||||
if (ctx.file === "index.html") {
|
if (ctx.file === "index.html") {
|
||||||
|
|
|
@ -22,13 +22,7 @@
|
||||||
|
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="icon" type="image/png" href={favicon} />
|
<link rel="icon" type="image/png" href={favicon} />
|
||||||
|
|
||||||
<link rel="stylesheet" href={publicPath('bundle.css')} />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Roboto+Mono" />
|
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -1,40 +1,45 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const bcrypt = require("../../utilities/bcrypt")
|
const bcrypt = require("../../utilities/bcrypt")
|
||||||
const { generateUserID, getUserParams, ViewNames } = require("../../db/utils")
|
const { generateUserID, getUserParams, ViewNames } = require("../../db/utils")
|
||||||
const { BUILTIN_ROLE_ID_ARRAY } = require("../../utilities/security/roles")
|
const { getRole } = require("../../utilities/security/roles")
|
||||||
const {
|
|
||||||
BUILTIN_PERMISSION_NAMES,
|
|
||||||
} = require("../../utilities/security/permissions")
|
|
||||||
|
|
||||||
exports.fetch = async function(ctx) {
|
exports.fetch = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.user.appId)
|
const database = new CouchDB(ctx.user.appId)
|
||||||
const data = await database.allDocs(
|
const users = (
|
||||||
getUserParams("", {
|
await database.allDocs(
|
||||||
include_docs: true,
|
getUserParams(null, {
|
||||||
})
|
include_docs: true,
|
||||||
)
|
})
|
||||||
ctx.body = data.rows.map(row => row.doc)
|
)
|
||||||
|
).rows.map(row => row.doc)
|
||||||
|
// user hashed password shouldn't ever be returned
|
||||||
|
for (let user of users) {
|
||||||
|
delete user.password
|
||||||
|
}
|
||||||
|
ctx.body = users
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = async function(ctx) {
|
exports.create = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.appId)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
const { email, password, roleId, permissions } = ctx.request.body
|
const { email, password, roleId } = ctx.request.body
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
ctx.throw(400, "email and Password Required.")
|
ctx.throw(400, "email and Password Required.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const role = await checkRole(db, roleId)
|
const role = await getRole(ctx.user.appId, roleId)
|
||||||
|
|
||||||
if (!role) ctx.throw(400, "Invalid Role")
|
if (!role) ctx.throw(400, "Invalid Role")
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash(password)
|
||||||
const user = {
|
const user = {
|
||||||
|
...ctx.request.body,
|
||||||
|
// these must all be after the object spread, make sure
|
||||||
|
// any values are overwritten, generateUserID will always
|
||||||
|
// generate the same ID for the user as it is not UUID based
|
||||||
_id: generateUserID(email),
|
_id: generateUserID(email),
|
||||||
email,
|
|
||||||
password: await bcrypt.hash(password),
|
|
||||||
type: "user",
|
type: "user",
|
||||||
roleId,
|
password: hashedPassword,
|
||||||
permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER],
|
|
||||||
tableId: ViewNames.USERS,
|
tableId: ViewNames.USERS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +64,12 @@ exports.create = async function(ctx) {
|
||||||
exports.update = async function(ctx) {
|
exports.update = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.appId)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
const user = ctx.request.body
|
const user = ctx.request.body
|
||||||
const dbUser = db.get(ctx.request.body._id)
|
const dbUser = await db.get(ctx.request.body._id)
|
||||||
|
if (user.password) {
|
||||||
|
user.password = await bcrypt.hash(user.password)
|
||||||
|
} else {
|
||||||
|
delete user.password
|
||||||
|
}
|
||||||
const newData = { ...dbUser, ...user }
|
const newData = { ...dbUser, ...user }
|
||||||
|
|
||||||
const response = await db.put(newData)
|
const response = await db.put(newData)
|
||||||
|
@ -79,22 +89,12 @@ exports.destroy = async function(ctx) {
|
||||||
|
|
||||||
exports.find = async function(ctx) {
|
exports.find = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.user.appId)
|
const database = new CouchDB(ctx.user.appId)
|
||||||
const user = await database.get(generateUserID(ctx.params.email))
|
let lookup = ctx.params.email
|
||||||
ctx.body = {
|
? generateUserID(ctx.params.email)
|
||||||
email: user.email,
|
: ctx.params.userId
|
||||||
name: user.name,
|
const user = await database.get(lookup)
|
||||||
_rev: user._rev,
|
if (user) {
|
||||||
|
delete user.password
|
||||||
}
|
}
|
||||||
}
|
ctx.body = user
|
||||||
|
|
||||||
const checkRole = async (db, roleId) => {
|
|
||||||
if (!roleId) return
|
|
||||||
if (BUILTIN_ROLE_ID_ARRAY.indexOf(roleId) !== -1) {
|
|
||||||
return {
|
|
||||||
_id: roleId,
|
|
||||||
name: roleId,
|
|
||||||
permissions: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await db.get(roleId)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ const apiKeysRoutes = require("./apikeys")
|
||||||
const templatesRoutes = require("./templates")
|
const templatesRoutes = require("./templates")
|
||||||
const analyticsRoutes = require("./analytics")
|
const analyticsRoutes = require("./analytics")
|
||||||
const routingRoutes = require("./routing")
|
const routingRoutes = require("./routing")
|
||||||
|
const permissionRoutes = require("./permission")
|
||||||
|
|
||||||
exports.mainRoutes = [
|
exports.mainRoutes = [
|
||||||
deployRoutes,
|
deployRoutes,
|
||||||
|
@ -32,6 +33,7 @@ exports.mainRoutes = [
|
||||||
analyticsRoutes,
|
analyticsRoutes,
|
||||||
webhookRoutes,
|
webhookRoutes,
|
||||||
routingRoutes,
|
routingRoutes,
|
||||||
|
permissionRoutes,
|
||||||
// these need to be handled last as they still use /api/:tableId
|
// these need to be handled last as they still use /api/:tableId
|
||||||
// this could be breaking as koa may recognise other routes as this
|
// this could be breaking as koa may recognise other routes as this
|
||||||
tableRoutes,
|
tableRoutes,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
const Router = require("@koa/router")
|
||||||
|
const controller = require("../controllers/permission")
|
||||||
|
const authorized = require("../../middleware/authorized")
|
||||||
|
const { BUILDER } = require("../../utilities/security/permissions")
|
||||||
|
|
||||||
|
const router = Router()
|
||||||
|
|
||||||
|
router.get("/api/permissions", authorized(BUILDER), controller.fetch)
|
||||||
|
|
||||||
|
module.exports = router
|
|
@ -2,11 +2,27 @@ const Router = require("@koa/router")
|
||||||
const controller = require("../controllers/role")
|
const controller = require("../controllers/role")
|
||||||
const authorized = require("../../middleware/authorized")
|
const authorized = require("../../middleware/authorized")
|
||||||
const { BUILDER } = require("../../utilities/security/permissions")
|
const { BUILDER } = require("../../utilities/security/permissions")
|
||||||
|
const Joi = require("joi")
|
||||||
|
const joiValidator = require("../../middleware/joi-validator")
|
||||||
|
const {
|
||||||
|
BUILTIN_PERMISSION_IDS,
|
||||||
|
} = require("../../utilities/security/permissions")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
function generateValidator() {
|
||||||
|
// prettier-ignore
|
||||||
|
return joiValidator.body(Joi.object({
|
||||||
|
_id: Joi.string().optional(),
|
||||||
|
_rev: Joi.string().optional(),
|
||||||
|
name: Joi.string().required(),
|
||||||
|
permissionId: Joi.string().valid(...Object.values(BUILTIN_PERMISSION_IDS)).required(),
|
||||||
|
inherits: Joi.string().optional(),
|
||||||
|
}).unknown(true))
|
||||||
|
}
|
||||||
|
|
||||||
router
|
router
|
||||||
.post("/api/roles", authorized(BUILDER), controller.save)
|
.post("/api/roles", authorized(BUILDER), generateValidator(), controller.save)
|
||||||
.get("/api/roles", authorized(BUILDER), controller.fetch)
|
.get("/api/roles", authorized(BUILDER), controller.fetch)
|
||||||
.get("/api/roles/:roleId", authorized(BUILDER), controller.find)
|
.get("/api/roles/:roleId", authorized(BUILDER), controller.find)
|
||||||
.delete("/api/roles/:roleId/:rev", authorized(BUILDER), controller.destroy)
|
.delete("/api/roles/:roleId/:rev", authorized(BUILDER), controller.destroy)
|
||||||
|
|
|
@ -25,7 +25,6 @@ router
|
||||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||||
rowController.find
|
rowController.find
|
||||||
)
|
)
|
||||||
.post("/api/rows/search", rowController.search)
|
|
||||||
.post(
|
.post(
|
||||||
"/api/:tableId/rows",
|
"/api/:tableId/rows",
|
||||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||||
|
|
|
@ -10,7 +10,6 @@ const router = Router()
|
||||||
function generateSaveValidation() {
|
function generateSaveValidation() {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return joiValidator.body(Joi.object({
|
return joiValidator.body(Joi.object({
|
||||||
_css: Joi.string().allow(""),
|
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
routing: Joi.object({
|
routing: Joi.object({
|
||||||
route: Joi.string().required(),
|
route: Joi.string().required(),
|
||||||
|
|
|
@ -5,22 +5,6 @@ const env = require("../../environment")
|
||||||
const authorized = require("../../middleware/authorized")
|
const authorized = require("../../middleware/authorized")
|
||||||
const { BUILDER } = require("../../utilities/security/permissions")
|
const { BUILDER } = require("../../utilities/security/permissions")
|
||||||
const usage = require("../../middleware/usageQuota")
|
const usage = require("../../middleware/usageQuota")
|
||||||
const joiValidator = require("../../middleware/joi-validator")
|
|
||||||
const Joi = require("joi")
|
|
||||||
|
|
||||||
function generateCssValidator() {
|
|
||||||
return joiValidator.body(
|
|
||||||
Joi.object({
|
|
||||||
_id: Joi.string().required(),
|
|
||||||
_rev: Joi.string().required(),
|
|
||||||
props: Joi.object()
|
|
||||||
.required()
|
|
||||||
.unknown(true),
|
|
||||||
})
|
|
||||||
.required()
|
|
||||||
.unknown(true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
@ -40,12 +24,6 @@ if (env.NODE_ENV !== "production") {
|
||||||
}
|
}
|
||||||
|
|
||||||
router
|
router
|
||||||
.post(
|
|
||||||
"/api/css/generate",
|
|
||||||
authorized(BUILDER),
|
|
||||||
generateCssValidator(),
|
|
||||||
controller.generateCss
|
|
||||||
)
|
|
||||||
.post(
|
.post(
|
||||||
"/api/attachments/process",
|
"/api/attachments/process",
|
||||||
authorized(BUILDER),
|
authorized(BUILDER),
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
const CouchDB = require("../../../db")
|
const CouchDB = require("../../../db")
|
||||||
const supertest = require("supertest")
|
const supertest = require("supertest")
|
||||||
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
|
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
|
||||||
const {
|
|
||||||
BUILTIN_PERMISSION_NAMES,
|
|
||||||
} = require("../../../utilities/security/permissions")
|
|
||||||
const packageJson = require("../../../../package")
|
const packageJson = require("../../../../package")
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const env = require("../../../environment")
|
const env = require("../../../environment")
|
||||||
|
@ -131,49 +128,7 @@ exports.createUser = async (
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
|
|
||||||
const createUserWithOnePermission = async (request, appId, permName) => {
|
const createUserWithRole = async (request, appId, roleId, email) => {
|
||||||
let permissions = [permName]
|
|
||||||
|
|
||||||
return await createUserWithPermissions(
|
|
||||||
request,
|
|
||||||
appId,
|
|
||||||
permissions,
|
|
||||||
"onePermOnlyUser"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createUserWithAdminPermissions = async (request, appId) => {
|
|
||||||
let permissions = [BUILTIN_PERMISSION_NAMES.ADMIN]
|
|
||||||
|
|
||||||
return await createUserWithPermissions(
|
|
||||||
request,
|
|
||||||
appId,
|
|
||||||
permissions,
|
|
||||||
"adminUser"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createUserWithAllPermissionExceptOne = async (
|
|
||||||
request,
|
|
||||||
appId,
|
|
||||||
permName
|
|
||||||
) => {
|
|
||||||
let permissions = [permName]
|
|
||||||
|
|
||||||
return await createUserWithPermissions(
|
|
||||||
request,
|
|
||||||
appId,
|
|
||||||
permissions,
|
|
||||||
"allPermsExceptOneUser"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createUserWithPermissions = async (
|
|
||||||
request,
|
|
||||||
appId,
|
|
||||||
permissions,
|
|
||||||
email
|
|
||||||
) => {
|
|
||||||
const password = `password_${email}`
|
const password = `password_${email}`
|
||||||
await request
|
await request
|
||||||
.post(`/api/users`)
|
.post(`/api/users`)
|
||||||
|
@ -181,8 +136,7 @@ const createUserWithPermissions = async (
|
||||||
.send({
|
.send({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
roleId: BUILTIN_ROLE_IDS.POWER,
|
roleId,
|
||||||
permissions,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const anonUser = {
|
const anonUser = {
|
||||||
|
@ -215,23 +169,29 @@ exports.testPermissionsForEndpoint = async ({
|
||||||
url,
|
url,
|
||||||
body,
|
body,
|
||||||
appId,
|
appId,
|
||||||
permName1,
|
passRole,
|
||||||
permName2,
|
failRole,
|
||||||
}) => {
|
}) => {
|
||||||
const headers = await createUserWithOnePermission(request, appId, permName1)
|
const passHeader = await createUserWithRole(
|
||||||
|
|
||||||
await createRequest(request, method, url, body)
|
|
||||||
.set(headers)
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
const noPermsHeaders = await createUserWithAllPermissionExceptOne(
|
|
||||||
request,
|
request,
|
||||||
appId,
|
appId,
|
||||||
permName2
|
passRole,
|
||||||
|
"passUser@budibase.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
await createRequest(request, method, url, body)
|
await createRequest(request, method, url, body)
|
||||||
.set(noPermsHeaders)
|
.set(passHeader)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const failHeader = await createUserWithRole(
|
||||||
|
request,
|
||||||
|
appId,
|
||||||
|
failRole,
|
||||||
|
"failUser@budibase.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
await createRequest(request, method, url, body)
|
||||||
|
.set(failHeader)
|
||||||
.expect(403)
|
.expect(403)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +202,12 @@ exports.builderEndpointShouldBlockNormalUsers = async ({
|
||||||
body,
|
body,
|
||||||
appId,
|
appId,
|
||||||
}) => {
|
}) => {
|
||||||
const headers = await createUserWithAdminPermissions(request, appId)
|
const headers = await createUserWithRole(
|
||||||
|
request,
|
||||||
|
appId,
|
||||||
|
BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
"basicUser@budibase.com"
|
||||||
|
)
|
||||||
|
|
||||||
await createRequest(request, method, url, body)
|
await createRequest(request, method, url, body)
|
||||||
.set(headers)
|
.set(headers)
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
const { generateAssetCss, generateCss } = require("../../../utilities/builder/generateCss")
|
|
||||||
|
|
||||||
describe("generate_css", () => {
|
|
||||||
it("Check how array styles are output", () => {
|
|
||||||
expect(generateCss({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0 10 0 15;")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Check handling of an array with empty string values", () => {
|
|
||||||
expect(generateCss({ padding: ["", "", "", ""] })).toBe("")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Check handling of an empty array", () => {
|
|
||||||
expect(generateCss({ margin: [] })).toBe("")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Check handling of valid font property", () => {
|
|
||||||
expect(generateCss({ "font-size": "10px" })).toBe("font-size: 10px;")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
describe("generate_screen_css", () => {
|
|
||||||
const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } }
|
|
||||||
|
|
||||||
it("Test generation of normal css styles", () => {
|
|
||||||
expect(generateAssetCss([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}")
|
|
||||||
})
|
|
||||||
|
|
||||||
const hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } }
|
|
||||||
|
|
||||||
it("Test generation of hover css styles", () => {
|
|
||||||
expect(generateAssetCss([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}")
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } }
|
|
||||||
|
|
||||||
it("Test generation of selection css styles", () => {
|
|
||||||
expect(generateAssetCss([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}")
|
|
||||||
})
|
|
||||||
|
|
||||||
const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } }
|
|
||||||
|
|
||||||
it("Testing handling of empty component styles", () => {
|
|
||||||
expect(generateAssetCss([emptyComponent])).toBe("")
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,15 +1,20 @@
|
||||||
const {
|
const {
|
||||||
createApplication,
|
createApplication,
|
||||||
createTable,
|
createTable,
|
||||||
createView,
|
createView,
|
||||||
supertest,
|
supertest,
|
||||||
defaultHeaders
|
defaultHeaders,
|
||||||
} = require("./couchTestUtils")
|
} = require("./couchTestUtils")
|
||||||
|
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
|
||||||
const {
|
const {
|
||||||
BUILTIN_ROLE_IDS,
|
BUILTIN_PERMISSION_IDS,
|
||||||
} = require("../../../utilities/security/roles")
|
} = require("../../../utilities/security/permissions")
|
||||||
|
|
||||||
const roleBody = { name: "user", inherits: BUILTIN_ROLE_IDS.BASIC }
|
const roleBody = {
|
||||||
|
name: "NewRole",
|
||||||
|
inherits: BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY,
|
||||||
|
}
|
||||||
|
|
||||||
describe("/roles", () => {
|
describe("/roles", () => {
|
||||||
let server
|
let server
|
||||||
|
@ -19,8 +24,8 @@ describe("/roles", () => {
|
||||||
let view
|
let view
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
({ request, server } = await supertest())
|
;({ request, server } = await supertest())
|
||||||
});
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
server.close()
|
server.close()
|
||||||
|
@ -34,30 +39,29 @@ describe("/roles", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
|
|
||||||
it("returns a success message when role is successfully created", async () => {
|
it("returns a success message when role is successfully created", async () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/roles`)
|
.post(`/api/roles`)
|
||||||
.send(roleBody)
|
.send(roleBody)
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.res.statusMessage).toEqual("Role 'user' created successfully.")
|
expect(res.res.statusMessage).toEqual(
|
||||||
|
"Role 'NewRole' created successfully."
|
||||||
|
)
|
||||||
expect(res.body._id).toBeDefined()
|
expect(res.body._id).toBeDefined()
|
||||||
expect(res.body._rev).toBeDefined()
|
expect(res.body._rev).toBeDefined()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
|
|
||||||
it("should list custom roles, plus 2 default roles", async () => {
|
it("should list custom roles, plus 2 default roles", async () => {
|
||||||
const createRes = await request
|
const createRes = await request
|
||||||
.post(`/api/roles`)
|
.post(`/api/roles`)
|
||||||
.send(roleBody)
|
.send(roleBody)
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const customRole = createRes.body
|
const customRole = createRes.body
|
||||||
|
@ -65,33 +69,37 @@ describe("/roles", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/roles`)
|
.get(`/api/roles`)
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.length).toBe(3)
|
expect(res.body.length).toBe(5)
|
||||||
|
|
||||||
const adminRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.ADMIN)
|
const adminRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.ADMIN)
|
||||||
expect(adminRole.inherits).toEqual(BUILTIN_ROLE_IDS.POWER)
|
|
||||||
expect(adminRole).toBeDefined()
|
expect(adminRole).toBeDefined()
|
||||||
|
expect(adminRole.inherits).toEqual(BUILTIN_ROLE_IDS.POWER)
|
||||||
|
expect(adminRole.permissionId).toEqual(BUILTIN_PERMISSION_IDS.ADMIN)
|
||||||
|
|
||||||
const powerUserRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.POWER)
|
const powerUserRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.POWER)
|
||||||
expect(powerUserRole.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
|
||||||
expect(powerUserRole).toBeDefined()
|
expect(powerUserRole).toBeDefined()
|
||||||
|
expect(powerUserRole.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
||||||
|
expect(powerUserRole.permissionId).toEqual(BUILTIN_PERMISSION_IDS.POWER)
|
||||||
|
|
||||||
const customRoleFetched = res.body.find(r => r._id === customRole._id)
|
const customRoleFetched = res.body.find(r => r._id === customRole._id)
|
||||||
expect(customRoleFetched.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
|
||||||
expect(customRoleFetched).toBeDefined()
|
expect(customRoleFetched).toBeDefined()
|
||||||
|
expect(customRoleFetched.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
||||||
|
expect(customRoleFetched.permissionId).toEqual(
|
||||||
|
BUILTIN_PERMISSION_IDS.READ_ONLY
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
describe("destroy", () => {
|
describe("destroy", () => {
|
||||||
it("should delete custom roles", async () => {
|
it("should delete custom roles", async () => {
|
||||||
const createRes = await request
|
const createRes = await request
|
||||||
.post(`/api/roles`)
|
.post(`/api/roles`)
|
||||||
.send({ name: "user" })
|
.send({ name: "user", permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY })
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const customRole = createRes.body
|
const customRole = createRes.body
|
||||||
|
@ -107,4 +115,4 @@ describe("/roles", () => {
|
||||||
.expect(404)
|
.expect(404)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
|
|
@ -51,11 +51,9 @@ describe("/rows", () => {
|
||||||
|
|
||||||
|
|
||||||
describe("save, load, update, delete", () => {
|
describe("save, load, update, delete", () => {
|
||||||
|
|
||||||
|
|
||||||
it("returns a success message when the row is created", async () => {
|
it("returns a success message when the row is created", async () => {
|
||||||
const res = await createRow()
|
const res = await createRow()
|
||||||
expect(res.res.statusMessage).toEqual(`${table.name} created successfully`)
|
expect(res.res.statusMessage).toEqual(`${table.name} saved successfully`)
|
||||||
expect(res.body.name).toEqual("Test Contact")
|
expect(res.body.name).toEqual("Test Contact")
|
||||||
expect(res.body._rev).toBeDefined()
|
expect(res.body._rev).toBeDefined()
|
||||||
})
|
})
|
||||||
|
@ -118,30 +116,6 @@ describe("/rows", () => {
|
||||||
expect(res.body.find(r => r.name === row.name)).toBeDefined()
|
expect(res.body.find(r => r.name === row.name)).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("lists rows when queried by their ID", async () => {
|
|
||||||
const newRow = {
|
|
||||||
tableId: table._id,
|
|
||||||
name: "Second Contact",
|
|
||||||
status: "new"
|
|
||||||
}
|
|
||||||
const row = await createRow()
|
|
||||||
const secondRow = await createRow(newRow)
|
|
||||||
|
|
||||||
const rowIds = [row.body._id, secondRow.body._id]
|
|
||||||
|
|
||||||
const res = await request
|
|
||||||
.post(`/api/rows/search`)
|
|
||||||
.set(defaultHeaders(appId))
|
|
||||||
.send({
|
|
||||||
keys: rowIds
|
|
||||||
})
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(res.body.length).toBe(2)
|
|
||||||
expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(rowIds))
|
|
||||||
})
|
|
||||||
|
|
||||||
it("load should return 404 when row does not exist", async () => {
|
it("load should return 404 when row does not exist", async () => {
|
||||||
await createRow()
|
await createRow()
|
||||||
await request
|
await request
|
||||||
|
|
|
@ -5,12 +5,14 @@ const {
|
||||||
createUser,
|
createUser,
|
||||||
testPermissionsForEndpoint,
|
testPermissionsForEndpoint,
|
||||||
} = require("./couchTestUtils")
|
} = require("./couchTestUtils")
|
||||||
const {
|
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
|
||||||
BUILTIN_PERMISSION_NAMES,
|
const { cloneDeep } = require("lodash/fp")
|
||||||
} = require("../../../utilities/security/permissions")
|
|
||||||
const {
|
const baseBody = {
|
||||||
BUILTIN_ROLE_IDS,
|
email: "bill@bill.com",
|
||||||
} = require("../../../utilities/security/roles")
|
password: "yeeooo",
|
||||||
|
roleId: BUILTIN_ROLE_IDS.POWER,
|
||||||
|
}
|
||||||
|
|
||||||
describe("/users", () => {
|
describe("/users", () => {
|
||||||
let request
|
let request
|
||||||
|
@ -19,13 +21,13 @@ describe("/users", () => {
|
||||||
let appId
|
let appId
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
({ request, server } = await supertest(server))
|
;({ request, server } = await supertest(server))
|
||||||
});
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
app = await createApplication(request)
|
app = await createApplication(request)
|
||||||
appId = app.instance._id
|
appId = app.instance._id
|
||||||
});
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
server.close()
|
server.close()
|
||||||
|
@ -39,9 +41,9 @@ describe("/users", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/users`)
|
.get(`/api/users`)
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.length).toBe(2)
|
expect(res.body.length).toBe(2)
|
||||||
expect(res.body.find(u => u.email === "brenda@brenda.com")).toBeDefined()
|
expect(res.body.find(u => u.email === "brenda@brenda.com")).toBeDefined()
|
||||||
expect(res.body.find(u => u.email === "pam@pam.com")).toBeDefined()
|
expect(res.body.find(u => u.email === "pam@pam.com")).toBeDefined()
|
||||||
|
@ -54,37 +56,39 @@ describe("/users", () => {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `/api/users`,
|
url: `/api/users`,
|
||||||
appId: appId,
|
appId: appId,
|
||||||
permName1: BUILTIN_PERMISSION_NAMES.POWER,
|
passRole: BUILTIN_ROLE_IDS.ADMIN,
|
||||||
permName2: BUILTIN_PERMISSION_NAMES.WRITE,
|
failRole: BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
it("returns a success message when a user is successfully created", async () => {
|
it("returns a success message when a user is successfully created", async () => {
|
||||||
|
const body = cloneDeep(baseBody)
|
||||||
|
body.email = "bill@budibase.com"
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/users`)
|
.post(`/api/users`)
|
||||||
.set(defaultHeaders(appId))
|
.set(defaultHeaders(appId))
|
||||||
.send({ email: "bill@bill.com", password: "bills_password", roleId: BUILTIN_ROLE_IDS.POWER })
|
.send(body)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
|
|
||||||
expect(res.res.statusMessage).toEqual("User created successfully.");
|
expect(res.res.statusMessage).toEqual("User created successfully.")
|
||||||
expect(res.body._id).toBeUndefined()
|
expect(res.body._id).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
const body = cloneDeep(baseBody)
|
||||||
|
body.email = "brandNewUser@user.com"
|
||||||
await testPermissionsForEndpoint({
|
await testPermissionsForEndpoint({
|
||||||
request,
|
request,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { email: "brandNewUser@user.com", password: "yeeooo", roleId: BUILTIN_ROLE_IDS.POWER },
|
body,
|
||||||
url: `/api/users`,
|
url: `/api/users`,
|
||||||
appId: appId,
|
appId: appId,
|
||||||
permName1: BUILTIN_PERMISSION_NAMES.ADMIN,
|
passRole: BUILTIN_ROLE_IDS.ADMIN,
|
||||||
permName2: BUILTIN_PERMISSION_NAMES.POWER,
|
failRole: BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -32,7 +32,7 @@ const USERS_TABLE_SCHEMA = {
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
presence: false,
|
presence: false,
|
||||||
inclusion: Object.keys(BUILTIN_ROLE_IDS),
|
inclusion: Object.values(BUILTIN_ROLE_IDS),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -102,15 +102,11 @@ exports.generateRowID = tableId => {
|
||||||
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
|
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
|
||||||
*/
|
*/
|
||||||
exports.getUserParams = (email = "", otherProps = {}) => {
|
exports.getUserParams = (email = "", otherProps = {}) => {
|
||||||
return getDocParams(
|
return exports.getRowParams(ViewNames.USERS, email, otherProps)
|
||||||
DocumentTypes.ROW,
|
|
||||||
`${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${email}`,
|
|
||||||
otherProps
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new user ID based on the passed in username.
|
* Generates a new user ID based on the passed in email.
|
||||||
* @param {string} email The email which the ID is going to be built up of.
|
* @param {string} email The email which the ID is going to be built up of.
|
||||||
* @returns {string} The new user ID which the user doc can be stored under.
|
* @returns {string} The new user ID which the user doc can be stored under.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles")
|
const {
|
||||||
|
BUILTIN_ROLE_IDS,
|
||||||
|
getUserPermissionIds,
|
||||||
|
} = require("../utilities/security/roles")
|
||||||
const {
|
const {
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
doesHavePermission,
|
doesHavePermission,
|
||||||
|
@ -48,7 +51,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const role = ctx.user.role
|
const role = ctx.user.role
|
||||||
const permissions = ctx.user.permissions
|
const permissions = await getUserPermissionIds(ctx.appId, role._id)
|
||||||
if (ADMIN_ROLES.indexOf(role._id) !== -1) {
|
if (ADMIN_ROLES.indexOf(role._id) !== -1) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,18 @@
|
||||||
const {
|
const { ensureDir, constants, copyFile } = require("fs-extra")
|
||||||
ensureDir,
|
|
||||||
constants,
|
|
||||||
copyFile,
|
|
||||||
writeFile,
|
|
||||||
readdir,
|
|
||||||
readFile,
|
|
||||||
existsSync,
|
|
||||||
} = require("fs-extra")
|
|
||||||
const { join } = require("../centralPath")
|
const { join } = require("../centralPath")
|
||||||
const { budibaseAppsDir } = require("../budibaseDir")
|
const { budibaseAppsDir } = require("../budibaseDir")
|
||||||
|
|
||||||
const CSS_DIRECTORY = "css"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile all the non-db static web assets that are required for the running of
|
* Compile all the non-db static web assets that are required for the running of
|
||||||
* a budibase application. This includes CSS, the JSON structure of the DOM and
|
* a budibase application. This includes the JSON structure of the DOM and
|
||||||
* the client library, a script responsible for reading the JSON structure
|
* the client library, a script responsible for reading the JSON structure
|
||||||
* and rendering the application.
|
* and rendering the application.
|
||||||
* @param {string} appId id of the application we want to compile static assets for
|
* @param {string} appId id of the application we want to compile static assets for
|
||||||
* @param {array|object} assets a list of screens or screen layouts for which the CSS should be extracted and stored.
|
|
||||||
*/
|
*/
|
||||||
module.exports = async (appId, assets) => {
|
module.exports = async appId => {
|
||||||
const publicPath = join(budibaseAppsDir(), appId, "public")
|
const publicPath = join(budibaseAppsDir(), appId, "public")
|
||||||
await ensureDir(publicPath)
|
await ensureDir(publicPath)
|
||||||
for (let asset of Array.isArray(assets) ? assets : [assets]) {
|
await copyClientLib(publicPath)
|
||||||
await buildCssBundle(publicPath, asset)
|
|
||||||
await copyClientLib(publicPath)
|
|
||||||
// remove props that shouldn't be present when written to DB
|
|
||||||
if (asset._css) {
|
|
||||||
delete asset._css
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return assets
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the _css property of all screens and the screen layouts, and creates a singular CSS
|
|
||||||
* bundle for the app at <appId>/public/bundle.css
|
|
||||||
* @param {String} publicPath - path to the public assets directory of the budibase application
|
|
||||||
* @param {Object} asset a single screen or screen layout which is being updated
|
|
||||||
*/
|
|
||||||
const buildCssBundle = async (publicPath, asset) => {
|
|
||||||
const cssPath = join(publicPath, CSS_DIRECTORY)
|
|
||||||
let cssString = ""
|
|
||||||
|
|
||||||
// create a singular CSS file for this asset
|
|
||||||
const assetCss = asset._css ? asset._css.trim() : ""
|
|
||||||
if (assetCss.length !== 0) {
|
|
||||||
await ensureDir(cssPath)
|
|
||||||
await writeFile(join(cssPath, asset._id), assetCss)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bundle up all the CSS in the directory into one top level CSS file
|
|
||||||
if (existsSync(cssPath)) {
|
|
||||||
const cssFiles = await readdir(cssPath)
|
|
||||||
for (let filename of cssFiles) {
|
|
||||||
const css = await readFile(join(cssPath, filename))
|
|
||||||
cssString += css
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile(join(publicPath, "bundle.css"), cssString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
exports.generateAssetCss = component_arr => {
|
|
||||||
let styles = ""
|
|
||||||
for (const { _styles, _id, _children, _component } of component_arr) {
|
|
||||||
let [componentName] = _component.match(/[a-z]*$/)
|
|
||||||
Object.keys(_styles).forEach(selector => {
|
|
||||||
const cssString = exports.generateCss(_styles[selector])
|
|
||||||
if (cssString) {
|
|
||||||
styles += exports.applyClass(_id, componentName, cssString, selector)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (_children && _children.length) {
|
|
||||||
styles += exports.generateAssetCss(_children) + "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return styles.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.generateCss = style => {
|
|
||||||
let cssString = Object.entries(style).reduce((str, [key, value]) => {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
if (value) {
|
|
||||||
return (str += `${key}: ${value};\n`)
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
if (value.length > 0 && !value.every(v => v === "")) {
|
|
||||||
return (str += `${key}: ${value.join(" ")};\n`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str
|
|
||||||
}, "")
|
|
||||||
|
|
||||||
return (cssString || "").trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.applyClass = (id, name = "element", styles, selector) => {
|
|
||||||
if (selector === "normal") {
|
|
||||||
return `.${name}-${id} {\n${styles}\n}`
|
|
||||||
} else {
|
|
||||||
let sel = selector === "selected" ? "::selection" : `:${selector}`
|
|
||||||
return `.${name}-${id}${sel} {\n${styles}\n}`
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
const { BUILTIN_ROLE_IDS } = require("../security/roles")
|
const { BUILTIN_ROLE_IDS } = require("../security/roles")
|
||||||
const { BUILTIN_PERMISSION_NAMES } = require("../security/permissions")
|
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
|
@ -11,7 +10,6 @@ module.exports = async (ctx, appId, version) => {
|
||||||
const builderUser = {
|
const builderUser = {
|
||||||
userId: "BUILDER",
|
userId: "BUILDER",
|
||||||
roleId: BUILTIN_ROLE_IDS.BUILDER,
|
roleId: BUILTIN_ROLE_IDS.BUILDER,
|
||||||
permissions: [BUILTIN_PERMISSION_NAMES.ADMIN],
|
|
||||||
version,
|
version,
|
||||||
}
|
}
|
||||||
if (env.BUDIBASE_API_KEY) {
|
if (env.BUDIBASE_API_KEY) {
|
||||||
|
|
|
@ -1,8 +1,59 @@
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { DocumentTypes, SEPARATOR } = require("../db/utils")
|
const { DocumentTypes, SEPARATOR } = require("../db/utils")
|
||||||
|
const fs = require("fs")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of how we convert various properties in rows to each other based on the row type.
|
||||||
|
*/
|
||||||
|
const TYPE_TRANSFORM_MAP = {
|
||||||
|
link: {
|
||||||
|
"": [],
|
||||||
|
[null]: [],
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
"": "",
|
||||||
|
[null]: "",
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
|
string: {
|
||||||
|
"": "",
|
||||||
|
[null]: "",
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
|
longform: {
|
||||||
|
"": "",
|
||||||
|
[null]: "",
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
"": null,
|
||||||
|
[null]: null,
|
||||||
|
[undefined]: undefined,
|
||||||
|
parse: n => parseFloat(n),
|
||||||
|
},
|
||||||
|
datetime: {
|
||||||
|
"": null,
|
||||||
|
[undefined]: undefined,
|
||||||
|
[null]: null,
|
||||||
|
},
|
||||||
|
attachment: {
|
||||||
|
"": [],
|
||||||
|
[null]: [],
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
|
boolean: {
|
||||||
|
"": null,
|
||||||
|
[null]: null,
|
||||||
|
[undefined]: undefined,
|
||||||
|
true: true,
|
||||||
|
false: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
function confirmAppId(possibleAppId) {
|
function confirmAppId(possibleAppId) {
|
||||||
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
||||||
? possibleAppId
|
? possibleAppId
|
||||||
|
@ -74,3 +125,46 @@ exports.setCookie = (ctx, name, value) => {
|
||||||
exports.isClient = ctx => {
|
exports.isClient = ctx => {
|
||||||
return ctx.headers["x-budibase-type"] === "client"
|
return ctx.headers["x-budibase-type"] === "client"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively walk a directory tree and execute a callback on all files.
|
||||||
|
* @param {String} dirPath - Directory to traverse
|
||||||
|
* @param {Function} callback - callback to execute on files
|
||||||
|
*/
|
||||||
|
exports.walkDir = (dirPath, callback) => {
|
||||||
|
for (let filename of fs.readdirSync(dirPath)) {
|
||||||
|
const filePath = `${dirPath}/${filename}`
|
||||||
|
const stat = fs.lstatSync(filePath)
|
||||||
|
|
||||||
|
if (stat.isFile()) {
|
||||||
|
callback(filePath)
|
||||||
|
} else {
|
||||||
|
exports.walkDir(filePath, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will coerce the values in a row to the correct types based on the type transform map and the
|
||||||
|
* table schema.
|
||||||
|
* @param {object} row The row which is to be coerced to correct values based on schema, this input
|
||||||
|
* row will not be updated.
|
||||||
|
* @param {object} table The table that has been retrieved from DB, this must contain the expected
|
||||||
|
* schema for the rows.
|
||||||
|
* @returns {object} The updated row will be returned with all values coerced.
|
||||||
|
*/
|
||||||
|
exports.coerceRowValues = (row, table) => {
|
||||||
|
const clonedRow = cloneDeep(row)
|
||||||
|
for (let [key, value] of Object.entries(clonedRow)) {
|
||||||
|
const field = table.schema[key]
|
||||||
|
if (!field) continue
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) {
|
||||||
|
clonedRow[key] = TYPE_TRANSFORM_MAP[field.type][value]
|
||||||
|
} else if (TYPE_TRANSFORM_MAP[field.type].parse) {
|
||||||
|
clonedRow[key] = TYPE_TRANSFORM_MAP[field.type].parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clonedRow
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ function getAllowedLevels(userPermLevel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.BUILTIN_PERMISSION_NAMES = {
|
exports.BUILTIN_PERMISSION_IDS = {
|
||||||
READ_ONLY: "read_only",
|
READ_ONLY: "read_only",
|
||||||
WRITE: "write",
|
WRITE: "write",
|
||||||
ADMIN: "admin",
|
ADMIN: "admin",
|
||||||
|
@ -54,21 +54,24 @@ exports.BUILTIN_PERMISSION_NAMES = {
|
||||||
|
|
||||||
exports.BUILTIN_PERMISSIONS = {
|
exports.BUILTIN_PERMISSIONS = {
|
||||||
READ_ONLY: {
|
READ_ONLY: {
|
||||||
name: exports.BUILTIN_PERMISSION_NAMES.READ_ONLY,
|
_id: exports.BUILTIN_PERMISSION_IDS.READ_ONLY,
|
||||||
|
name: "Read only",
|
||||||
permissions: [
|
permissions: [
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.READ),
|
new Permission(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
WRITE: {
|
WRITE: {
|
||||||
name: exports.BUILTIN_PERMISSION_NAMES.WRITE,
|
_id: exports.BUILTIN_PERMISSION_IDS.WRITE,
|
||||||
|
name: "Read/Write",
|
||||||
permissions: [
|
permissions: [
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
POWER: {
|
POWER: {
|
||||||
name: exports.BUILTIN_PERMISSION_NAMES.POWER,
|
_id: exports.BUILTIN_PERMISSION_IDS.POWER,
|
||||||
|
name: "Power",
|
||||||
permissions: [
|
permissions: [
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||||
new Permission(PermissionTypes.USER, PermissionLevels.READ),
|
new Permission(PermissionTypes.USER, PermissionLevels.READ),
|
||||||
|
@ -78,7 +81,8 @@ exports.BUILTIN_PERMISSIONS = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ADMIN: {
|
ADMIN: {
|
||||||
name: exports.BUILTIN_PERMISSION_NAMES.ADMIN,
|
_id: exports.BUILTIN_PERMISSION_IDS.ADMIN,
|
||||||
|
name: "Admin",
|
||||||
permissions: [
|
permissions: [
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.ADMIN),
|
new Permission(PermissionTypes.TABLE, PermissionLevels.ADMIN),
|
||||||
new Permission(PermissionTypes.USER, PermissionLevels.ADMIN),
|
new Permission(PermissionTypes.USER, PermissionLevels.ADMIN),
|
||||||
|
@ -89,11 +93,11 @@ exports.BUILTIN_PERMISSIONS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.doesHavePermission = (permType, permLevel, userPermissionNames) => {
|
exports.doesHavePermission = (permType, permLevel, permissionIds) => {
|
||||||
const builtins = Object.values(exports.BUILTIN_PERMISSIONS)
|
const builtins = Object.values(exports.BUILTIN_PERMISSIONS)
|
||||||
let permissions = flatten(
|
let permissions = flatten(
|
||||||
builtins
|
builtins
|
||||||
.filter(builtin => userPermissionNames.indexOf(builtin.name) !== -1)
|
.filter(builtin => permissionIds.indexOf(builtin._id) !== -1)
|
||||||
.map(builtin => builtin.permissions)
|
.map(builtin => builtin.permissions)
|
||||||
)
|
)
|
||||||
for (let permission of permissions) {
|
for (let permission of permissions) {
|
||||||
|
|
|
@ -1,28 +1,46 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
const { BUILTIN_PERMISSION_IDS } = require("./permissions")
|
||||||
|
|
||||||
const BUILTIN_IDS = {
|
const BUILTIN_IDS = {
|
||||||
ADMIN: "ADMIN",
|
ADMIN: "ADMIN",
|
||||||
POWER: "POWER_USER",
|
POWER: "POWER",
|
||||||
BASIC: "BASIC",
|
BASIC: "BASIC",
|
||||||
PUBLIC: "PUBLIC",
|
PUBLIC: "PUBLIC",
|
||||||
BUILDER: "BUILDER",
|
BUILDER: "BUILDER",
|
||||||
}
|
}
|
||||||
|
|
||||||
function Role(id, name, inherits) {
|
function Role(id, name) {
|
||||||
this._id = id
|
this._id = id
|
||||||
this.name = name
|
this.name = name
|
||||||
if (inherits) {
|
}
|
||||||
this.inherits = inherits
|
|
||||||
}
|
Role.prototype.addPermission = function(permissionId) {
|
||||||
|
this.permissionId = permissionId
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
Role.prototype.addInheritance = function(inherits) {
|
||||||
|
this.inherits = inherits
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.BUILTIN_ROLES = {
|
exports.BUILTIN_ROLES = {
|
||||||
ADMIN: new Role(BUILTIN_IDS.ADMIN, "Admin", BUILTIN_IDS.POWER),
|
ADMIN: new Role(BUILTIN_IDS.ADMIN, "Admin")
|
||||||
POWER: new Role(BUILTIN_IDS.POWER, "Power", BUILTIN_IDS.BASIC),
|
.addPermission(BUILTIN_PERMISSION_IDS.ADMIN)
|
||||||
BASIC: new Role(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.PUBLIC),
|
.addInheritance(BUILTIN_IDS.POWER),
|
||||||
PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public"),
|
POWER: new Role(BUILTIN_IDS.POWER, "Power")
|
||||||
BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder"),
|
.addPermission(BUILTIN_PERMISSION_IDS.POWER)
|
||||||
|
.addInheritance(BUILTIN_IDS.BASIC),
|
||||||
|
BASIC: new Role(BUILTIN_IDS.BASIC, "Basic")
|
||||||
|
.addPermission(BUILTIN_PERMISSION_IDS.WRITE)
|
||||||
|
.addInheritance(BUILTIN_IDS.PUBLIC),
|
||||||
|
PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public").addPermission(
|
||||||
|
BUILTIN_PERMISSION_IDS.READ_ONLY
|
||||||
|
),
|
||||||
|
BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder").addPermission(
|
||||||
|
BUILTIN_PERMISSION_IDS.ADMIN
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.BUILTIN_ROLE_ID_ARRAY = Object.values(exports.BUILTIN_ROLES).map(
|
exports.BUILTIN_ROLE_ID_ARRAY = Object.values(exports.BUILTIN_ROLES).map(
|
||||||
|
@ -60,6 +78,29 @@ exports.getRole = async (appId, roleId) => {
|
||||||
return role
|
return role
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple function to get all the roles based on the top level user role ID.
|
||||||
|
*/
|
||||||
|
async function getAllUserRoles(appId, userRoleId) {
|
||||||
|
if (!userRoleId) {
|
||||||
|
return [BUILTIN_IDS.PUBLIC]
|
||||||
|
}
|
||||||
|
let currentRole = await exports.getRole(appId, userRoleId)
|
||||||
|
let roles = currentRole ? [currentRole] : []
|
||||||
|
let roleIds = [userRoleId]
|
||||||
|
// get all the inherited roles
|
||||||
|
while (
|
||||||
|
currentRole &&
|
||||||
|
currentRole.inherits &&
|
||||||
|
roleIds.indexOf(currentRole.inherits) === -1
|
||||||
|
) {
|
||||||
|
roleIds.push(currentRole.inherits)
|
||||||
|
currentRole = await exports.getRole(appId, currentRole.inherits)
|
||||||
|
roles.push(currentRole)
|
||||||
|
}
|
||||||
|
return roles
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an ordered array of the user's inherited role IDs, this can be used
|
* Returns an ordered array of the user's inherited role IDs, this can be used
|
||||||
* to determine if a user can access something that requires a specific role.
|
* to determine if a user can access something that requires a specific role.
|
||||||
|
@ -70,22 +111,21 @@ exports.getRole = async (appId, roleId) => {
|
||||||
*/
|
*/
|
||||||
exports.getUserRoleHierarchy = async (appId, userRoleId) => {
|
exports.getUserRoleHierarchy = async (appId, userRoleId) => {
|
||||||
// special case, if they don't have a role then they are a public user
|
// special case, if they don't have a role then they are a public user
|
||||||
if (!userRoleId) {
|
return (await getAllUserRoles(appId, userRoleId)).map(role => role._id)
|
||||||
return [BUILTIN_IDS.PUBLIC]
|
}
|
||||||
}
|
|
||||||
let roleIds = [userRoleId]
|
/**
|
||||||
let userRole = await exports.getRole(appId, userRoleId)
|
* Get all of the user permissions which could be found across the role hierarchy
|
||||||
// check if inherited makes it possible
|
* @param appId The ID of the application from which roles should be obtained.
|
||||||
while (
|
* @param userRoleId The user's role ID, this can be found in their access token.
|
||||||
userRole &&
|
* @returns {Promise<string[]>} A list of permission IDs these should all be unique.
|
||||||
userRole.inherits &&
|
*/
|
||||||
roleIds.indexOf(userRole.inherits) === -1
|
exports.getUserPermissionIds = async (appId, userRoleId) => {
|
||||||
) {
|
return [
|
||||||
roleIds.push(userRole.inherits)
|
...new Set(
|
||||||
// go to get the inherited incase it inherits anything
|
(await getAllUserRoles(appId, userRoleId)).map(role => role.permissionId)
|
||||||
userRole = await exports.getRole(appId, userRole.inherits)
|
),
|
||||||
}
|
]
|
||||||
return roleIds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccessController {
|
class AccessController {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
$: target = openInNewTab ? "_blank" : "_self"
|
$: target = openInNewTab ? "_blank" : "_self"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href={url} use:linkable {target} use:styleable={$component.styles}>
|
<a href={url || '/'} use:linkable {target} use:styleable={$component.styles}>
|
||||||
{text}
|
{text}
|
||||||
<slot />
|
<slot />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
export let logoUrl
|
export let logoUrl
|
||||||
|
|
||||||
const logOut = () => {
|
const logOut = async () => {
|
||||||
authStore.actions.logOut()
|
await authStore.actions.logOut()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
325
yarn.lock
325
yarn.lock
|
@ -2,40 +2,64 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
|
"@babel/code-frame@^7.0.0":
|
||||||
version "7.8.3"
|
version "7.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/highlight" "^7.8.3"
|
"@babel/highlight" "^7.8.3"
|
||||||
|
|
||||||
"@babel/generator@^7.8.4":
|
"@babel/code-frame@^7.10.4":
|
||||||
version "7.8.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.4.tgz#35bbc74486956fe4251829f9f6c48330e8d0985e"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
|
||||||
|
integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.8.3"
|
"@babel/highlight" "^7.10.4"
|
||||||
|
|
||||||
|
"@babel/generator@^7.12.5":
|
||||||
|
version "7.12.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de"
|
||||||
|
integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.12.5"
|
||||||
jsesc "^2.5.1"
|
jsesc "^2.5.1"
|
||||||
lodash "^4.17.13"
|
|
||||||
source-map "^0.5.0"
|
source-map "^0.5.0"
|
||||||
|
|
||||||
"@babel/helper-function-name@^7.8.3":
|
"@babel/helper-function-name@^7.10.4":
|
||||||
version "7.8.3"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
|
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
|
||||||
|
integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-get-function-arity" "^7.8.3"
|
"@babel/helper-get-function-arity" "^7.10.4"
|
||||||
"@babel/template" "^7.8.3"
|
"@babel/template" "^7.10.4"
|
||||||
"@babel/types" "^7.8.3"
|
"@babel/types" "^7.10.4"
|
||||||
|
|
||||||
"@babel/helper-get-function-arity@^7.8.3":
|
"@babel/helper-get-function-arity@^7.10.4":
|
||||||
version "7.8.3"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
|
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
|
||||||
|
integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.8.3"
|
"@babel/types" "^7.10.4"
|
||||||
|
|
||||||
"@babel/helper-split-export-declaration@^7.8.3":
|
"@babel/helper-split-export-declaration@^7.11.0":
|
||||||
version "7.8.3"
|
version "7.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9"
|
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f"
|
||||||
|
integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.8.3"
|
"@babel/types" "^7.11.0"
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier@^7.10.4":
|
||||||
|
version "7.10.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
|
||||||
|
integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
|
||||||
|
|
||||||
|
"@babel/highlight@^7.10.4":
|
||||||
|
version "7.10.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
|
||||||
|
integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-validator-identifier" "^7.10.4"
|
||||||
|
chalk "^2.0.0"
|
||||||
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
"@babel/highlight@^7.8.3":
|
"@babel/highlight@^7.8.3":
|
||||||
version "7.8.3"
|
version "7.8.3"
|
||||||
|
@ -45,38 +69,42 @@
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
"@babel/parser@^7.0.0", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4":
|
"@babel/parser@^7.12.7", "@babel/parser@^7.7.0":
|
||||||
version "7.8.4"
|
version "7.12.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056"
|
||||||
|
integrity sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==
|
||||||
|
|
||||||
"@babel/template@^7.8.3":
|
"@babel/template@^7.10.4":
|
||||||
version "7.8.3"
|
version "7.12.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
|
||||||
|
integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.8.3"
|
"@babel/code-frame" "^7.10.4"
|
||||||
"@babel/parser" "^7.8.3"
|
"@babel/parser" "^7.12.7"
|
||||||
"@babel/types" "^7.8.3"
|
"@babel/types" "^7.12.7"
|
||||||
|
|
||||||
"@babel/traverse@^7.0.0":
|
"@babel/traverse@^7.7.0":
|
||||||
version "7.8.4"
|
version "7.12.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.4.tgz#f0845822365f9d5b0e312ed3959d3f827f869e3c"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.9.tgz#fad26c972eabbc11350e0b695978de6cc8e8596f"
|
||||||
|
integrity sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.8.3"
|
"@babel/code-frame" "^7.10.4"
|
||||||
"@babel/generator" "^7.8.4"
|
"@babel/generator" "^7.12.5"
|
||||||
"@babel/helper-function-name" "^7.8.3"
|
"@babel/helper-function-name" "^7.10.4"
|
||||||
"@babel/helper-split-export-declaration" "^7.8.3"
|
"@babel/helper-split-export-declaration" "^7.11.0"
|
||||||
"@babel/parser" "^7.8.4"
|
"@babel/parser" "^7.12.7"
|
||||||
"@babel/types" "^7.8.3"
|
"@babel/types" "^7.12.7"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.19"
|
||||||
|
|
||||||
"@babel/types@^7.0.0", "@babel/types@^7.8.3":
|
"@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.7.0":
|
||||||
version "7.8.3"
|
version "7.12.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13"
|
||||||
|
integrity sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
"@babel/helper-validator-identifier" "^7.10.4"
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.19"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@fortawesome/fontawesome-common-types@^0.1.7":
|
"@fortawesome/fontawesome-common-types@^0.1.7":
|
||||||
|
@ -825,13 +853,15 @@ abbrev@1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||||
|
|
||||||
acorn-jsx@^5.1.0:
|
acorn-jsx@^5.2.0:
|
||||||
version "5.1.0"
|
version "5.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
|
||||||
|
integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
|
||||||
|
|
||||||
acorn@^7.1.0:
|
acorn@^7.1.1:
|
||||||
version "7.1.0"
|
version "7.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||||
|
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||||
|
|
||||||
agent-base@4, agent-base@^4.3.0:
|
agent-base@4, agent-base@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
|
@ -865,10 +895,11 @@ ansi-escapes@^3.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
||||||
|
|
||||||
ansi-escapes@^4.2.1:
|
ansi-escapes@^4.2.1:
|
||||||
version "4.3.0"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d"
|
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
|
||||||
|
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest "^0.8.1"
|
type-fest "^0.11.0"
|
||||||
|
|
||||||
ansi-regex@^2.0.0:
|
ansi-regex@^2.0.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
|
@ -892,6 +923,13 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert "^1.9.0"
|
color-convert "^1.9.0"
|
||||||
|
|
||||||
|
ansi-styles@^4.1.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||||
|
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||||
|
dependencies:
|
||||||
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
aproba@^1.0.3, aproba@^1.1.1:
|
aproba@^1.0.3, aproba@^1.1.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||||
|
@ -998,13 +1036,14 @@ aws4@^1.8.0:
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
|
||||||
|
|
||||||
babel-eslint@^10.0.3:
|
babel-eslint@^10.0.3:
|
||||||
version "10.0.3"
|
version "10.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
|
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
|
||||||
|
integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.0.0"
|
"@babel/code-frame" "^7.0.0"
|
||||||
"@babel/parser" "^7.0.0"
|
"@babel/parser" "^7.7.0"
|
||||||
"@babel/traverse" "^7.0.0"
|
"@babel/traverse" "^7.7.0"
|
||||||
"@babel/types" "^7.0.0"
|
"@babel/types" "^7.7.0"
|
||||||
eslint-visitor-keys "^1.0.0"
|
eslint-visitor-keys "^1.0.0"
|
||||||
resolve "^1.12.0"
|
resolve "^1.12.0"
|
||||||
|
|
||||||
|
@ -1202,6 +1241,14 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.2:
|
||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
supports-color "^5.3.0"
|
supports-color "^5.3.0"
|
||||||
|
|
||||||
|
chalk@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||||
|
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.1.0"
|
||||||
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
chardet@^0.7.0:
|
chardet@^0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||||
|
@ -1232,6 +1279,7 @@ cli-cursor@^2.1.0:
|
||||||
cli-cursor@^3.1.0:
|
cli-cursor@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
||||||
|
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
|
||||||
dependencies:
|
dependencies:
|
||||||
restore-cursor "^3.1.0"
|
restore-cursor "^3.1.0"
|
||||||
|
|
||||||
|
@ -1239,6 +1287,11 @@ cli-width@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||||
|
|
||||||
|
cli-width@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
|
||||||
|
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
|
||||||
|
|
||||||
cliui@^4.0.0:
|
cliui@^4.0.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
|
||||||
|
@ -1275,10 +1328,22 @@ color-convert@^1.9.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name "1.1.3"
|
color-name "1.1.3"
|
||||||
|
|
||||||
|
color-convert@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||||
|
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
|
||||||
|
dependencies:
|
||||||
|
color-name "~1.1.4"
|
||||||
|
|
||||||
color-name@1.1.3:
|
color-name@1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||||
|
|
||||||
|
color-name@~1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
columnify@^1.5.4:
|
columnify@^1.5.4:
|
||||||
version "1.5.4"
|
version "1.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
|
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
|
||||||
|
@ -1447,6 +1512,7 @@ cosmiconfig@^5.1.0:
|
||||||
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||||
version "6.0.5"
|
version "6.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||||
|
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
nice-try "^1.0.4"
|
nice-try "^1.0.4"
|
||||||
path-key "^2.0.1"
|
path-key "^2.0.1"
|
||||||
|
@ -1530,6 +1596,7 @@ dedent@^0.7.0:
|
||||||
deep-is@~0.1.3:
|
deep-is@~0.1.3:
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||||
|
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
|
||||||
|
|
||||||
defaults@^1.0.3:
|
defaults@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
|
@ -1637,6 +1704,7 @@ emoji-regex@^7.0.1:
|
||||||
emoji-regex@^8.0.0:
|
emoji-regex@^8.0.0:
|
||||||
version "8.0.0"
|
version "8.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
|
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||||
|
|
||||||
encoding@^0.1.11:
|
encoding@^0.1.11:
|
||||||
version "0.1.12"
|
version "0.1.12"
|
||||||
|
@ -1699,14 +1767,16 @@ escape-string-regexp@^1.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||||
|
|
||||||
eslint-plugin-cypress@^2.11.1:
|
eslint-plugin-cypress@^2.11.1:
|
||||||
version "2.11.1"
|
version "2.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.1.tgz#a945e2774b88211e2c706a059d431e262b5c2862"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0"
|
||||||
|
integrity sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA==
|
||||||
dependencies:
|
dependencies:
|
||||||
globals "^11.12.0"
|
globals "^11.12.0"
|
||||||
|
|
||||||
eslint-plugin-prettier@^3.1.2:
|
eslint-plugin-prettier@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.2.0.tgz#af391b2226fa0e15c96f36c733f6e9035dbd952c"
|
||||||
|
integrity sha512-kOUSJnFjAUFKwVxuzy6sA5yyMx6+o9ino4gCdShzBNx4eyFRudWRYKCFolKjoM40PEiuU6Cn7wBLfq3WsGg7qg==
|
||||||
dependencies:
|
dependencies:
|
||||||
prettier-linter-helpers "^1.0.0"
|
prettier-linter-helpers "^1.0.0"
|
||||||
|
|
||||||
|
@ -1715,15 +1785,17 @@ eslint-plugin-svelte3@^2.7.3:
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-svelte3/-/eslint-plugin-svelte3-2.7.3.tgz#e793b646b848e717674fe668c21b909cfa025eb3"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-svelte3/-/eslint-plugin-svelte3-2.7.3.tgz#e793b646b848e717674fe668c21b909cfa025eb3"
|
||||||
|
|
||||||
eslint-scope@^5.0.0:
|
eslint-scope@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||||
|
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
||||||
dependencies:
|
dependencies:
|
||||||
esrecurse "^4.1.0"
|
esrecurse "^4.3.0"
|
||||||
estraverse "^4.1.1"
|
estraverse "^4.1.1"
|
||||||
|
|
||||||
eslint-utils@^1.4.3:
|
eslint-utils@^1.4.3:
|
||||||
version "1.4.3"
|
version "1.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
|
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
|
||||||
|
integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^1.1.0"
|
eslint-visitor-keys "^1.1.0"
|
||||||
|
|
||||||
|
@ -1734,6 +1806,7 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
|
||||||
eslint@^6.8.0:
|
eslint@^6.8.0:
|
||||||
version "6.8.0"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
|
||||||
|
integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.0.0"
|
"@babel/code-frame" "^7.0.0"
|
||||||
ajv "^6.10.0"
|
ajv "^6.10.0"
|
||||||
|
@ -1774,11 +1847,12 @@ eslint@^6.8.0:
|
||||||
v8-compile-cache "^2.0.3"
|
v8-compile-cache "^2.0.3"
|
||||||
|
|
||||||
espree@^6.1.2:
|
espree@^6.1.2:
|
||||||
version "6.1.2"
|
version "6.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d"
|
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
|
||||||
|
integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^7.1.0"
|
acorn "^7.1.1"
|
||||||
acorn-jsx "^5.1.0"
|
acorn-jsx "^5.2.0"
|
||||||
eslint-visitor-keys "^1.1.0"
|
eslint-visitor-keys "^1.1.0"
|
||||||
|
|
||||||
esprima@^4.0.0:
|
esprima@^4.0.0:
|
||||||
|
@ -1786,21 +1860,28 @@ esprima@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||||
|
|
||||||
esquery@^1.0.1:
|
esquery@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
|
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
|
||||||
|
integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
estraverse "^4.0.0"
|
estraverse "^5.1.0"
|
||||||
|
|
||||||
esrecurse@^4.1.0:
|
esrecurse@^4.3.0:
|
||||||
version "4.2.1"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
|
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
|
||||||
|
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
|
||||||
dependencies:
|
dependencies:
|
||||||
estraverse "^4.1.0"
|
estraverse "^5.2.0"
|
||||||
|
|
||||||
estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1:
|
estraverse@^4.1.1:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||||
|
|
||||||
|
estraverse@^5.1.0, estraverse@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
|
||||||
|
integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
|
||||||
|
|
||||||
estree-walker@^0.6.1:
|
estree-walker@^0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
|
||||||
|
@ -1913,6 +1994,7 @@ fast-json-stable-stringify@^2.0.0:
|
||||||
fast-levenshtein@~2.0.6:
|
fast-levenshtein@~2.0.6:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
|
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||||
|
|
||||||
figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
|
figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
|
@ -1925,8 +2007,9 @@ figures@^2.0.0:
|
||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
|
|
||||||
figures@^3.0.0:
|
figures@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/figures/-/figures-3.1.0.tgz#4b198dd07d8d71530642864af2d45dd9e459c4ec"
|
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
|
||||||
|
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
|
|
||||||
|
@ -2235,6 +2318,11 @@ has-flag@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||||
|
|
||||||
|
has-flag@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||||
|
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||||
|
|
||||||
has-symbols@^1.0.0, has-symbols@^1.0.1:
|
has-symbols@^1.0.0, has-symbols@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
|
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
|
||||||
|
@ -2422,21 +2510,22 @@ inquirer@^6.2.0:
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
|
|
||||||
inquirer@^7.0.0:
|
inquirer@^7.0.0:
|
||||||
version "7.0.4"
|
version "7.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703"
|
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
|
||||||
|
integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-escapes "^4.2.1"
|
ansi-escapes "^4.2.1"
|
||||||
chalk "^2.4.2"
|
chalk "^4.1.0"
|
||||||
cli-cursor "^3.1.0"
|
cli-cursor "^3.1.0"
|
||||||
cli-width "^2.0.0"
|
cli-width "^3.0.0"
|
||||||
external-editor "^3.0.3"
|
external-editor "^3.0.3"
|
||||||
figures "^3.0.0"
|
figures "^3.0.0"
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.19"
|
||||||
mute-stream "0.0.8"
|
mute-stream "0.0.8"
|
||||||
run-async "^2.2.0"
|
run-async "^2.4.0"
|
||||||
rxjs "^6.5.3"
|
rxjs "^6.6.0"
|
||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^5.1.0"
|
strip-ansi "^6.0.0"
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
|
|
||||||
invert-kv@^2.0.0:
|
invert-kv@^2.0.0:
|
||||||
|
@ -2546,6 +2635,7 @@ is-fullwidth-code-point@^2.0.0:
|
||||||
is-fullwidth-code-point@^3.0.0:
|
is-fullwidth-code-point@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||||
|
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||||
|
|
||||||
is-glob@^3.1.0:
|
is-glob@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
|
@ -2776,6 +2866,7 @@ lerna@3.14.1:
|
||||||
levn@^0.3.0, levn@~0.3.0:
|
levn@^0.3.0, levn@~0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
||||||
|
integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
type-check "~0.3.2"
|
type-check "~0.3.2"
|
||||||
|
@ -2877,10 +2968,15 @@ lodash.uniq@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||||
|
|
||||||
lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@^4.2.1:
|
lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@^4.2.1:
|
||||||
version "4.17.15"
|
version "4.17.15"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||||
|
|
||||||
|
lodash@^4.17.19:
|
||||||
|
version "4.17.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||||
|
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||||
|
|
||||||
loud-rejection@^1.0.0:
|
loud-rejection@^1.0.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||||
|
@ -3063,6 +3159,7 @@ mimic-fn@^1.0.0:
|
||||||
mimic-fn@^2.0.0, mimic-fn@^2.1.0:
|
mimic-fn@^2.0.0, mimic-fn@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
|
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||||
|
|
||||||
minimatch@^3.0.0, minimatch@^3.0.4:
|
minimatch@^3.0.0, minimatch@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
|
@ -3169,6 +3266,7 @@ mute-stream@0.0.7:
|
||||||
mute-stream@0.0.8, mute-stream@~0.0.4:
|
mute-stream@0.0.8, mute-stream@~0.0.4:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||||
|
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
||||||
|
|
||||||
nanomatch@^1.2.9:
|
nanomatch@^1.2.9:
|
||||||
version "1.2.13"
|
version "1.2.13"
|
||||||
|
@ -3404,8 +3502,9 @@ onetime@^2.0.0:
|
||||||
mimic-fn "^1.0.0"
|
mimic-fn "^1.0.0"
|
||||||
|
|
||||||
onetime@^5.1.0:
|
onetime@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5"
|
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
||||||
|
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
|
@ -3419,6 +3518,7 @@ optimist@^0.6.1:
|
||||||
optionator@^0.8.3:
|
optionator@^0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
||||||
|
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-is "~0.1.3"
|
deep-is "~0.1.3"
|
||||||
fast-levenshtein "~2.0.6"
|
fast-levenshtein "~2.0.6"
|
||||||
|
@ -3696,6 +3796,7 @@ posix-character-classes@^0.1.0:
|
||||||
prelude-ls@~1.1.2:
|
prelude-ls@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||||
|
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
|
||||||
|
|
||||||
prettier-linter-helpers@^1.0.0:
|
prettier-linter-helpers@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
@ -3704,13 +3805,14 @@ prettier-linter-helpers@^1.0.0:
|
||||||
fast-diff "^1.1.2"
|
fast-diff "^1.1.2"
|
||||||
|
|
||||||
prettier-plugin-svelte@^1.4.0:
|
prettier-plugin-svelte@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-1.4.0.tgz#bb992759fb77ec2c3545d454a7c60f7a258cb745"
|
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-1.4.1.tgz#2f0f7a149190f476dc9b4ba9da8d482bd196f1e2"
|
||||||
integrity sha512-KXO2He7Kql0Lz4DdlzVli1j2JTDUR9jPV/DqyfnJmY1pCeSV1qZkxgdsyYma35W6OLrCAr/G6yKdmzo+75u2Ng==
|
integrity sha512-6y0m37Xw01GRf/WIHau+Kp3uXj2JB1agtEmNVKb9opMy34A6OMOYhfneVpNIlrghQSw/jIV+t3e5Ngt4up2CMA==
|
||||||
|
|
||||||
prettier@^1.19.1:
|
prettier@^1.19.1:
|
||||||
version "1.19.1"
|
version "1.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||||
|
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
|
@ -3907,6 +4009,7 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
||||||
regexpp@^2.0.1:
|
regexpp@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
|
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
|
||||||
|
integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
|
||||||
|
|
||||||
repeat-element@^1.1.2:
|
repeat-element@^1.1.2:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
|
@ -3995,6 +4098,7 @@ restore-cursor@^2.0.0:
|
||||||
restore-cursor@^3.1.0:
|
restore-cursor@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
||||||
|
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
|
||||||
dependencies:
|
dependencies:
|
||||||
onetime "^5.1.0"
|
onetime "^5.1.0"
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
|
@ -4045,18 +4149,30 @@ run-async@^2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-promise "^2.1.0"
|
is-promise "^2.1.0"
|
||||||
|
|
||||||
|
run-async@^2.4.0:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
||||||
|
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
|
||||||
|
|
||||||
run-queue@^1.0.0, run-queue@^1.0.3:
|
run-queue@^1.0.0, run-queue@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
|
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
|
||||||
dependencies:
|
dependencies:
|
||||||
aproba "^1.1.1"
|
aproba "^1.1.1"
|
||||||
|
|
||||||
rxjs@^6.4.0, rxjs@^6.5.3:
|
rxjs@^6.4.0:
|
||||||
version "6.5.4"
|
version "6.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
|
rxjs@^6.6.0:
|
||||||
|
version "6.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
||||||
|
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.9.0"
|
||||||
|
|
||||||
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||||
|
@ -4082,6 +4198,7 @@ safe-regex@^1.1.0:
|
||||||
semver@^6.0.0, semver@^6.1.2:
|
semver@^6.0.0, semver@^6.1.2:
|
||||||
version "6.3.0"
|
version "6.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
semver@~5.3.0:
|
semver@~5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
|
@ -4320,6 +4437,7 @@ string-width@^3.0.0:
|
||||||
string-width@^4.1.0:
|
string-width@^4.1.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
|
||||||
|
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
|
||||||
dependencies:
|
dependencies:
|
||||||
emoji-regex "^8.0.0"
|
emoji-regex "^8.0.0"
|
||||||
is-fullwidth-code-point "^3.0.0"
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
@ -4400,8 +4518,9 @@ strip-indent@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
|
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
|
||||||
|
|
||||||
strip-json-comments@^3.0.1:
|
strip-json-comments@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||||
|
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||||
|
|
||||||
strong-log-transformer@^2.0.0:
|
strong-log-transformer@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
|
@ -4417,6 +4536,13 @@ supports-color@^5.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^3.0.0"
|
has-flag "^3.0.0"
|
||||||
|
|
||||||
|
supports-color@^7.1.0:
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||||
|
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||||
|
dependencies:
|
||||||
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
svelte@^3.30.0:
|
svelte@^3.30.0:
|
||||||
version "3.30.0"
|
version "3.30.0"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.30.0.tgz#cbde341e96bf34f4ac73c8f14f8a014e03bfb7d6"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.30.0.tgz#cbde341e96bf34f4ac73c8f14f8a014e03bfb7d6"
|
||||||
|
@ -4563,9 +4689,15 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||||
type-check@~0.3.2:
|
type-check@~0.3.2:
|
||||||
version "0.3.2"
|
version "0.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
|
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
|
||||||
|
integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
|
|
||||||
|
type-fest@^0.11.0:
|
||||||
|
version "0.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
|
||||||
|
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
|
||||||
|
|
||||||
type-fest@^0.8.1:
|
type-fest@^0.8.1:
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||||
|
@ -4723,6 +4855,7 @@ windows-release@^3.1.0:
|
||||||
word-wrap@~1.2.3:
|
word-wrap@~1.2.3:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||||
|
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||||
|
|
||||||
wordwrap@~0.0.2:
|
wordwrap@~0.0.2:
|
||||||
version "0.0.3"
|
version "0.0.3"
|
||||||
|
|
Loading…
Reference in New Issue