import and export apps
This commit is contained in:
parent
108d233040
commit
9f8c9fa810
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
let dbDumpReadStream
|
||||||
|
|
||||||
|
if (template.fileImportPath) {
|
||||||
|
dbDumpReadStream = fs.createReadStream(template.fileImportPath)
|
||||||
|
} else {
|
||||||
const templatePath = await downloadTemplate(...template.key.split("/"))
|
const templatePath = await downloadTemplate(...template.key.split("/"))
|
||||||
const dbDumpReadStream = fs.createReadStream(
|
dbDumpReadStream = fs.createReadStream(
|
||||||
join(templatePath, "db", "dump.txt")
|
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."
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue