Merge branch 'develop' of github.com:Budibase/budibase into new-design-ui

This commit is contained in:
Andrew Kingston 2022-06-22 12:49:46 +01:00
commit 955c7b0600
18 changed files with 169 additions and 129 deletions

View File

@ -20,6 +20,8 @@ RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh
FROM couchdb:3.2.1 FROM couchdb:3.2.1
ARG TARGETARCH amd64
COPY --from=build /app /app COPY --from=build /app /app
COPY --from=build /worker /worker COPY --from=build /worker /worker
@ -39,7 +41,6 @@ ENV DEPLOYMENT_ENVIRONMENT=docker \
SELF_HOSTED=1 \ SELF_HOSTED=1 \
CLUSTER_PORT=10000 \ CLUSTER_PORT=10000 \
REDIS_PASSWORD=budibase \ REDIS_PASSWORD=budibase \
ARCHITECTURE=amd \
APP_PORT=4001 \ APP_PORT=4001 \
WORKER_PORT=4002 WORKER_PORT=4002
@ -66,7 +67,7 @@ RUN mkdir /etc/nginx/logs && \
WORKDIR / WORKDIR /
RUN mkdir -p scripts/integrations/oracle RUN mkdir -p scripts/integrations/oracle
ADD packages/server/scripts/integrations/oracle scripts/integrations/oracle ADD packages/server/scripts/integrations/oracle scripts/integrations/oracle
RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/x86-64/install.sh RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh
# setup clouseau # setup clouseau
WORKDIR / WORKDIR /
@ -87,7 +88,8 @@ ADD hosting/single/vm.args ./etc/
# setup minio # setup minio
WORKDIR /minio WORKDIR /minio
RUN wget https://dl.min.io/server/minio/release/linux-${ARCHITECTURE}64/minio && chmod +x minio ADD scripts/install-minio.sh ./install.sh
RUN chmod +x install.sh && ./install.sh
# setup runner file # setup runner file
WORKDIR / WORKDIR /

View File

@ -62,6 +62,7 @@
"build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
"build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild", "build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild",
"build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -", "build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -",
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
"build:docker:single:image": "docker build -f hosting/single/Dockerfile -t budibase:latest .", "build:docker:single:image": "docker build -f hosting/single/Dockerfile -t budibase:latest .",
"build:docker:single": "lerna run build && lerna run predocker && npm run build:docker:single:image", "build:docker:single": "lerna run build && lerna run predocker && npm run build:docker:single:image",
"build:docs": "lerna run build:docs", "build:docs": "lerna run build:docs",

View File

@ -222,6 +222,7 @@ export const getFrontendStore = () => {
// Build array of promises to speed up bulk deletions // Build array of promises to speed up bulk deletions
const promises = [] const promises = []
let deleteUrls = []
screensToDelete.forEach(screen => { screensToDelete.forEach(screen => {
// Delete the screen // Delete the screen
promises.push( promises.push(
@ -231,14 +232,10 @@ export const getFrontendStore = () => {
}) })
) )
// Remove links to this screen // Remove links to this screen
promises.push( deleteUrls.push(screen.routing.route)
store.actions.components.links.delete(
screen.routing.route,
screen.props._instanceName
)
)
}) })
promises.push(store.actions.links.delete(deleteUrls))
await Promise.all(promises) await Promise.all(promises)
const deletedIds = screensToDelete.map(screen => screen._id) const deletedIds = screensToDelete.map(screen => screen._id)
store.update(state => { store.update(state => {
@ -617,89 +614,38 @@ export const getFrontendStore = () => {
}) })
await store.actions.preview.saveSelected() await store.actions.preview.saveSelected()
}, },
links: { },
save: async (url, title) => { links: {
const layout = get(mainLayout) save: async (url, title) => {
if (!layout) { const layout = get(mainLayout)
return if (!layout) {
} return
}
// Add link setting to main layout // Add link setting to main layout
if (layout.props._component.endsWith("layout")) { if (!layout.props.links) {
// If using a new SDK, add to the layout component settings layout.props.links = []
if (!layout.props.links) { }
layout.props.links = [] layout.props.links.push({
} text: title,
layout.props.links.push({ url,
text: title, })
url,
})
} else {
// If using an old SDK, add to the navigation component
// TODO: remove this when we can assume everyone has updated
const nav = findComponentType(
layout.props,
"@budibase/standard-components/navigation"
)
if (!nav) {
return
}
let newLink await store.actions.layouts.save(layout)
if (nav._children && nav._children.length) { },
// Clone an existing link if one exists delete: async urls => {
newLink = cloneDeep(nav._children[0]) const layout = get(mainLayout)
if (!layout?.props.links?.length) {
return
}
// Set our new props // Filter out the URLs to delete
newLink._id = Helpers.uuid() urls = Array.isArray(urls) ? urls : [urls]
newLink._instanceName = `${title} Link` layout.props.links = layout.props.links.filter(
newLink.url = url link => !urls.includes(link.url)
newLink.text = title )
} else {
// Otherwise create vanilla new link
newLink = {
...store.actions.components.createInstance("link"),
url,
text: title,
_instanceName: `${title} Link`,
}
nav._children = [...nav._children, newLink]
}
}
// Save layout await store.actions.layouts.save(layout)
await store.actions.layouts.save(layout)
},
delete: async (url, title) => {
const layout = get(mainLayout)
if (!layout) {
return
}
// Add link setting to main layout
if (layout.props._component.endsWith("layout")) {
// If using a new SDK, add to the layout component settings
layout.props.links = layout.props.links.filter(
link => !(link.text === title && link.url === url)
)
} else {
// If using an old SDK, add to the navigation component
// TODO: remove this when we can assume everyone has updated
const nav = findComponentType(
layout.props,
"@budibase/standard-components/navigation"
)
if (!nav) {
return
}
nav._children = nav._children.filter(
child => !(child.url === url && child.text === title)
)
}
// Save layout
await store.actions.layouts.save(layout)
},
}, },
}, },
settings: { settings: {

View File

@ -15,7 +15,7 @@ export default function (tables) {
name: `${table.name} - New`, name: `${table.name} - New`,
create: () => createScreen(table), create: () => createScreen(table),
id: NEW_ROW_TEMPLATE, id: NEW_ROW_TEMPLATE,
table: table.name, table: table._id,
} }
}) })
} }

View File

@ -17,7 +17,7 @@ export default function (tables) {
name: `${table.name} - Detail`, name: `${table.name} - Detail`,
create: () => createScreen(table), create: () => createScreen(table),
id: ROW_DETAIL_TEMPLATE, id: ROW_DETAIL_TEMPLATE,
table: table.name, table: table._id,
} }
}) })
} }

View File

@ -10,7 +10,7 @@ export default function (tables) {
name: `${table.name} - List`, name: `${table.name} - List`,
create: () => createScreen(table), create: () => createScreen(table),
id: ROW_LIST_TEMPLATE, id: ROW_LIST_TEMPLATE,
table: table.name, table: table._id,
} }
}) })
} }

View File

@ -1,27 +1,18 @@
<script> <script>
import { Label, Select, Body } from "@budibase/bbui" import { Label, Select, Body, Multiselect } from "@budibase/bbui"
import { findAllMatchingComponents } from "builderStore/componentUtils" import {
findAllMatchingComponents,
findComponent,
} from "builderStore/componentUtils"
import { currentAsset } from "builderStore" import { currentAsset } from "builderStore"
import { onMount } from "svelte" import { onMount } from "svelte"
import {
getDatasourceForProvider,
getSchemaForDatasource,
} from "builderStore/dataBinding"
export let parameters export let parameters
$: tables = findAllMatchingComponents($currentAsset?.props, component =>
component._component.endsWith("table")
).map(table => ({
label: table._instanceName,
value: table._id,
}))
$: tableBlocks = findAllMatchingComponents($currentAsset?.props, component =>
component._component.endsWith("tableblock")
).map(block => ({
label: block._instanceName,
value: `${block._id}-table`,
}))
$: componentOptions = tables.concat(tableBlocks)
const FORMATS = [ const FORMATS = [
{ {
label: "CSV", label: "CSV",
@ -33,6 +24,32 @@
}, },
] ]
$: tables = findAllMatchingComponents($currentAsset?.props, component =>
component._component.endsWith("table")
).map(table => ({
label: table._instanceName,
value: table._id,
}))
$: tableBlocks = findAllMatchingComponents($currentAsset?.props, component =>
component._component.endsWith("tableblock")
).map(block => ({
label: block._instanceName,
value: `${block._id}-table`,
}))
$: componentOptions = tables.concat(tableBlocks)
$: columnOptions = getColumnOptions(parameters.tableComponentId)
const getColumnOptions = tableId => {
// Strip block suffix if block component
if (tableId?.includes("-")) {
tableId = tableId.split("-")[0]
}
const selectedTable = findComponent($currentAsset?.props, tableId)
const datasource = getDatasourceForProvider($currentAsset, selectedTable)
const { schema } = getSchemaForDatasource($currentAsset, datasource)
return Object.keys(schema || {})
}
onMount(() => { onMount(() => {
if (!parameters.type) { if (!parameters.type) {
parameters.type = "csv" parameters.type = "csv"
@ -53,10 +70,16 @@
<Select <Select
bind:value={parameters.tableComponentId} bind:value={parameters.tableComponentId}
options={componentOptions} options={componentOptions}
on:change={() => (parameters.columns = [])}
/> />
<Label small>Export as</Label> <Label small>Export as</Label>
<Select bind:value={parameters.type} options={FORMATS} /> <Select bind:value={parameters.type} options={FORMATS} />
<Label small>Export columns</Label>
<Multiselect
placeholder="All columns"
bind:value={parameters.columns}
options={columnOptions}
/>
</div> </div>
</div> </div>
@ -80,7 +103,7 @@
display: grid; display: grid;
column-gap: var(--spacing-xs); column-gap: var(--spacing-xs);
row-gap: var(--spacing-s); row-gap: var(--spacing-s);
grid-template-columns: 70px 1fr; grid-template-columns: 90px 1fr;
align-items: center; align-items: center;
} }
</style> </style>

View File

@ -20,14 +20,14 @@
let selectedScreens = [...initalScreens] let selectedScreens = [...initalScreens]
const toggleScreenSelection = (table, datasource) => { const toggleScreenSelection = (table, datasource) => {
if (selectedScreens.find(s => s.table === table.name)) { if (selectedScreens.find(s => s.table === table._id)) {
selectedScreens = selectedScreens.filter( selectedScreens = selectedScreens.filter(
screen => screen.table !== table.name screen => screen.table !== table._id
) )
} else { } else {
let partialTemplates = getTemplates($store, $tables.list).reduce( let partialTemplates = getTemplates($store, $tables.list).reduce(
(acc, template) => { (acc, template) => {
if (template.table === table.name) { if (template.table === table._id) {
template.datasource = datasource.name template.datasource = datasource.name
acc.push(template) acc.push(template)
} }
@ -95,7 +95,7 @@
<div <div
class="data-source-entry" class="data-source-entry"
class:selected={selectedScreens.find( class:selected={selectedScreens.find(
x => x.table === table.name x => x.table === table._id
)} )}
on:click={() => toggleScreenSelection(table, datasource)} on:click={() => toggleScreenSelection(table, datasource)}
> >
@ -109,8 +109,7 @@
<use xlink:href="#spectrum-icon-18-Table" /> <use xlink:href="#spectrum-icon-18-Table" />
</svg> </svg>
{table.name} {table.name}
{#if selectedScreens.find(x => x.table === table._id)}
{#if selectedScreens.find(x => x.table === table.name)}
<span class="data-source-check"> <span class="data-source-check">
<Icon size="S" name="CheckmarkCircle" /> <Icon size="S" name="CheckmarkCircle" />
</span> </span>
@ -123,7 +122,7 @@
<div <div
class="data-source-entry" class="data-source-entry"
class:selected={selectedScreens.find( class:selected={selectedScreens.find(
x => x.table === datasource.entities[table_key].name x => x.table === datasource.entities[table_key]._id
)} )}
on:click={() => on:click={() =>
toggleScreenSelection( toggleScreenSelection(
@ -141,8 +140,7 @@
<use xlink:href="#spectrum-icon-18-Table" /> <use xlink:href="#spectrum-icon-18-Table" />
</svg> </svg>
{datasource.entities[table_key].name} {datasource.entities[table_key].name}
{#if selectedScreens.find(x => x.table === datasource.entities[table_key]._id)}
{#if selectedScreens.find(x => x.table === datasource.entities[table_key].name)}
<span class="data-source-check"> <span class="data-source-check">
<Icon size="S" name="CheckmarkCircle" /> <Icon size="S" name="CheckmarkCircle" />
</span> </span>

View File

@ -68,7 +68,7 @@
// Add link in layout for list screens // Add link in layout for list screens
if (screen.props._instanceName.endsWith("List")) { if (screen.props._instanceName.endsWith("List")) {
await store.actions.components.links.save( await store.actions.links.save(
screen.routing.route, screen.routing.route,
screen.routing.route.split("/")[1] screen.routing.route.split("/")[1]
) )
@ -133,6 +133,7 @@
const screens = selectedTemplates.map(template => { const screens = selectedTemplates.map(template => {
let screenTemplate = template.create() let screenTemplate = template.create()
screenTemplate.datasource = template.datasource screenTemplate.datasource = template.datasource
screenTemplate.autoTableId = template.table
return screenTemplate return screenTemplate
}) })
await createScreens({ screens, screenAccessRole }) await createScreens({ screens, screenAccessRole })

View File

@ -270,6 +270,7 @@ const exportDataHandler = async action => {
tableId: selection.tableId, tableId: selection.tableId,
rows: selection.selectedRows, rows: selection.selectedRows,
format: action.parameters.type, format: action.parameters.type,
columns: action.parameters.columns,
}) })
download(data, `${selection.tableId}.${action.parameters.type}`) download(data, `${selection.tableId}.${action.parameters.type}`)
} catch (error) { } catch (error) {

View File

@ -65,12 +65,15 @@ export const buildRowEndpoints = API => ({
* Exports rows. * Exports rows.
* @param tableId the table ID to export the rows from * @param tableId the table ID to export the rows from
* @param rows the array of rows to export * @param rows the array of rows to export
* @param format the format to export (csv or json)
* @param columns which columns to export (all if undefined)
*/ */
exportRows: async ({ tableId, rows, format }) => { exportRows: async ({ tableId, rows, format, columns }) => {
return await API.post({ return await API.post({
url: `/api/${tableId}/rows/exportRows?format=${format}`, url: `/api/${tableId}/rows/exportRows?format=${format}`,
body: { body: {
rows, rows,
columns,
}, },
parseResponse: async response => { parseResponse: async response => {
return await response.text() return await response.text()

View File

@ -0,0 +1,23 @@
#!/bin/bash
# Must be root to continue
if [[ $(id -u) -ne 0 ]] ; then echo "Please run as root" ; exit 1 ; fi
# Allow for re-runs
rm -rf /opt/oracle
echo "Installing oracle instant client"
# copy and unzip package
mkdir -p /opt/oracle
cp scripts/integrations/oracle/instantclient/linux/arm64/basiclite-19.10.zip /opt/oracle
cd /opt/oracle
unzip -qq basiclite-19.10.zip -d .
rm *.zip
mv instantclient* instantclient
# update runtime link path
sh -c "echo /opt/oracle/instantclient > /etc/ld.so.conf.d/oracle-instantclient.conf"
ldconfig /etc/ld.so.conf.d
echo "Installation complete"

View File

@ -0,0 +1,10 @@
#!/bin/bash
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )"
if [[ $TARGETARCH == arm* ]] ;
then
echo "Installing ARM Oracle instant client..."
$SCRIPT_DIR/arm64/install.sh
else
echo "Installing x86-64 Oracle instant client..."
$SCRIPT_DIR/x86-64/install.sh
fi

View File

@ -157,7 +157,8 @@ exports.validate = async () => {
exports.exportRows = async ctx => { exports.exportRows = async ctx => {
const { datasourceId } = breakExternalTableId(ctx.params.tableId) const { datasourceId } = breakExternalTableId(ctx.params.tableId)
const db = getAppDB() const db = getAppDB()
let format = ctx.query.format const format = ctx.query.format
const { columns } = ctx.request.body
const datasource = await db.get(datasourceId) const datasource = await db.get(datasourceId)
if (!datasource || !datasource.entities) { if (!datasource || !datasource.entities) {
ctx.throw(400, "Datasource has not been configured for plus API.") ctx.throw(400, "Datasource has not been configured for plus API.")
@ -171,13 +172,27 @@ exports.exportRows = async ctx => {
} }
let result = await exports.search(ctx) let result = await exports.search(ctx)
let headers = Object.keys(result.rows[0]) let rows = []
// Filter data to only specified columns if required
if (columns && columns.length) {
for (let i = 0; i < result.rows.length; i++) {
rows[i] = {}
for (let column of columns) {
rows[i][column] = result.rows[i][column]
}
}
} else {
rows = result.rows
}
let headers = Object.keys(rows[0])
const exporter = exporters[format] const exporter = exporters[format]
const filename = `export.${format}` const filename = `export.${format}`
// send down the file // send down the file
ctx.attachment(filename) ctx.attachment(filename)
return apiFileReturn(exporter(headers, result.rows)) return apiFileReturn(exporter(headers, rows))
} }
exports.fetchEnrichedRow = async ctx => { exports.fetchEnrichedRow = async ctx => {

4
scripts/buildx-multiarch.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
sudo apt-get install -y qemu qemu-user-static
docker buildx create --name budibase
docker buildx use budibase

View File

@ -1,11 +1,16 @@
#!/bin/bash #!/bin/bash
dir=$(pwd) dir=$(pwd)
mv dist / declare -a keep=("dist" "package.json" "yarn.lock" "client" "builder" "build" "pm2.config.js" "docker_run.sh")
mv package.json / for moveDir in "${keep[@]}"
do
mv $moveDir / 2>/dev/null
done
cd / cd /
rm -r $dir rm -r $dir
mkdir $dir mkdir $dir
mv /dist $dir for keepDir in "${keep[@]}"
mv /package.json $dir do
mv /$keepDir $dir/ 2>/dev/null
done
cd $dir cd $dir
NODE_ENV=production yarn NODE_ENV=production yarn

8
scripts/install-minio.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
if [[ $TARGETARCH == arm* ]] ;
then
wget https://dl.min.io/server/minio/release/linux-arm64/minio
else
wget https://dl.min.io/server/minio/release/linux-amd64/minio
fi
chmod +x minio