Merge pull request #11058 from Budibase/feature/budi-7076
Datasource settings tab for SQL databases and SQL schema export functionality
This commit is contained in:
commit
72e7373073
|
@ -84,7 +84,7 @@
|
||||||
await roles.save(selectedRole)
|
await roles.save(selectedRole)
|
||||||
notifications.success("Role saved successfully")
|
notifications.success("Role saved successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error saving role")
|
notifications.error(`Error saving role - ${error.message}`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,8 @@
|
||||||
changeRole()
|
changeRole()
|
||||||
notifications.success("Role deleted successfully")
|
notifications.success("Role deleted successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error deleting role")
|
notifications.error(`Error deleting role - ${error.message}`)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +140,7 @@
|
||||||
<Input
|
<Input
|
||||||
label="Name"
|
label="Name"
|
||||||
bind:value={selectedRole.name}
|
bind:value={selectedRole.name}
|
||||||
disabled={shouldDisableRoleInput}
|
disabled={!!selectedRoleId}
|
||||||
error={roleNameError}
|
error={roleNameError}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import { Body, Button, Layout, notifications } from "@budibase/bbui"
|
||||||
|
import Panel from "./Panel.svelte"
|
||||||
|
import { API } from "api"
|
||||||
|
import { downloadText } from "@budibase/frontend-core"
|
||||||
|
|
||||||
|
export let datasource
|
||||||
|
|
||||||
|
async function download() {
|
||||||
|
if (!datasource?._id) {
|
||||||
|
notifications.error("Datasource invalid")
|
||||||
|
}
|
||||||
|
const response = await API.fetchExternalSchema(datasource._id)
|
||||||
|
downloadText(`${datasource.name}-dump.sql`, response.schema)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<div class="main">
|
||||||
|
<Layout gap="S" noPadding>
|
||||||
|
<Body size="L" weight="700">Troubleshooting</Body>
|
||||||
|
<Body>Download your schema to share with the Budibase team</Body>
|
||||||
|
<div class="download-button">
|
||||||
|
<Button cta on:click={download}>Download</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.main {
|
||||||
|
margin-top: calc(var(--spacing-l) * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-button {
|
||||||
|
padding-top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -10,6 +10,8 @@
|
||||||
import RestAuthenticationPanel from "./_components/panels/Authentication/index.svelte"
|
import RestAuthenticationPanel from "./_components/panels/Authentication/index.svelte"
|
||||||
import RestVariablesPanel from "./_components/panels/Variables/index.svelte"
|
import RestVariablesPanel from "./_components/panels/Variables/index.svelte"
|
||||||
import PromptQueryModal from "./_components/PromptQueryModal.svelte"
|
import PromptQueryModal from "./_components/PromptQueryModal.svelte"
|
||||||
|
import SettingsPanel from "./_components/panels/Settings.svelte"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
let selectedPanel = null
|
let selectedPanel = null
|
||||||
let panelOptions = []
|
let panelOptions = []
|
||||||
|
@ -38,6 +40,10 @@
|
||||||
panelOptions = ["Queries"]
|
panelOptions = ["Queries"]
|
||||||
selectedPanel = "Queries"
|
selectedPanel = "Queries"
|
||||||
}
|
}
|
||||||
|
// always the last option for SQL
|
||||||
|
if (helpers.isSQL(datasource)) {
|
||||||
|
panelOptions.push("Settings")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -81,6 +87,8 @@
|
||||||
<RestAuthenticationPanel {datasource} />
|
<RestAuthenticationPanel {datasource} />
|
||||||
{:else if selectedPanel === "Variables"}
|
{:else if selectedPanel === "Variables"}
|
||||||
<RestVariablesPanel {datasource} />
|
<RestVariablesPanel {datasource} />
|
||||||
|
{:else if selectedPanel === "Settings"}
|
||||||
|
<SettingsPanel {datasource} />
|
||||||
{:else}
|
{:else}
|
||||||
<Body>Something went wrong</Body>
|
<Body>Something went wrong</Body>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -83,4 +83,10 @@ export const buildDatasourceEndpoints = API => ({
|
||||||
body: { datasource },
|
body: { datasource },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchExternalSchema: async datasourceId => {
|
||||||
|
return await API.get({
|
||||||
|
url: `/api/datasources/${datasourceId}/schema/external`,
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export function downloadText(filename, text) {
|
||||||
|
if (typeof text === "object") {
|
||||||
|
text = JSON.stringify(text)
|
||||||
|
}
|
||||||
|
const blob = new Blob([text], { type: "plain/text" })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement("a")
|
||||||
|
link.href = url
|
||||||
|
link.download = filename
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
|
@ -5,3 +5,4 @@ export * as RoleUtils from "./roles"
|
||||||
export * as Utils from "./utils"
|
export * as Utils from "./utils"
|
||||||
export { memo, derivedMemo } from "./memo"
|
export { memo, derivedMemo } from "./memo"
|
||||||
export { createWebsocket } from "./websocket"
|
export { createWebsocket } from "./websocket"
|
||||||
|
export { downloadText } from "./download"
|
||||||
|
|
|
@ -427,8 +427,8 @@ export async function destroy(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function find(ctx: UserCtx) {
|
export async function find(ctx: UserCtx) {
|
||||||
const database = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const datasource = await database.get(ctx.params.datasourceId)
|
const datasource = await db.get(ctx.params.datasourceId)
|
||||||
ctx.body = await sdk.datasources.removeSecretSingle(datasource)
|
ctx.body = await sdk.datasources.removeSecretSingle(datasource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,7 +443,8 @@ export async function query(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExternalSchema(ctx: UserCtx) {
|
export async function getExternalSchema(ctx: UserCtx) {
|
||||||
const { datasource } = ctx.request.body
|
const db = context.getAppDB()
|
||||||
|
const datasource = await db.get(ctx.params.datasourceId)
|
||||||
const enrichedDatasource = await getAndMergeDatasource(datasource)
|
const enrichedDatasource = await getAndMergeDatasource(datasource)
|
||||||
const connector = await getConnector(enrichedDatasource)
|
const connector = await getConnector(enrichedDatasource)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import { roles, context, events } from "@budibase/backend-core"
|
import { roles, context, events, db as dbCore } from "@budibase/backend-core"
|
||||||
import {
|
import { getUserMetadataParams, InternalTables } from "../../db/utils"
|
||||||
generateRoleID,
|
|
||||||
getUserMetadataParams,
|
|
||||||
InternalTables,
|
|
||||||
} from "../../db/utils"
|
|
||||||
import { UserCtx, Database } from "@budibase/types"
|
import { UserCtx, Database } from "@budibase/types"
|
||||||
|
|
||||||
const UpdateRolesOptions = {
|
const UpdateRolesOptions = {
|
||||||
|
@ -55,6 +51,7 @@ export async function save(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let { _id, name, inherits, permissionId, version } = ctx.request.body
|
let { _id, name, inherits, permissionId, version } = ctx.request.body
|
||||||
let isCreate = false
|
let isCreate = false
|
||||||
|
const isNewVersion = version === roles.RoleIDVersion.NAME
|
||||||
|
|
||||||
if (_id && roles.isBuiltin(_id)) {
|
if (_id && roles.isBuiltin(_id)) {
|
||||||
ctx.throw(400, "Cannot update builtin roles.")
|
ctx.throw(400, "Cannot update builtin roles.")
|
||||||
|
@ -62,12 +59,20 @@ export async function save(ctx: UserCtx) {
|
||||||
|
|
||||||
// if not id found, then its creation
|
// if not id found, then its creation
|
||||||
if (!_id) {
|
if (!_id) {
|
||||||
_id = generateRoleID(name)
|
_id = dbCore.generateRoleID(name)
|
||||||
isCreate = true
|
isCreate = true
|
||||||
}
|
}
|
||||||
// version 2 roles need updated to add back role_
|
// version 2 roles need updated to add back role_
|
||||||
else if (version === roles.RoleIDVersion.NAME) {
|
else if (isNewVersion) {
|
||||||
_id = generateRoleID(name)
|
_id = dbCore.prefixRoleID(_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbRole
|
||||||
|
if (!isCreate) {
|
||||||
|
dbRole = await db.get(_id)
|
||||||
|
}
|
||||||
|
if (dbRole && dbRole.name !== name && isNewVersion) {
|
||||||
|
ctx.throw(400, "Cannot change custom role name")
|
||||||
}
|
}
|
||||||
|
|
||||||
const role = new roles.Role(_id, name, permissionId).addInheritance(inherits)
|
const role = new roles.Role(_id, name, permissionId).addInheritance(inherits)
|
||||||
|
@ -98,7 +103,7 @@ export async function destroy(ctx: UserCtx) {
|
||||||
ctx.throw(400, "Cannot delete builtin role.")
|
ctx.throw(400, "Cannot delete builtin role.")
|
||||||
} else {
|
} else {
|
||||||
// make sure has the prefix (if it has it then it won't be added)
|
// make sure has the prefix (if it has it then it won't be added)
|
||||||
roleId = generateRoleID(roleId)
|
roleId = dbCore.generateRoleID(roleId)
|
||||||
}
|
}
|
||||||
const role = await db.get(roleId)
|
const role = await db.get(roleId)
|
||||||
// first check no users actively attached to role
|
// first check no users actively attached to role
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { SourceName, SqlQuery, Datasource, Table } from "@budibase/types"
|
import { SourceName, SqlQuery, Datasource, Table } from "@budibase/types"
|
||||||
import { DocumentType, SEPARATOR } from "../db/utils"
|
import { DocumentType, SEPARATOR } from "../db/utils"
|
||||||
import { FieldTypes, BuildSchemaErrors, InvalidColumns } from "../constants"
|
import { FieldTypes, BuildSchemaErrors, InvalidColumns } from "../constants"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
|
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
|
||||||
const ROW_ID_REGEX = /^\[.*]$/g
|
const ROW_ID_REGEX = /^\[.*]$/g
|
||||||
|
@ -178,18 +179,7 @@ export function getSqlQuery(query: SqlQuery | string): SqlQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSQL(datasource: Datasource): boolean {
|
export const isSQL = helpers.isSQL
|
||||||
if (!datasource || !datasource.source) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const SQL = [
|
|
||||||
SourceName.POSTGRES,
|
|
||||||
SourceName.SQL_SERVER,
|
|
||||||
SourceName.MYSQL,
|
|
||||||
SourceName.ORACLE,
|
|
||||||
]
|
|
||||||
return SQL.indexOf(datasource.source) !== -1
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isIsoDateString(str: string) {
|
export function isIsoDateString(str: string) {
|
||||||
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)) {
|
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)) {
|
||||||
|
|
|
@ -1,5 +1,18 @@
|
||||||
import { SourceName } from "@budibase/types"
|
import { Datasource, SourceName } from "@budibase/types"
|
||||||
|
|
||||||
export function isGoogleSheets(type: SourceName) {
|
export function isGoogleSheets(type: SourceName) {
|
||||||
return type === SourceName.GOOGLE_SHEETS
|
return type === SourceName.GOOGLE_SHEETS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSQL(datasource: Datasource): boolean {
|
||||||
|
if (!datasource || !datasource.source) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const SQL = [
|
||||||
|
SourceName.POSTGRES,
|
||||||
|
SourceName.SQL_SERVER,
|
||||||
|
SourceName.MYSQL,
|
||||||
|
SourceName.ORACLE,
|
||||||
|
]
|
||||||
|
return SQL.indexOf(datasource.source) !== -1
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue