Removing export all apps functionality from cloud - this was a very niche feature and often confusing, now there is a consistent flow for starting out in self host.
This commit is contained in:
parent
55ce83c444
commit
060dc05afa
|
@ -1,46 +0,0 @@
|
||||||
<script>
|
|
||||||
import { notifications, ModalContent, Dropzone, Body } from "@budibase/bbui"
|
|
||||||
import { API } from "api"
|
|
||||||
import { admin } from "stores/portal"
|
|
||||||
|
|
||||||
let submitting = false
|
|
||||||
|
|
||||||
$: value = { file: null }
|
|
||||||
|
|
||||||
async function importApps() {
|
|
||||||
submitting = true
|
|
||||||
try {
|
|
||||||
// Create form data to create app
|
|
||||||
let data = new FormData()
|
|
||||||
data.append("importFile", value.file)
|
|
||||||
|
|
||||||
// Create App
|
|
||||||
await API.importApps(data)
|
|
||||||
await admin.checkImportComplete()
|
|
||||||
notifications.success("Import complete, please finish registration!")
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Failed to import apps")
|
|
||||||
}
|
|
||||||
submitting = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent
|
|
||||||
title="Import apps"
|
|
||||||
confirmText="Import apps"
|
|
||||||
onConfirm={importApps}
|
|
||||||
disabled={!value.file}
|
|
||||||
>
|
|
||||||
<Body>
|
|
||||||
Please upload the file that was exported from your Cloud environment to get
|
|
||||||
started
|
|
||||||
</Body>
|
|
||||||
<Dropzone
|
|
||||||
gallery={false}
|
|
||||||
label="File to import"
|
|
||||||
value={[value.file]}
|
|
||||||
on:change={e => {
|
|
||||||
value.file = e.detail?.[0]
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ModalContent>
|
|
|
@ -10,10 +10,8 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { admin, auth } from "stores/portal"
|
import { admin, auth } from "stores/portal"
|
||||||
import ImportAppsModal from "./_components/ImportAppsModal.svelte"
|
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
import { onMount } from "svelte"
|
import { FancyForm, FancyInput } from "@budibase/bbui"
|
||||||
import { FancyForm, FancyInput, ActionButton } from "@budibase/bbui"
|
|
||||||
import { TestimonialPage } from "@budibase/frontend-core/src/components"
|
import { TestimonialPage } from "@budibase/frontend-core/src/components"
|
||||||
import { passwordsMatch, handleError } from "../auth/_components/utils"
|
import { passwordsMatch, handleError } from "../auth/_components/utils"
|
||||||
|
|
||||||
|
@ -24,8 +22,6 @@
|
||||||
let submitted = false
|
let submitted = false
|
||||||
|
|
||||||
$: tenantId = $auth.tenantId
|
$: tenantId = $auth.tenantId
|
||||||
$: cloud = $admin.cloud
|
|
||||||
$: imported = $admin.importComplete
|
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
form.validate()
|
form.validate()
|
||||||
|
@ -46,22 +42,8 @@
|
||||||
notifications.error("Failed to create admin user")
|
notifications.error("Failed to create admin user")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
if (!cloud) {
|
|
||||||
try {
|
|
||||||
await admin.checkImportComplete()
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error checking import status")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal} padding={false} width="600px">
|
|
||||||
<ImportAppsModal />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<TestimonialPage>
|
<TestimonialPage>
|
||||||
<Layout gap="M" noPadding>
|
<Layout gap="M" noPadding>
|
||||||
<Layout justifyItems="center" noPadding>
|
<Layout justifyItems="center" noPadding>
|
||||||
|
@ -156,20 +138,6 @@
|
||||||
Create super admin user
|
Create super admin user
|
||||||
</Button>
|
</Button>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="XS" noPadding justifyItems="center">
|
|
||||||
<div class="user-actions">
|
|
||||||
{#if !cloud && !imported}
|
|
||||||
<ActionButton
|
|
||||||
quiet
|
|
||||||
on:click={() => {
|
|
||||||
modal.show()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import from cloud
|
|
||||||
</ActionButton>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</TestimonialPage>
|
</TestimonialPage>
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,11 @@
|
||||||
notifications,
|
notifications,
|
||||||
Notification,
|
Notification,
|
||||||
Body,
|
Body,
|
||||||
Icon,
|
|
||||||
Search,
|
Search,
|
||||||
InlineAlert,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||||
import AppLimitModal from "components/portal/licensing/AppLimitModal.svelte"
|
import AppLimitModal from "components/portal/licensing/AppLimitModal.svelte"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
|
||||||
|
|
||||||
import { store, automationStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
@ -33,11 +30,9 @@
|
||||||
let appLimitModal
|
let appLimitModal
|
||||||
let creatingApp = false
|
let creatingApp = false
|
||||||
let searchTerm = ""
|
let searchTerm = ""
|
||||||
let cloud = $admin.cloud
|
|
||||||
let creatingFromTemplate = false
|
let creatingFromTemplate = false
|
||||||
let automationErrors
|
let automationErrors
|
||||||
let accessFilterList = null
|
let accessFilterList = null
|
||||||
let confirmDownloadDialog
|
|
||||||
|
|
||||||
$: welcomeHeader = `Welcome ${$auth?.user?.firstName || "back"}`
|
$: welcomeHeader = `Welcome ${$auth?.user?.firstName || "back"}`
|
||||||
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
||||||
|
@ -123,15 +118,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initiateAppsExport = () => {
|
|
||||||
try {
|
|
||||||
window.location = `/api/cloud/export`
|
|
||||||
notifications.success("Apps exported successfully")
|
|
||||||
} catch (err) {
|
|
||||||
notifications.error(`Error exporting apps: ${err}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const initiateAppImport = () => {
|
const initiateAppImport = () => {
|
||||||
template = { fromFile: true }
|
template = { fromFile: true }
|
||||||
creationModal.show()
|
creationModal.show()
|
||||||
|
@ -264,13 +250,6 @@
|
||||||
</div>
|
</div>
|
||||||
{#if enrichedApps.length > 1}
|
{#if enrichedApps.length > 1}
|
||||||
<div class="app-actions">
|
<div class="app-actions">
|
||||||
{#if cloud}
|
|
||||||
<Icon
|
|
||||||
name="Download"
|
|
||||||
hoverable
|
|
||||||
on:click={confirmDownloadDialog.show}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<Select
|
<Select
|
||||||
autoWidth
|
autoWidth
|
||||||
bind:value={sortBy}
|
bind:value={sortBy}
|
||||||
|
@ -316,18 +295,6 @@
|
||||||
|
|
||||||
<AppLimitModal bind:this={appLimitModal} />
|
<AppLimitModal bind:this={appLimitModal} />
|
||||||
|
|
||||||
<ConfirmDialog
|
|
||||||
bind:this={confirmDownloadDialog}
|
|
||||||
okText="Continue"
|
|
||||||
onOk={initiateAppsExport}
|
|
||||||
warning={false}
|
|
||||||
title="Download all apps"
|
|
||||||
>
|
|
||||||
<InlineAlert
|
|
||||||
header="Do not share your budibase application exports publicly as they may contain sensitive information such as database credentials or secret keys."
|
|
||||||
/>
|
|
||||||
</ConfirmDialog>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -37,14 +37,6 @@ export function createAdminStore() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkImportComplete() {
|
|
||||||
const result = await API.checkImportComplete()
|
|
||||||
admin.update(store => {
|
|
||||||
store.importComplete = result ? result.imported : false
|
|
||||||
return store
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getEnvironment() {
|
async function getEnvironment() {
|
||||||
const environment = await API.getEnvironment()
|
const environment = await API.getEnvironment()
|
||||||
admin.update(store => {
|
admin.update(store => {
|
||||||
|
@ -92,7 +84,6 @@ export function createAdminStore() {
|
||||||
return {
|
return {
|
||||||
subscribe: admin.subscribe,
|
subscribe: admin.subscribe,
|
||||||
init,
|
init,
|
||||||
checkImportComplete,
|
|
||||||
unload,
|
unload,
|
||||||
getChecklist,
|
getChecklist,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ vi.mock("svelte/store", () => {
|
||||||
vi.mock("api", () => {
|
vi.mock("api", () => {
|
||||||
return {
|
return {
|
||||||
API: {
|
API: {
|
||||||
checkImportComplete: vi.fn(),
|
|
||||||
getEnvironment: vi.fn(),
|
getEnvironment: vi.fn(),
|
||||||
getSystemStatus: vi.fn(),
|
getSystemStatus: vi.fn(),
|
||||||
getChecklist: vi.fn(),
|
getChecklist: vi.fn(),
|
||||||
|
@ -55,7 +54,6 @@ describe("admin store", () => {
|
||||||
expect(ctx.returnedStore).toEqual({
|
expect(ctx.returnedStore).toEqual({
|
||||||
subscribe: expect.toBe(ctx.writableReturn.subscribe),
|
subscribe: expect.toBe(ctx.writableReturn.subscribe),
|
||||||
init: expect.toBeFunc(),
|
init: expect.toBeFunc(),
|
||||||
checkImportComplete: expect.toBeFunc(),
|
|
||||||
unload: expect.toBeFunc(),
|
unload: expect.toBeFunc(),
|
||||||
getChecklist: expect.toBeFunc(),
|
getChecklist: expect.toBeFunc(),
|
||||||
})
|
})
|
||||||
|
@ -205,37 +203,6 @@ describe("admin store", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("checkImportComplete", () => {
|
|
||||||
describe("import complete", () => {
|
|
||||||
beforeEach(async ctx => {
|
|
||||||
API.checkImportComplete.mockReturnValue({ imported: true })
|
|
||||||
await ctx.returnedStore.checkImportComplete()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("updates the store's importComplete parameter", ctx => {
|
|
||||||
expect(ctx.writableReturn.update.calls[0][0]({ foo: "foo" })).toEqual({
|
|
||||||
foo: "foo",
|
|
||||||
importComplete: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("import not complete", () => {
|
|
||||||
beforeEach(async ctx => {
|
|
||||||
// Can be null
|
|
||||||
API.checkImportComplete.mockReturnValue(null)
|
|
||||||
await ctx.returnedStore.checkImportComplete()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("updates the store's importComplete parameter", ctx => {
|
|
||||||
expect(ctx.writableReturn.update.calls[0][0]({ foo: "foo" })).toEqual({
|
|
||||||
foo: "foo",
|
|
||||||
importComplete: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("unload", () => {
|
describe("unload", () => {
|
||||||
beforeEach(ctx => {
|
beforeEach(ctx => {
|
||||||
ctx.returnedStore.unload()
|
ctx.returnedStore.unload()
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
export const buildOtherEndpoints = API => ({
|
export const buildOtherEndpoints = API => ({
|
||||||
/**
|
|
||||||
* TODO: find out what this is
|
|
||||||
*/
|
|
||||||
checkImportComplete: async () => {
|
|
||||||
return await API.get({
|
|
||||||
url: "/api/cloud/import/complete",
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current environment details.
|
* Gets the current environment details.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
import env from "../../environment"
|
|
||||||
import { db as dbCore, tenancy } from "@budibase/backend-core"
|
|
||||||
import { streamFile } from "../../utilities/fileSystem"
|
|
||||||
import { stringToReadStream } from "../../utilities"
|
|
||||||
import { getDocParams, DocumentType, isDevAppID } from "../../db/utils"
|
|
||||||
import { create } from "./application"
|
|
||||||
import { join } from "path"
|
|
||||||
import sdk from "../../sdk"
|
|
||||||
import { App, Ctx, Database } from "@budibase/types"
|
|
||||||
|
|
||||||
async function createApp(appName: string, appDirectory: string) {
|
|
||||||
const ctx = {
|
|
||||||
request: {
|
|
||||||
body: {
|
|
||||||
useTemplate: true,
|
|
||||||
name: appName,
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
templateFile: {
|
|
||||||
path: appDirectory,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
return create(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAllDocType(db: Database, docType: string) {
|
|
||||||
const response = await db.allDocs(
|
|
||||||
getDocParams(docType, null, {
|
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
return response.rows.map(row => row.doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function exportApps(ctx: Ctx) {
|
|
||||||
if (env.SELF_HOSTED || !env.MULTI_TENANCY) {
|
|
||||||
ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.")
|
|
||||||
}
|
|
||||||
const apps = (await dbCore.getAllApps({ all: true })) as App[]
|
|
||||||
const globalDBString = await sdk.backups.exportDB(dbCore.getGlobalDBName(), {
|
|
||||||
filter: (doc: any) => !doc._id.startsWith(DocumentType.USER),
|
|
||||||
})
|
|
||||||
// only export the dev apps as they will be the latest, the user can republish the apps
|
|
||||||
// in their self-hosted environment
|
|
||||||
let appMetadata = apps
|
|
||||||
.filter((app: App) => isDevAppID(app.appId || app._id))
|
|
||||||
.map((app: App) => ({ appId: (app.appId || app._id)!, name: app.name }))
|
|
||||||
const tmpPath = await sdk.backups.exportMultipleApps(
|
|
||||||
appMetadata,
|
|
||||||
globalDBString
|
|
||||||
)
|
|
||||||
const filename = `cloud-export-${new Date().getTime()}.tar.gz`
|
|
||||||
ctx.attachment(filename)
|
|
||||||
ctx.body = streamFile(tmpPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkHasBeenImported() {
|
|
||||||
if (!env.SELF_HOSTED) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
const apps = await dbCore.getAllApps({ all: true })
|
|
||||||
return apps.length !== 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function hasBeenImported(ctx: Ctx) {
|
|
||||||
ctx.body = {
|
|
||||||
imported: await checkHasBeenImported(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function importApps(ctx: Ctx) {
|
|
||||||
if (!env.SELF_HOSTED) {
|
|
||||||
ctx.throw(400, "Importing only allowed in self hosted environments.")
|
|
||||||
}
|
|
||||||
const beenImported = await checkHasBeenImported()
|
|
||||||
if (beenImported || !ctx.request.files || !ctx.request.files.importFile) {
|
|
||||||
ctx.throw(
|
|
||||||
400,
|
|
||||||
"Import file is required and environment must be fresh to import apps."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const file = ctx.request.files.importFile as any
|
|
||||||
if (Array.isArray(file)) {
|
|
||||||
ctx.throw(400, "Single file is required")
|
|
||||||
}
|
|
||||||
if (file.type !== "application/gzip" && file.type !== "application/x-gzip") {
|
|
||||||
ctx.throw(400, "Import file must be a gzipped tarball.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// initially get all the app databases out of the tarball
|
|
||||||
const tmpPath = sdk.backups.untarFile(file)
|
|
||||||
const globalDbImport = sdk.backups.getGlobalDBFile(tmpPath)
|
|
||||||
const appNames = sdk.backups.getListOfAppsInMulti(tmpPath)
|
|
||||||
|
|
||||||
const globalDb = tenancy.getGlobalDB()
|
|
||||||
// load the global db first
|
|
||||||
await globalDb.load(stringToReadStream(globalDbImport))
|
|
||||||
for (let appName of appNames) {
|
|
||||||
await createApp(appName, join(tmpPath, appName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there are any users make sure to remove them
|
|
||||||
let users = await getAllDocType(globalDb, DocumentType.USER)
|
|
||||||
let userDeletionPromises = []
|
|
||||||
for (let user of users) {
|
|
||||||
userDeletionPromises.push(globalDb.remove(user._id, user._rev))
|
|
||||||
}
|
|
||||||
if (userDeletionPromises.length > 0) {
|
|
||||||
await Promise.all(userDeletionPromises)
|
|
||||||
}
|
|
||||||
|
|
||||||
await globalDb.bulkDocs(users)
|
|
||||||
ctx.body = {
|
|
||||||
message: "Apps successfully imported.",
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import currentApp from "../middleware/currentapp"
|
||||||
import zlib from "zlib"
|
import zlib from "zlib"
|
||||||
import { mainRoutes, staticRoutes, publicRoutes } from "./routes"
|
import { mainRoutes, staticRoutes, publicRoutes } from "./routes"
|
||||||
import pkg from "../../package.json"
|
import pkg from "../../package.json"
|
||||||
import env from "../environment"
|
|
||||||
import { middleware as pro } from "@budibase/pro"
|
import { middleware as pro } from "@budibase/pro"
|
||||||
export { shutdown } from "./routes/public"
|
export { shutdown } from "./routes/public"
|
||||||
const compress = require("koa-compress")
|
const compress = require("koa-compress")
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import Router from "@koa/router"
|
|
||||||
import * as controller from "../controllers/cloud"
|
|
||||||
import authorized from "../../middleware/authorized"
|
|
||||||
import { permissions } from "@budibase/backend-core"
|
|
||||||
|
|
||||||
const router: Router = new Router()
|
|
||||||
|
|
||||||
router
|
|
||||||
.get(
|
|
||||||
"/api/cloud/export",
|
|
||||||
authorized(permissions.BUILDER),
|
|
||||||
controller.exportApps
|
|
||||||
)
|
|
||||||
// has to be public, only run if apps don't exist
|
|
||||||
.post("/api/cloud/import", controller.importApps)
|
|
||||||
.get("/api/cloud/import/complete", controller.hasBeenImported)
|
|
||||||
|
|
||||||
export default router
|
|
|
@ -22,7 +22,6 @@ import queryRoutes from "./query"
|
||||||
import backupRoutes from "./backup"
|
import backupRoutes from "./backup"
|
||||||
import metadataRoutes from "./metadata"
|
import metadataRoutes from "./metadata"
|
||||||
import devRoutes from "./dev"
|
import devRoutes from "./dev"
|
||||||
import cloudRoutes from "./cloud"
|
|
||||||
import migrationRoutes from "./migrations"
|
import migrationRoutes from "./migrations"
|
||||||
import pluginRoutes from "./plugin"
|
import pluginRoutes from "./plugin"
|
||||||
import opsRoutes from "./ops"
|
import opsRoutes from "./ops"
|
||||||
|
@ -60,7 +59,6 @@ export const mainRoutes: Router[] = [
|
||||||
queryRoutes,
|
queryRoutes,
|
||||||
metadataRoutes,
|
metadataRoutes,
|
||||||
devRoutes,
|
devRoutes,
|
||||||
cloudRoutes,
|
|
||||||
rowRoutes,
|
rowRoutes,
|
||||||
migrationRoutes,
|
migrationRoutes,
|
||||||
pluginRoutes,
|
pluginRoutes,
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
import { App } from "@budibase/types"
|
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
|
||||||
|
|
||||||
import { AppStatus } from "../../../db/utils"
|
|
||||||
|
|
||||||
import * as setup from "./utilities"
|
|
||||||
|
|
||||||
import { wipeDb } from "./utilities/TestFunctions"
|
|
||||||
import { tenancy } from "@budibase/backend-core"
|
|
||||||
|
|
||||||
describe("/cloud", () => {
|
|
||||||
let request = setup.getRequest()!
|
|
||||||
let config = setup.getConfig()
|
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
// Importing is only allowed in self hosted environments
|
|
||||||
await config.init()
|
|
||||||
config.modeSelf()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("import", () => {
|
|
||||||
it("should be able to import apps", async () => {
|
|
||||||
// first we need to delete any existing apps on the system so it looks clean otherwise the
|
|
||||||
// import will not run
|
|
||||||
await wipeDb()
|
|
||||||
|
|
||||||
// Perform the import
|
|
||||||
const res = await request
|
|
||||||
.post(`/api/cloud/import`)
|
|
||||||
.set(config.publicHeaders())
|
|
||||||
.attach("importFile", "src/api/routes/tests/data/export-test.tar.gz")
|
|
||||||
.expect(200)
|
|
||||||
expect(res.body.message).toEqual("Apps successfully imported.")
|
|
||||||
|
|
||||||
// get a count of apps after the import
|
|
||||||
const postImportApps = await request
|
|
||||||
.get(`/api/applications?status=${AppStatus.ALL}`)
|
|
||||||
.set(config.publicHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
const apps = postImportApps.body as App[]
|
|
||||||
// There are two apps in the file that was imported so check for this
|
|
||||||
expect(apps.length).toEqual(2)
|
|
||||||
// The new tenant id was assigned to the imported apps
|
|
||||||
expect(tenancy.getTenantIDFromAppID(apps[0].appId)).toBe(
|
|
||||||
config.getTenantId()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -148,41 +148,6 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Export all apps + global DB (if supplied) to a single tarball, this includes
|
|
||||||
* the attachments for each app as well.
|
|
||||||
* @param {object[]} appMetadata The IDs and names of apps to export.
|
|
||||||
* @param {string} globalDbContents The contents of the global DB to export as well.
|
|
||||||
* @return {string} The path to the tarball.
|
|
||||||
*/
|
|
||||||
export async function exportMultipleApps(
|
|
||||||
appMetadata: { appId: string; name: string }[],
|
|
||||||
globalDbContents?: string
|
|
||||||
) {
|
|
||||||
const tmpPath = join(budibaseTempDir(), uuid())
|
|
||||||
fs.mkdirSync(tmpPath)
|
|
||||||
let exportPromises: Promise<void>[] = []
|
|
||||||
// export each app to a directory, then move it into the complete export
|
|
||||||
const exportAndMove = async (appId: string, appName: string) => {
|
|
||||||
const path = await exportApp(appId)
|
|
||||||
await fs.promises.rename(path, join(tmpPath, appName))
|
|
||||||
}
|
|
||||||
for (let metadata of appMetadata) {
|
|
||||||
exportPromises.push(exportAndMove(metadata.appId, metadata.name))
|
|
||||||
}
|
|
||||||
// wait for all exports to finish
|
|
||||||
await Promise.all(exportPromises)
|
|
||||||
// add the global DB contents
|
|
||||||
if (globalDbContents) {
|
|
||||||
fs.writeFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), globalDbContents)
|
|
||||||
}
|
|
||||||
const appNames = appMetadata.map(metadata => metadata.name)
|
|
||||||
const tarPath = tarFilesToTmp(tmpPath, [...appNames, GLOBAL_DB_EXPORT_FILE])
|
|
||||||
// clear up the tmp path now tarball generated
|
|
||||||
fs.rmSync(tmpPath, { recursive: true, force: true })
|
|
||||||
return tarPath
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Streams a backup of the database state for an app
|
* Streams a backup of the database state for an app
|
||||||
* @param {string} appId The ID of the app which is to be backed up.
|
* @param {string} appId The ID of the app which is to be backed up.
|
||||||
|
|
96
yarn.lock
96
yarn.lock
|
@ -1386,45 +1386,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.5.6-alpha.3":
|
|
||||||
version "2.5.6-alpha.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.5.6-alpha.3.tgz#686d1533a3218507e4c82a58bf73caf875e0d895"
|
|
||||||
integrity sha512-TYaGj1G1h3K/QNwSanEFbwxlA04T3dc0DDHDgauyvqX8kHXYhY11BDn8xqrCQrpqXC7XlC4nL78NrhZiDJ+eFA==
|
|
||||||
dependencies:
|
|
||||||
"@budibase/nano" "10.1.2"
|
|
||||||
"@budibase/pouchdb-replication-stream" "1.2.10"
|
|
||||||
"@budibase/types" "2.5.6-alpha.3"
|
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
|
||||||
aws-cloudfront-sign "2.2.0"
|
|
||||||
aws-sdk "2.1030.0"
|
|
||||||
bcrypt "5.0.1"
|
|
||||||
bcryptjs "2.4.3"
|
|
||||||
bull "4.10.1"
|
|
||||||
correlation-id "4.0.0"
|
|
||||||
dotenv "16.0.1"
|
|
||||||
emitter-listener "1.1.2"
|
|
||||||
ioredis "4.28.0"
|
|
||||||
joi "17.6.0"
|
|
||||||
jsonwebtoken "9.0.0"
|
|
||||||
koa-passport "4.1.4"
|
|
||||||
koa-pino-logger "4.0.0"
|
|
||||||
lodash "4.17.21"
|
|
||||||
lodash.isarguments "3.1.0"
|
|
||||||
node-fetch "2.6.7"
|
|
||||||
passport-google-oauth "2.0.0"
|
|
||||||
passport-jwt "4.0.0"
|
|
||||||
passport-local "1.0.0"
|
|
||||||
passport-oauth2-refresh "^2.1.0"
|
|
||||||
posthog-node "1.3.0"
|
|
||||||
pouchdb "7.3.0"
|
|
||||||
pouchdb-find "7.2.2"
|
|
||||||
redlock "4.2.0"
|
|
||||||
sanitize-s3-objectkey "0.0.1"
|
|
||||||
semver "7.3.7"
|
|
||||||
tar-fs "2.1.1"
|
|
||||||
uuid "8.3.2"
|
|
||||||
|
|
||||||
"@budibase/bbui@^0.9.139":
|
"@budibase/bbui@^0.9.139":
|
||||||
version "0.9.190"
|
version "0.9.190"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.190.tgz#e1ec400ac90f556bfbc80fc23a04506f1585ea81"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.190.tgz#e1ec400ac90f556bfbc80fc23a04506f1585ea81"
|
||||||
|
@ -1525,15 +1486,15 @@
|
||||||
pouchdb-promise "^6.0.4"
|
pouchdb-promise "^6.0.4"
|
||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
|
|
||||||
"@budibase/pro@2.5.6-alpha.5":
|
"@budibase/pro@2.5.6-alpha.6":
|
||||||
version "2.5.6-alpha.5"
|
version "2.5.6-alpha.6"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.5.6-alpha.5.tgz#8136ea50e06aab8a23e48359d58c6c84ba386263"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.5.6-alpha.6.tgz#079c9f6ee1e0efdc1487d841d24f765eb8c3ca40"
|
||||||
integrity sha512-YEcDRJU4Up7g/fMSnO8K8YH6gObO/Sy0Jh28Ms+Sxb9RQ/zqlcobonsICF6h2ntfzLZH2cfArLGFEvIT10/PvA==
|
integrity sha512-7R51B1FIlXVolMoWa/WmDrpDvSqtrbZywIzOHq6NRbdvVSBCW3sD+biDIRXo9MFvNT9sLJXB21A4DLjfIYDpOA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.5.6-alpha.5"
|
"@budibase/backend-core" "2.5.6-alpha.6"
|
||||||
"@budibase/shared-core" "2.4.44-alpha.1"
|
"@budibase/shared-core" "2.4.44-alpha.1"
|
||||||
"@budibase/string-templates" "2.4.44-alpha.1"
|
"@budibase/string-templates" "2.4.44-alpha.1"
|
||||||
"@budibase/types" "2.5.6-alpha.5"
|
"@budibase/types" "2.5.6-alpha.6"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
|
@ -1586,13 +1547,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.4.44-alpha.1.tgz#1679657aa180d9c59afa1dffa611bff0638bd933"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.4.44-alpha.1.tgz#1679657aa180d9c59afa1dffa611bff0638bd933"
|
||||||
integrity sha512-Sq+8HfM75EBMoOvKYFwELdlxmVN6wNZMofDjT/2G+9aF+Zfe5Tzw69C+unmdBgcGGjGCHEYWSz4mF0v8FPAGbg==
|
integrity sha512-Sq+8HfM75EBMoOvKYFwELdlxmVN6wNZMofDjT/2G+9aF+Zfe5Tzw69C+unmdBgcGGjGCHEYWSz4mF0v8FPAGbg==
|
||||||
|
|
||||||
"@budibase/types@2.5.6-alpha.3":
|
|
||||||
version "2.5.6-alpha.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.5.6-alpha.3.tgz#fd7656599d388d2d5a21806b9058a2656793ec87"
|
|
||||||
integrity sha512-SD9WTXb2A+3jqEEk8HoEDwphm5tiwfQWiBFOzTZlooWiS5M6UGxKCgXzPv0Ad9Pfxi2oGxfGh7qJcNgHQDoE3Q==
|
|
||||||
dependencies:
|
|
||||||
scim-patch "^0.7.0"
|
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.7.0.tgz#231f687187c0cb34e0b97f463917b6aaeb4ef6af"
|
resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.7.0.tgz#231f687187c0cb34e0b97f463917b6aaeb4ef6af"
|
||||||
|
@ -3053,7 +3007,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13":
|
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||||
version "1.4.15"
|
version "1.4.15"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||||
|
@ -3763,7 +3717,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
"@rollup/plugin-commonjs@16.0.0", "@rollup/plugin-commonjs@^16.0.0":
|
"@rollup/plugin-commonjs@^16.0.0":
|
||||||
version "16.0.0"
|
version "16.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz#169004d56cd0f0a1d0f35915d31a036b0efe281f"
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz#169004d56cd0f0a1d0f35915d31a036b0efe281f"
|
||||||
integrity sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==
|
integrity sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==
|
||||||
|
@ -3846,22 +3800,6 @@
|
||||||
"@rollup/pluginutils" "^3.1.0"
|
"@rollup/pluginutils" "^3.1.0"
|
||||||
magic-string "^0.25.7"
|
magic-string "^0.25.7"
|
||||||
|
|
||||||
"@rollup/plugin-replace@^5.0.2":
|
|
||||||
version "5.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz#45f53501b16311feded2485e98419acb8448c61d"
|
|
||||||
integrity sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==
|
|
||||||
dependencies:
|
|
||||||
"@rollup/pluginutils" "^5.0.1"
|
|
||||||
magic-string "^0.27.0"
|
|
||||||
|
|
||||||
"@rollup/plugin-typescript@8.3.0":
|
|
||||||
version "8.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz#bc1077fa5897b980fc27e376c4e377882c63e68b"
|
|
||||||
integrity sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA==
|
|
||||||
dependencies:
|
|
||||||
"@rollup/pluginutils" "^3.1.0"
|
|
||||||
resolve "^1.17.0"
|
|
||||||
|
|
||||||
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
|
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
|
||||||
|
@ -11760,7 +11698,7 @@ fs.realpath@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||||
|
|
||||||
fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2:
|
fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||||
|
@ -16750,13 +16688,6 @@ magic-string@^0.26.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
sourcemap-codec "^1.4.8"
|
sourcemap-codec "^1.4.8"
|
||||||
|
|
||||||
magic-string@^0.27.0:
|
|
||||||
version "0.27.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"
|
|
||||||
integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==
|
|
||||||
dependencies:
|
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.13"
|
|
||||||
|
|
||||||
make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.1.0:
|
make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||||
|
@ -21486,13 +21417,6 @@ rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.6.0,
|
||||||
dependencies:
|
dependencies:
|
||||||
estree-walker "^0.6.1"
|
estree-walker "^0.6.1"
|
||||||
|
|
||||||
rollup@2.45.2:
|
|
||||||
version "2.45.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48"
|
|
||||||
integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==
|
|
||||||
optionalDependencies:
|
|
||||||
fsevents "~2.3.1"
|
|
||||||
|
|
||||||
rollup@^2.36.2, rollup@^2.44.0, rollup@^2.45.2, rollup@^2.79.1:
|
rollup@^2.36.2, rollup@^2.44.0, rollup@^2.45.2, rollup@^2.79.1:
|
||||||
version "2.79.1"
|
version "2.79.1"
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
|
||||||
|
@ -23455,7 +23379,7 @@ timed-out@^4.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
||||||
integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==
|
integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==
|
||||||
|
|
||||||
timekeeper@2.2.0, timekeeper@^2.2.0:
|
timekeeper@2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/timekeeper/-/timekeeper-2.2.0.tgz#9645731fce9e3280a18614a57a9d1b72af3ca368"
|
resolved "https://registry.yarnpkg.com/timekeeper/-/timekeeper-2.2.0.tgz#9645731fce9e3280a18614a57a9d1b72af3ca368"
|
||||||
integrity sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==
|
integrity sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==
|
||||||
|
|
Loading…
Reference in New Issue