Merge branch 'feature/draft-apps' of github.com:Budibase/budibase into feature/draft-apps
This commit is contained in:
commit
332f0555a3
|
@ -11,47 +11,9 @@ class Replication {
|
||||||
this.target = getDB(target)
|
this.target = getDB(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
sync(opts) {
|
promisify(operation, opts = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(resolve => {
|
||||||
this.source
|
operation(this.target, opts)
|
||||||
.sync(this.target, opts)
|
|
||||||
.on("change", function (info) {
|
|
||||||
// handle change
|
|
||||||
})
|
|
||||||
.on("paused", function (err) {
|
|
||||||
// replication paused (e.g. replication up to date, user went offline)
|
|
||||||
})
|
|
||||||
.on("active", function () {
|
|
||||||
// replicate resumed (e.g. new changes replicating, user went back online)
|
|
||||||
})
|
|
||||||
.on("denied", function (err) {
|
|
||||||
// a document failed to replicate (e.g. due to permissions)
|
|
||||||
return reject(
|
|
||||||
new Error(`Denied: Document failed to replicate ${err}`)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on("complete", function (info) {
|
|
||||||
return resolve(info)
|
|
||||||
})
|
|
||||||
.on("error", function (err) {
|
|
||||||
return reject(new Error(`Replication Error: ${err}`))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
replicate() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.replication = this.source.replicate
|
|
||||||
.to(this.target)
|
|
||||||
// .on("change", function (info) {
|
|
||||||
// // handle change
|
|
||||||
// })
|
|
||||||
// .on("paused", function (err) {
|
|
||||||
// // replication paused (e.g. replication up to date, user went offline)
|
|
||||||
// })
|
|
||||||
// .on("active", function () {
|
|
||||||
// // replicate resumed (e.g. new changes replicating, user went back online)
|
|
||||||
// })
|
|
||||||
.on("denied", function (err) {
|
.on("denied", function (err) {
|
||||||
// a document failed to replicate (e.g. due to permissions)
|
// a document failed to replicate (e.g. due to permissions)
|
||||||
throw new Error(`Denied: Document failed to replicate ${err}`)
|
throw new Error(`Denied: Document failed to replicate ${err}`)
|
||||||
|
@ -65,6 +27,40 @@ class Replication {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two way replication operation, intended to be promise based.
|
||||||
|
* @param {Object} opts - PouchDB replication options
|
||||||
|
*/
|
||||||
|
sync(opts) {
|
||||||
|
this.replication = this.promisify(this.source.sync, opts)
|
||||||
|
return this.replication
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One way replication operation, intended to be promise based.
|
||||||
|
* @param {Object} opts - PouchDB replication options
|
||||||
|
*/
|
||||||
|
replicate(opts) {
|
||||||
|
this.replication = this.promisify(this.source.replicate.to, opts)
|
||||||
|
return this.replication
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up an ongoing live sync between 2 CouchDB databases.
|
||||||
|
* @param {Object} opts - PouchDB replication options
|
||||||
|
*/
|
||||||
|
subscribe(opts = {}) {
|
||||||
|
this.replication = this.source.replicate
|
||||||
|
.to(this.target, {
|
||||||
|
live: true,
|
||||||
|
retry: true,
|
||||||
|
...opts,
|
||||||
|
})
|
||||||
|
.on("error", function (err) {
|
||||||
|
throw new Error(`Replication Error: ${err}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async rollback() {
|
async rollback() {
|
||||||
await this.target.destroy()
|
await this.target.destroy()
|
||||||
await this.replicate()
|
await this.replicate()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { General, DangerZone, APIKeys } from "./tabs"
|
import { General, DangerZone } from "./tabs"
|
||||||
import { ModalContent, Tab, Tabs } from "@budibase/bbui"
|
import { ModalContent, Tab, Tabs } from "@budibase/bbui"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@
|
||||||
<Tab title="General">
|
<Tab title="General">
|
||||||
<General />
|
<General />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="API Keys">
|
<!-- <Tab title="API Keys">
|
||||||
<APIKeys />
|
<APIKeys />
|
||||||
</Tab>
|
</Tab> -->
|
||||||
<Tab title="Danger Zone">
|
<Tab title="Danger Zone">
|
||||||
<DangerZone />
|
<DangerZone />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let openApp
|
export let openApp
|
||||||
export let deleteApp
|
export let deleteApp
|
||||||
export let releaseLock
|
export let releaseLock
|
||||||
|
export let deletable
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
@ -34,9 +35,11 @@
|
||||||
<MenuItem on:click={() => exportApp(app)} icon="Download">
|
<MenuItem on:click={() => exportApp(app)} icon="Download">
|
||||||
Export
|
Export
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
|
{#if deletable}
|
||||||
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
|
||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
||||||
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen">
|
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen">
|
||||||
Release Lock
|
Release Lock
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
export let openApp
|
export let openApp
|
||||||
export let exportApp
|
export let exportApp
|
||||||
export let deleteApp
|
export let deleteApp
|
||||||
export let last
|
|
||||||
export let releaseLock
|
export let releaseLock
|
||||||
|
export let last
|
||||||
|
export let deletable
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="title" class:last>
|
<div class="title" class:last>
|
||||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<Link on:click={openApp}>
|
<Link on:click={() => openApp(app)}>
|
||||||
<Heading size="XS">
|
<Heading size="XS">
|
||||||
{app.name}
|
{app.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
@ -45,7 +46,9 @@
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<Icon hoverable slot="control" name="More" />
|
<Icon hoverable slot="control" name="More" />
|
||||||
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
|
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
|
||||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
{#if deletable}
|
||||||
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
||||||
|
{/if}
|
||||||
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
||||||
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen">
|
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen">
|
||||||
Release Lock
|
Release Lock
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const FrontendTypes = {
|
||||||
|
|
||||||
export const AppStatus = {
|
export const AppStatus = {
|
||||||
DEV: "dev",
|
DEV: "dev",
|
||||||
|
PUBLISHED: "published"
|
||||||
}
|
}
|
||||||
|
|
||||||
// fields on the user table that cannot be edited
|
// fields on the user table that cannot be edited
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<img src={Rocket} alt="Rocket flying through sky" />
|
<img src={Rocket} alt="Rocket flying through sky" />
|
||||||
<div>
|
<div>
|
||||||
<Heading size="M">It's time to shine!</Heading>
|
<Heading size="M">It's time to shine!</Heading>
|
||||||
<Button size="XL" cta medium on:click={deployApp}>Deploy App</Button>
|
<Button size="XL" cta medium on:click={deployApp}>Publish App</Button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<Modal bind:this={feedbackModal}>
|
<Modal bind:this={feedbackModal}>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
|
|
||||||
let layout = "grid"
|
let layout = "grid"
|
||||||
let appStatus = "deployed"
|
let appStatus = AppStatus.PUBLISHED
|
||||||
let template
|
let template
|
||||||
let appToDelete
|
let appToDelete
|
||||||
let creationModal
|
let creationModal
|
||||||
|
@ -95,6 +95,7 @@
|
||||||
await del(`/api/applications/${appToDelete?._id}`)
|
await del(`/api/applications/${appToDelete?._id}`)
|
||||||
await apps.load()
|
await apps.load()
|
||||||
appToDelete = null
|
appToDelete = null
|
||||||
|
notifications.success("App deleted successfully.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const releaseLock = async appId => {
|
const releaseLock = async appId => {
|
||||||
|
@ -131,8 +132,8 @@
|
||||||
<Select
|
<Select
|
||||||
bind:value={appStatus}
|
bind:value={appStatus}
|
||||||
options={[
|
options={[
|
||||||
{ label: "Deployed", value: "deployed" },
|
{ label: "Published", value: AppStatus.PUBLISHED },
|
||||||
{ label: "In Development", value: "dev" },
|
{ label: "In Development", value: AppStatus.DEV },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -159,6 +160,7 @@
|
||||||
{#each $apps as app, idx (app._id)}
|
{#each $apps as app, idx (app._id)}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={layout === "grid" ? AppCard : AppRow}
|
this={layout === "grid" ? AppCard : AppRow}
|
||||||
|
deletable={appStatus === AppStatus.PUBLISHED}
|
||||||
{releaseLock}
|
{releaseLock}
|
||||||
{app}
|
{app}
|
||||||
{openApp}
|
{openApp}
|
||||||
|
|
|
@ -57,24 +57,30 @@ async function storeLocalDeploymentHistory(deployment) {
|
||||||
|
|
||||||
async function deployApp(deployment) {
|
async function deployApp(deployment) {
|
||||||
try {
|
try {
|
||||||
const deployTarget = deployment.appId.replace("_dev", "")
|
const productionAppId = deployment.appId.replace("_dev", "")
|
||||||
|
|
||||||
const replication = new Replication({
|
const replication = new Replication({
|
||||||
source: deployment.appId,
|
source: deployment.appId,
|
||||||
target: deployTarget,
|
target: productionAppId,
|
||||||
})
|
})
|
||||||
|
|
||||||
await replication.replicate()
|
await replication.replicate()
|
||||||
|
|
||||||
// Strip the _dev prefix and update the appID document in the new DB
|
// Strip the _dev prefix and update the appID document in the new DB
|
||||||
const db = new PouchDB(deployTarget)
|
const db = new PouchDB(productionAppId)
|
||||||
const appDoc = await db.get(deployment.appId)
|
const appDoc = await db.get(deployment.appId)
|
||||||
await db.remove(appDoc)
|
appDoc._id = productionAppId
|
||||||
appDoc._id = deployTarget
|
|
||||||
delete appDoc._rev
|
delete appDoc._rev
|
||||||
appDoc.instance._id = deployTarget
|
appDoc.instance._id = productionAppId
|
||||||
await db.put(appDoc)
|
await db.put(appDoc)
|
||||||
|
|
||||||
|
// Set up live sync between the live and dev instances
|
||||||
|
const liveReplication = new Replication({
|
||||||
|
source: productionAppId,
|
||||||
|
target: deployment.appId,
|
||||||
|
})
|
||||||
|
liveReplication.subscribe()
|
||||||
|
|
||||||
deployment.setStatus(DeploymentStatus.SUCCESS)
|
deployment.setStatus(DeploymentStatus.SUCCESS)
|
||||||
await storeLocalDeploymentHistory(deployment)
|
await storeLocalDeploymentHistory(deployment)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ const StaticDatabases = {
|
||||||
|
|
||||||
const AppStatus = {
|
const AppStatus = {
|
||||||
DEV: "dev",
|
DEV: "dev",
|
||||||
DEPLOYED: "deployed",
|
DEPLOYED: "PUBLISHED",
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
|
|
Loading…
Reference in New Issue