import and export apps

This commit is contained in:
Martin McKeaveney 2021-01-27 13:55:46 +00:00
parent 108d233040
commit 9f8c9fa810
10 changed files with 134 additions and 16 deletions

10
hosting/Dockerfile.all Normal file
View File

@ -0,0 +1,10 @@
FROM ubuntu:latest
FROM envoyproxy/envoy:v1.16-latest
COPY envoy.yaml /etc/envoy/envoy.yaml
FROM apache/couchdb:3.0 as db
FROM minio/minio

View File

@ -2,7 +2,27 @@
import { TextButton } from "@budibase/bbui" import { TextButton } from "@budibase/bbui"
import { Heading } from "@budibase/bbui" import { Heading } from "@budibase/bbui"
import { Spacer } from "@budibase/bbui" import { Spacer } from "@budibase/bbui"
import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications"
import Spinner from "components/common/Spinner.svelte"
export let name, _id export let name, _id
let appExportLoading = false
async function exportApp() {
appExportLoading = true
try {
const response = await api.post(`/api/backups/export`, { appId: _id })
const { url } = await response.json()
notifier.success("App Export Complete.")
window.location = url
} catch (err) {
notifier.danger("App Export Failed.")
} finally {
appExportLoading = false
}
}
</script> </script>
<div class="apps-card"> <div class="apps-card">
@ -10,9 +30,12 @@
<Spacer medium /> <Spacer medium />
<div class="card-footer"> <div class="card-footer">
<TextButton text medium blue href="/_builder/{_id}"> <TextButton text medium blue href="/_builder/{_id}">
Open Open {name}
{name} </TextButton>
<TextButton text medium blue on:click={exportApp}>
{#if appExportLoading}
<Spinner size="10" />
{:else}Export{/if}
</TextButton> </TextButton>
</div> </div>
</div> </div>

View File

@ -1,14 +1,25 @@
<script> <script>
import { Label, Heading, Input } from "@budibase/bbui" import { Label, Heading, Input } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte"
export let validationErrors export let validationErrors
export let template export let template
let blurred = { appName: false } let blurred = { appName: false }
let files
$: if (files) {
template.fileImportPath = files[0]
}
</script> </script>
<h2>Create your Web App</h2> <h2>Create your Web App</h2>
<div class="container"> <div class="container">
{#if template} {#if template.fromFile}
<div class="template">
<Dropzone bind:files />
</div>
{:else if template}
<div class="template"> <div class="template">
<Label extraSmall grey>Selected Template</Label> <Label extraSmall grey>Selected Template</Label>
<Heading small>{template.name}</Heading> <Heading small>{template.name}</Heading>

View File

@ -46,12 +46,18 @@
modal.show() modal.show()
} }
function initiateAppImport() {
template = { fromFile: true }
modal.show()
}
checkIfKeysAndApps() checkIfKeysAndApps()
</script> </script>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<Heading medium black>Welcome to the Budibase Beta</Heading> <Heading medium black>Welcome to the Budibase Beta</Heading>
<Button secondary on:click={initiateAppImport}>Import Web App</Button>
<Button primary on:click={modal.show}>Create New Web App</Button> <Button primary on:click={modal.show}>Create New Web App</Button>
</div> </div>

View File

@ -105,10 +105,16 @@ async function createInstance(template) {
// replicate the template data to the instance DB // replicate the template data to the instance DB
if (template) { if (template) {
const templatePath = await downloadTemplate(...template.key.split("/")) let dbDumpReadStream
const dbDumpReadStream = fs.createReadStream(
join(templatePath, "db", "dump.txt") if (template.fileImportPath) {
) dbDumpReadStream = fs.createReadStream(template.fileImportPath)
} else {
const templatePath = await downloadTemplate(...template.key.split("/"))
dbDumpReadStream = fs.createReadStream(
join(templatePath, "db", "dump.txt")
)
}
const { ok } = await db.load(dbDumpReadStream) const { ok } = await db.load(dbDumpReadStream)
if (!ok) { if (!ok) {
throw "Error loading database dump from template." throw "Error loading database dump from template."

View File

@ -0,0 +1,36 @@
const { performDump } = require("../../utilities/templates")
const path = require("path")
const os = require("os")
const fs = require("fs-extra")
exports.exportAppDump = async function(ctx) {
const { appId } = ctx.request.body
const backupsDir = path.join(os.homedir(), ".budibase", "backups")
fs.ensureDirSync(backupsDir)
const backupIdentifier = `${appId} Backup: ${new Date()}.txt`
await performDump({
dir: backupsDir,
appId,
name: backupIdentifier,
})
ctx.status = 200
ctx.body = {
url: `/api/backups/download/${backupIdentifier}`,
}
}
exports.downloadAppDump = async function(ctx) {
const fileName = ctx.params.fileName
const backupsDir = path.join(os.homedir(), ".budibase", "backups")
fs.ensureDirSync(backupsDir)
const backupFile = path.join(backupsDir, fileName)
ctx.attachment(fileName)
ctx.body = fs.createReadStream(backupFile)
}

View File

@ -5,6 +5,7 @@ const zlib = require("zlib")
const { budibaseAppsDir } = require("../utilities/budibaseDir") const { budibaseAppsDir } = require("../utilities/budibaseDir")
const { isDev } = require("../utilities") const { isDev } = require("../utilities")
const { mainRoutes, authRoutes, staticRoutes } = require("./routes") const { mainRoutes, authRoutes, staticRoutes } = require("./routes")
const pkg = require("../../package.json")
const router = new Router() const router = new Router()
const env = require("../environment") const env = require("../environment")
@ -32,6 +33,7 @@ router
await next() await next()
}) })
.use("/health", ctx => (ctx.status = 200)) .use("/health", ctx => (ctx.status = 200))
.use("/version", ctx => (ctx.body = pkg.version))
.use(authenticated) .use(authenticated)
// error handling middleware // error handling middleware

View File

@ -0,0 +1,16 @@
const Router = require("@koa/router")
const controller = require("../controllers/backup")
const authorized = require("../../middleware/authorized")
const { BUILDER } = require("../../utilities/security/permissions")
const router = Router()
router
.post("/api/backups/export", authorized(BUILDER), controller.exportAppDump)
.get(
"/api/backups/download/:fileName",
authorized(BUILDER),
controller.downloadAppDump
)
module.exports = router

View File

@ -21,6 +21,7 @@ const permissionRoutes = require("./permission")
const datasourceRoutes = require("./datasource") const datasourceRoutes = require("./datasource")
const queryRoutes = require("./query") const queryRoutes = require("./query")
const hostingRoutes = require("./hosting") const hostingRoutes = require("./hosting")
const backupRoutes = require("./backup")
exports.mainRoutes = [ exports.mainRoutes = [
deployRoutes, deployRoutes,
@ -42,6 +43,7 @@ exports.mainRoutes = [
datasourceRoutes, datasourceRoutes,
queryRoutes, queryRoutes,
hostingRoutes, hostingRoutes,
backupRoutes,
// 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,

View File

@ -56,6 +56,19 @@ exports.downloadTemplate = async function(type, name) {
return dirName return dirName
} }
async function performDump({ dir, appId, name = "dump.txt" }) {
const writeStream = fs.createWriteStream(join(dir, name))
// perform couch dump
const instanceDb = new CouchDB(appId)
await instanceDb.dump(writeStream, {
filter: doc => {
return !doc._id.startsWith(DocumentTypes.USER)
},
})
}
exports.performDump = performDump
exports.exportTemplateFromApp = async function({ templateName, appId }) { exports.exportTemplateFromApp = async function({ templateName, appId }) {
// Copy frontend files // Copy frontend files
const templatesDir = join( const templatesDir = join(
@ -67,13 +80,6 @@ exports.exportTemplateFromApp = async function({ templateName, appId }) {
"db" "db"
) )
fs.ensureDirSync(templatesDir) fs.ensureDirSync(templatesDir)
const writeStream = fs.createWriteStream(join(templatesDir, "dump.txt")) await performDump({ dir: templatesDir, appId })
// perform couch dump
const instanceDb = new CouchDB(appId)
await instanceDb.dump(writeStream, {
filter: doc => {
return !doc._id.startsWith(DocumentTypes.USER)
},
})
return templatesDir return templatesDir
} }