Merge branch 'feature/draft-apps' of github.com:Budibase/budibase into feature/draft-apps
This commit is contained in:
commit
17504443df
|
@ -31,7 +31,7 @@ class Replication {
|
||||||
* Two way replication operation, intended to be promise based.
|
* Two way replication operation, intended to be promise based.
|
||||||
* @param {Object} opts - PouchDB replication options
|
* @param {Object} opts - PouchDB replication options
|
||||||
*/
|
*/
|
||||||
sync(opts) {
|
sync(opts = {}) {
|
||||||
this.replication = this.promisify(this.source.sync, opts)
|
this.replication = this.promisify(this.source.sync, opts)
|
||||||
return this.replication
|
return this.replication
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ class Replication {
|
||||||
* One way replication operation, intended to be promise based.
|
* One way replication operation, intended to be promise based.
|
||||||
* @param {Object} opts - PouchDB replication options
|
* @param {Object} opts - PouchDB replication options
|
||||||
*/
|
*/
|
||||||
replicate(opts) {
|
replicate(opts = {}) {
|
||||||
this.replication = this.promisify(this.source.replicate.to, opts)
|
this.replication = this.promisify(this.source.replicate.to, opts)
|
||||||
return this.replication
|
return this.replication
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,13 @@ class Replication {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback the target DB back to the state of the source DB
|
||||||
|
*/
|
||||||
async rollback() {
|
async rollback() {
|
||||||
await this.target.destroy()
|
await this.target.destroy()
|
||||||
|
// Recreate the DB again
|
||||||
|
this.target = getDB(this.target.name)
|
||||||
await this.replicate()
|
await this.replicate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,9 @@ exports.StaticDatabases = {
|
||||||
GLOBAL: {
|
GLOBAL: {
|
||||||
name: "global-db",
|
name: "global-db",
|
||||||
},
|
},
|
||||||
|
DEPLOYMENTS: {
|
||||||
|
name: "deployments",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
|
@ -22,6 +25,7 @@ const DocumentTypes = {
|
||||||
TEMPLATE: "template",
|
TEMPLATE: "template",
|
||||||
APP: "app",
|
APP: "app",
|
||||||
APP_DEV: "app_dev",
|
APP_DEV: "app_dev",
|
||||||
|
APP_METADATA: "app_metadata",
|
||||||
ROLE: "role",
|
ROLE: "role",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +166,7 @@ exports.getAllApps = async (devApps = false) => {
|
||||||
const appDbNames = allDbs.filter(dbName =>
|
const appDbNames = allDbs.filter(dbName =>
|
||||||
dbName.startsWith(exports.APP_PREFIX)
|
dbName.startsWith(exports.APP_PREFIX)
|
||||||
)
|
)
|
||||||
const appPromises = appDbNames.map(db => new CouchDB(db).get(db))
|
const appPromises = appDbNames.map(db => new CouchDB(db).get(DocumentTypes.APP_METADATA))
|
||||||
if (appPromises.length === 0) {
|
if (appPromises.length === 0) {
|
||||||
return []
|
return []
|
||||||
} else {
|
} else {
|
||||||
|
@ -172,9 +176,9 @@ exports.getAllApps = async (devApps = false) => {
|
||||||
.map(({ value }) => value)
|
.map(({ value }) => value)
|
||||||
return apps.filter(app => {
|
return apps.filter(app => {
|
||||||
if (devApps) {
|
if (devApps) {
|
||||||
return app._id.startsWith(exports.APP_DEV_PREFIX)
|
return app.appId.startsWith(exports.APP_DEV_PREFIX)
|
||||||
}
|
}
|
||||||
return !app._id.startsWith(exports.APP_DEV_PREFIX)
|
return !app.appId.startsWith(exports.APP_DEV_PREFIX)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,14 +51,14 @@ export const getFrontendStore = () => {
|
||||||
store.actions = {
|
store.actions = {
|
||||||
initialise: async pkg => {
|
initialise: async pkg => {
|
||||||
const { layouts, screens, application, clientLibPath } = pkg
|
const { layouts, screens, application, clientLibPath } = pkg
|
||||||
const components = await fetchComponentLibDefinitions(application._id)
|
const components = await fetchComponentLibDefinitions(application.appId)
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
libraries: application.componentLibraries,
|
libraries: application.componentLibraries,
|
||||||
components,
|
components,
|
||||||
name: application.name,
|
name: application.name,
|
||||||
description: application.description,
|
description: application.description,
|
||||||
appId: application._id,
|
appId: application.appId,
|
||||||
url: application.url,
|
url: application.url,
|
||||||
layouts,
|
layouts,
|
||||||
screens,
|
screens,
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
<script>
|
||||||
|
import { onMount, onDestroy } from "svelte"
|
||||||
|
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import analytics from "analytics"
|
||||||
|
import FeedbackIframe from "components/feedback/FeedbackIframe.svelte"
|
||||||
|
|
||||||
|
const DeploymentStatus = {
|
||||||
|
SUCCESS: "SUCCESS",
|
||||||
|
PENDING: "PENDING",
|
||||||
|
FAILURE: "FAILURE",
|
||||||
|
}
|
||||||
|
|
||||||
|
const POLL_INTERVAL = 1000
|
||||||
|
|
||||||
|
|
||||||
|
let loading = false
|
||||||
|
let feedbackModal
|
||||||
|
let deployments = []
|
||||||
|
let poll
|
||||||
|
let publishModal
|
||||||
|
|
||||||
|
$: appId = $store.appId
|
||||||
|
|
||||||
|
async function deployApp() {
|
||||||
|
try {
|
||||||
|
notifications.info(`Deployment started. Please wait.`)
|
||||||
|
const response = await api.post("/api/deploy")
|
||||||
|
const json = await response.json()
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analytics.requestFeedbackOnDeploy()) {
|
||||||
|
feedbackModal.show()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
analytics.captureException(err)
|
||||||
|
notifications.error("Deployment unsuccessful. Please try again later.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDeployments() {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/api/deployments`)
|
||||||
|
const json = await response.json()
|
||||||
|
|
||||||
|
if (deployments.length > 0) {
|
||||||
|
checkIncomingDeploymentStatus(deployments, json)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployments = json
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
clearInterval(poll)
|
||||||
|
notifications.error(
|
||||||
|
"Error fetching deployment history. Please try again."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required to check any updated deployment statuses between polls
|
||||||
|
function checkIncomingDeploymentStatus(current, incoming) {
|
||||||
|
for (let incomingDeployment of incoming) {
|
||||||
|
if (incomingDeployment.status === DeploymentStatus.FAILURE || incomingDeployment.status === DeploymentStatus.SUCCESS) {
|
||||||
|
const currentDeployment = current.find(
|
||||||
|
deployment => deployment._id === incomingDeployment._id
|
||||||
|
)
|
||||||
|
|
||||||
|
// We have just been notified of an ongoing deployments status change
|
||||||
|
if (
|
||||||
|
!currentDeployment ||
|
||||||
|
currentDeployment.status === DeploymentStatus.PENDING
|
||||||
|
) {
|
||||||
|
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
|
||||||
|
notifications.error(incomingDeployment.err)
|
||||||
|
} else {
|
||||||
|
notifications.send("Published to Production.", "success", "CheckmarkCircle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
fetchDeployments()
|
||||||
|
poll = setInterval(fetchDeployments, POLL_INTERVAL)
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => clearInterval(poll))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
secondary
|
||||||
|
on:click={publishModal.show}
|
||||||
|
>
|
||||||
|
Publish
|
||||||
|
</Button>
|
||||||
|
<Modal bind:this={publishModal}>
|
||||||
|
<ModalContent
|
||||||
|
title="Publish to Production"
|
||||||
|
confirmText="Publish"
|
||||||
|
onConfirm={deployApp}
|
||||||
|
>
|
||||||
|
<span>The changes you have made will be published to the production version of the application.</span>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script>
|
||||||
|
import { onMount, onDestroy } from "svelte"
|
||||||
|
import { Button, Icon, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import { apps } from "stores/portal"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
let revertModal
|
||||||
|
|
||||||
|
$: appId = $store.appId
|
||||||
|
|
||||||
|
const revert = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.post(`/api/dev/${appId}/revert`)
|
||||||
|
const json = await response.json()
|
||||||
|
if (response.status !== 200) throw json.message
|
||||||
|
|
||||||
|
notifications.info("Changes reverted.")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Error reverting changes: ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<Icon name="Revert" hoverable on:click={revertModal.show} />
|
||||||
|
<Modal bind:this={revertModal}>
|
||||||
|
<ModalContent
|
||||||
|
title="Revert Changes"
|
||||||
|
confirmText="Revert"
|
||||||
|
onConfirm={revert}
|
||||||
|
>
|
||||||
|
<span>The changes you have made will be deleted and the application reverted back to its production state.</span>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
|
@ -41,7 +41,7 @@
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/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.appId)} icon="LockOpen">
|
||||||
Release Lock
|
Release Lock
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
||||||
{/if}
|
{/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.appId)} icon="LockOpen">
|
||||||
Release Lock
|
Release Lock
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -96,7 +96,7 @@
|
||||||
|
|
||||||
// Select Correct Application/DB in prep for creating user
|
// Select Correct Application/DB in prep for creating user
|
||||||
const applicationPkg = await get(
|
const applicationPkg = await get(
|
||||||
`/api/applications/${appJson._id}/appPackage`
|
`/api/applications/${appJson.instance._id}/appPackage`
|
||||||
)
|
)
|
||||||
const pkg = await applicationPkg.json()
|
const pkg = await applicationPkg.json()
|
||||||
if (applicationPkg.ok) {
|
if (applicationPkg.ok) {
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
}
|
}
|
||||||
const userResp = await api.post(`/api/users/metadata/self`, user)
|
const userResp = await api.post(`/api/users/metadata/self`, user)
|
||||||
await userResp.json()
|
await userResp.json()
|
||||||
$goto(`/builder/app/${appJson._id}`)
|
$goto(`/builder/app/${appJson.instance._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
notifications.error(error)
|
notifications.error(error)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, automationStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { Button, ActionGroup, ActionButton, Tabs, Tab } from "@budibase/bbui"
|
import { Button, Icon, Modal, ModalContent, ActionGroup, ActionButton, Tabs, Tab } from "@budibase/bbui"
|
||||||
import SettingsLink from "components/settings/Link.svelte"
|
import SettingsLink from "components/settings/Link.svelte"
|
||||||
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
|
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
|
||||||
import FeedbackNavLink from "components/feedback/FeedbackNavLink.svelte"
|
import FeedbackNavLink from "components/feedback/FeedbackNavLink.svelte"
|
||||||
|
import DeployModal from "components/deploy/DeployModal.svelte"
|
||||||
|
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||||
import { get } from "builderStore/api"
|
import { get } from "builderStore/api"
|
||||||
import { isActive, goto, layout } from "@roxi/routify"
|
import { isActive, goto, layout } from "@roxi/routify"
|
||||||
import Logo from "/assets/bb-logo.svg"
|
import Logo from "/assets/bb-logo.svg"
|
||||||
|
@ -81,25 +83,13 @@
|
||||||
<ActionGroup />
|
<ActionGroup />
|
||||||
</div>
|
</div>
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<ThemeEditorDropdown />
|
<RevertModal />
|
||||||
<FeedbackNavLink />
|
<Icon name="Play" hoverable
|
||||||
<div class="topnavitemright">
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/Budibase/budibase/discussions"
|
|
||||||
>
|
|
||||||
<i class="ri-github-fill" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<SettingsLink />
|
|
||||||
<Button
|
|
||||||
secondary
|
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
window.open(`/${application}`)
|
window.open(`/${application}`)
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
Preview
|
<DeployModal />
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="beta">
|
<div class="beta">
|
||||||
|
@ -153,6 +143,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topleftnav {
|
.topleftnav {
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<!-- routify:options index=4 -->
|
<!-- routify:options index=4 -->
|
||||||
<slot />
|
<!-- <slot /> -->
|
|
@ -62,16 +62,16 @@
|
||||||
|
|
||||||
const openApp = app => {
|
const openApp = app => {
|
||||||
if (appStatus === AppStatus.DEV) {
|
if (appStatus === AppStatus.DEV) {
|
||||||
$goto(`../../app/${app._id}`)
|
$goto(`../../app/${app.appId}`)
|
||||||
} else {
|
} else {
|
||||||
window.open(`/${app._id}`, "_blank")
|
window.open(`/${app.appId}`, '_blank');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportApp = app => {
|
const exportApp = app => {
|
||||||
try {
|
try {
|
||||||
download(
|
download(
|
||||||
`/api/backups/export?appId=${app._id}&appname=${encodeURIComponent(
|
`/api/backups/export?appId=${app.appId}&appname=${encodeURIComponent(
|
||||||
app.name
|
app.name
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
class:appGrid={layout === "grid"}
|
class:appGrid={layout === "grid"}
|
||||||
class:appTable={layout === "table"}
|
class:appTable={layout === "table"}
|
||||||
>
|
>
|
||||||
{#each $apps as app, idx (app._id)}
|
{#each $apps as app, idx (app.appId)}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={layout === "grid" ? AppCard : AppRow}
|
this={layout === "grid" ? AppCard : AppRow}
|
||||||
deletable={appStatus === AppStatus.PUBLISHED}
|
deletable={appStatus === AppStatus.PUBLISHED}
|
||||||
|
|
|
@ -13,6 +13,7 @@ export function createAppStore() {
|
||||||
} else {
|
} else {
|
||||||
store.set([])
|
store.set([])
|
||||||
}
|
}
|
||||||
|
return json
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
store.set([])
|
store.set([])
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CouchDB = require("../src/db")
|
const CouchDB = require("../src/db")
|
||||||
|
const { DocumentTypes } = require("../src/db/utils")
|
||||||
|
|
||||||
const appName = process.argv[2].toLowerCase()
|
const appName = process.argv[2].toLowerCase()
|
||||||
const remoteUrl = process.argv[3]
|
const remoteUrl = process.argv[3]
|
||||||
|
@ -18,7 +19,7 @@ const run = async () => {
|
||||||
let apps = []
|
let apps = []
|
||||||
for (let dbName of appDbNames) {
|
for (let dbName of appDbNames) {
|
||||||
const db = new CouchDB(dbName)
|
const db = new CouchDB(dbName)
|
||||||
apps.push(db.get(dbName))
|
apps.push(db.get(DocumentTypes.APP_METADATA))
|
||||||
}
|
}
|
||||||
apps = await Promise.all(apps)
|
apps = await Promise.all(apps)
|
||||||
const app = apps.find(
|
const app = apps.find(
|
||||||
|
@ -32,7 +33,7 @@ const run = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const instanceDb = new CouchDB(app._id)
|
const instanceDb = new CouchDB(app.appId)
|
||||||
const remoteDb = new CouchDB(`${remoteUrl}/${appName}`)
|
const remoteDb = new CouchDB(`${remoteUrl}/${appName}`)
|
||||||
|
|
||||||
instanceDb.replicate
|
instanceDb.replicate
|
||||||
|
|
|
@ -85,7 +85,6 @@ async function getAppUrlIfNotInUse(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createInstance(template) {
|
async function createInstance(template) {
|
||||||
// TODO: Do we need the normal app ID?
|
|
||||||
const baseAppId = generateAppID()
|
const baseAppId = generateAppID()
|
||||||
const appId = generateDevAppID(baseAppId)
|
const appId = generateDevAppID(baseAppId)
|
||||||
|
|
||||||
|
@ -125,7 +124,7 @@ exports.fetch = async function (ctx) {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const locks = await getAllLocks()
|
const locks = await getAllLocks()
|
||||||
for (let app of apps) {
|
for (let app of apps) {
|
||||||
const lock = locks.find(lock => lock.appId === app._id)
|
const lock = locks.find(lock => lock.appId === app.appId)
|
||||||
if (lock) {
|
if (lock) {
|
||||||
app.lockedBy = lock.user
|
app.lockedBy = lock.user
|
||||||
} else {
|
} else {
|
||||||
|
@ -156,7 +155,7 @@ exports.fetchAppDefinition = async function (ctx) {
|
||||||
|
|
||||||
exports.fetchAppPackage = async function (ctx) {
|
exports.fetchAppPackage = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.params.appId)
|
const db = new CouchDB(ctx.params.appId)
|
||||||
const application = await db.get(ctx.params.appId)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
const [layouts, screens] = await Promise.all([getLayouts(db), getScreens(db)])
|
const [layouts, screens] = await Promise.all([getLayouts(db), getScreens(db)])
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
@ -181,7 +180,8 @@ exports.create = async function (ctx) {
|
||||||
const url = await getAppUrlIfNotInUse(ctx)
|
const url = await getAppUrlIfNotInUse(ctx)
|
||||||
const appId = instance._id
|
const appId = instance._id
|
||||||
const newApplication = {
|
const newApplication = {
|
||||||
_id: appId,
|
_id: DocumentTypes.APP_METADATA,
|
||||||
|
appId: instance._id,
|
||||||
type: "app",
|
type: "app",
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
componentLibraries: ["@budibase/standard-components"],
|
componentLibraries: ["@budibase/standard-components"],
|
||||||
|
@ -244,7 +244,7 @@ exports.delete = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEmptyAppPackage = async (ctx, app) => {
|
const createEmptyAppPackage = async (ctx, app) => {
|
||||||
const db = new CouchDB(app._id)
|
const db = new CouchDB(app.instance._id)
|
||||||
|
|
||||||
let screensAndLayouts = []
|
let screensAndLayouts = []
|
||||||
for (let layout of BASE_LAYOUTS) {
|
for (let layout of BASE_LAYOUTS) {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
|
const { DocumentTypes } = require("../../db/utils")
|
||||||
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
|
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
|
||||||
|
|
||||||
exports.fetchAppComponentDefinitions = async function (ctx) {
|
exports.fetchAppComponentDefinitions = async function (ctx) {
|
||||||
const appId = ctx.params.appId || ctx.appId
|
const appId = ctx.params.appId || ctx.appId
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const app = await db.get(appId)
|
const app = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
let componentManifests = await Promise.all(
|
let componentManifests = await Promise.all(
|
||||||
app.componentLibraries.map(async library => {
|
app.componentLibraries.map(async library => {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
const PouchDB = require("../../../db")
|
const PouchDB = require("../../../db")
|
||||||
const Deployment = require("./Deployment")
|
const Deployment = require("./Deployment")
|
||||||
const { Replication } = require("@budibase/auth/db")
|
const { Replication, StaticDatabases } = require("@budibase/auth/db")
|
||||||
|
const { DocumentTypes } = require("../../../db/utils")
|
||||||
|
|
||||||
// the max time we can wait for an invalidation to complete before considering it failed
|
// the max time we can wait for an invalidation to complete before considering it failed
|
||||||
const MAX_PENDING_TIME_MS = 30 * 60000
|
const MAX_PENDING_TIME_MS = 30 * 60000
|
||||||
const DeploymentStatus = {
|
const DeploymentStatus = {
|
||||||
|
@ -26,16 +28,16 @@ async function checkAllDeployments(deployments) {
|
||||||
return { updated, deployments }
|
return { updated, deployments }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeLocalDeploymentHistory(deployment) {
|
async function storeDeploymentHistory(deployment) {
|
||||||
const appId = deployment.getAppId()
|
const appId = deployment.getAppId()
|
||||||
const deploymentJSON = deployment.getJSON()
|
const deploymentJSON = deployment.getJSON()
|
||||||
const db = new PouchDB(appId)
|
const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
|
||||||
|
|
||||||
let deploymentDoc
|
let deploymentDoc
|
||||||
try {
|
try {
|
||||||
deploymentDoc = await db.get("_local/deployments")
|
deploymentDoc = await db.get(appId)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
deploymentDoc = { _id: "_local/deployments", history: {} }
|
deploymentDoc = { _id: appId, history: {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
const deploymentId = deploymentJSON._id
|
const deploymentId = deploymentJSON._id
|
||||||
|
@ -65,12 +67,9 @@ async function deployApp(deployment) {
|
||||||
})
|
})
|
||||||
|
|
||||||
await replication.replicate()
|
await replication.replicate()
|
||||||
|
|
||||||
// Strip the _dev prefix and update the appID document in the new DB
|
|
||||||
const db = new PouchDB(productionAppId)
|
const db = new PouchDB(productionAppId)
|
||||||
const appDoc = await db.get(deployment.appId)
|
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||||
appDoc._id = productionAppId
|
appDoc.appId = productionAppId
|
||||||
delete appDoc._rev
|
|
||||||
appDoc.instance._id = productionAppId
|
appDoc.instance._id = productionAppId
|
||||||
await db.put(appDoc)
|
await db.put(appDoc)
|
||||||
|
|
||||||
|
@ -79,13 +78,17 @@ async function deployApp(deployment) {
|
||||||
source: productionAppId,
|
source: productionAppId,
|
||||||
target: deployment.appId,
|
target: deployment.appId,
|
||||||
})
|
})
|
||||||
liveReplication.subscribe()
|
liveReplication.subscribe({
|
||||||
|
filter: function (doc) {
|
||||||
|
return doc._id !== DocumentTypes.APP_METADATA
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
deployment.setStatus(DeploymentStatus.SUCCESS)
|
deployment.setStatus(DeploymentStatus.SUCCESS)
|
||||||
await storeLocalDeploymentHistory(deployment)
|
await storeDeploymentHistory(deployment)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
deployment.setStatus(DeploymentStatus.FAILURE, err.message)
|
deployment.setStatus(DeploymentStatus.FAILURE, err.message)
|
||||||
await storeLocalDeploymentHistory(deployment)
|
await storeDeploymentHistory(deployment)
|
||||||
throw {
|
throw {
|
||||||
...err,
|
...err,
|
||||||
message: `Deployment Failed: ${err.message}`,
|
message: `Deployment Failed: ${err.message}`,
|
||||||
|
@ -95,8 +98,8 @@ async function deployApp(deployment) {
|
||||||
|
|
||||||
exports.fetchDeployments = async function (ctx) {
|
exports.fetchDeployments = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
const db = new PouchDB(ctx.appId)
|
const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
|
||||||
const deploymentDoc = await db.get("_local/deployments")
|
const deploymentDoc = await db.get(ctx.appId)
|
||||||
const { updated, deployments } = await checkAllDeployments(
|
const { updated, deployments } = await checkAllDeployments(
|
||||||
deploymentDoc,
|
deploymentDoc,
|
||||||
ctx.user
|
ctx.user
|
||||||
|
@ -112,8 +115,8 @@ exports.fetchDeployments = async function (ctx) {
|
||||||
|
|
||||||
exports.deploymentProgress = async function (ctx) {
|
exports.deploymentProgress = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
const db = new PouchDB(ctx.appId)
|
const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
|
||||||
const deploymentDoc = await db.get("_local/deployments")
|
const deploymentDoc = await db.get(ctx.appId)
|
||||||
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(
|
ctx.throw(
|
||||||
|
@ -126,7 +129,7 @@ exports.deploymentProgress = async function (ctx) {
|
||||||
exports.deployApp = async function (ctx) {
|
exports.deployApp = async function (ctx) {
|
||||||
let deployment = new Deployment(ctx.appId)
|
let deployment = new Deployment(ctx.appId)
|
||||||
deployment.setStatus(DeploymentStatus.PENDING)
|
deployment.setStatus(DeploymentStatus.PENDING)
|
||||||
deployment = await storeLocalDeploymentHistory(deployment)
|
deployment = await storeDeploymentHistory(deployment)
|
||||||
|
|
||||||
await deployApp(deployment)
|
await deployApp(deployment)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
|
const CouchDB = require("../../db")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { checkSlashesInUrl } = require("../../utilities")
|
const { checkSlashesInUrl } = require("../../utilities")
|
||||||
const { request } = require("../../utilities/workerRequests")
|
const { request } = require("../../utilities/workerRequests")
|
||||||
const { clearLock } = require("../../utilities/redis")
|
const { clearLock } = require("../../utilities/redis")
|
||||||
|
const { Replication } = require("@budibase/auth").db
|
||||||
|
const { DocumentTypes } = require("../../db/utils")
|
||||||
|
|
||||||
async function redirect(ctx, method) {
|
async function redirect(ctx, method) {
|
||||||
const { devPath } = ctx.params
|
const { devPath } = ctx.params
|
||||||
|
@ -45,3 +48,37 @@ exports.clearLock = async ctx => {
|
||||||
message: "Lock released successfully.",
|
message: "Lock released successfully.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.revert = async ctx => {
|
||||||
|
const { appId } = ctx.params
|
||||||
|
const productionAppId = appId.replace("_dev", "")
|
||||||
|
|
||||||
|
// App must have been deployed first
|
||||||
|
try {
|
||||||
|
const db = new CouchDB(productionAppId, { skip_setup: true })
|
||||||
|
const info = await db.info()
|
||||||
|
if (info.error) throw info.error
|
||||||
|
} catch (err) {
|
||||||
|
return ctx.throw(400, "App has not yet been deployed")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const replication = new Replication({
|
||||||
|
source: productionAppId,
|
||||||
|
target: appId,
|
||||||
|
})
|
||||||
|
|
||||||
|
await replication.rollback()
|
||||||
|
// update appID in reverted app to be dev version again
|
||||||
|
const db = new CouchDB(appId)
|
||||||
|
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
appDoc.appId = appId
|
||||||
|
appDoc.instance._id = appId
|
||||||
|
await db.put(appDoc)
|
||||||
|
ctx.body = {
|
||||||
|
message: "Reverted changes successfully.",
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.throw(400, `Unable to revert. ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ const env = require("../../../environment")
|
||||||
const { objectStoreUrl, clientLibraryPath } = require("../../../utilities")
|
const { objectStoreUrl, clientLibraryPath } = require("../../../utilities")
|
||||||
const { upload } = require("../../../utilities/fileSystem")
|
const { upload } = require("../../../utilities/fileSystem")
|
||||||
const { attachmentsRelativeURL } = require("../../../utilities")
|
const { attachmentsRelativeURL } = require("../../../utilities")
|
||||||
|
const { DocumentTypes } = require("../../../db/utils")
|
||||||
|
|
||||||
async function prepareUpload({ s3Key, bucket, metadata, file }) {
|
async function prepareUpload({ s3Key, bucket, metadata, file }) {
|
||||||
const response = await upload({
|
const response = await upload({
|
||||||
|
@ -85,7 +86,7 @@ exports.serveApp = async function (ctx) {
|
||||||
}
|
}
|
||||||
const App = require("./templates/BudibaseApp.svelte").default
|
const App = require("./templates/BudibaseApp.svelte").default
|
||||||
const db = new CouchDB(appId, { skip_setup: true })
|
const db = new CouchDB(appId, { skip_setup: true })
|
||||||
const appInfo = await db.get(appId)
|
const appInfo = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
const { head, html, css } = App.render({
|
const { head, html, css } = App.render({
|
||||||
title: appInfo.name,
|
title: appInfo.name,
|
||||||
|
@ -125,7 +126,7 @@ exports.serveComponentLibrary = async function (ctx) {
|
||||||
return send(ctx, "/index.js", { root: componentLibraryPath })
|
return send(ctx, "/index.js", { root: componentLibraryPath })
|
||||||
}
|
}
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const appInfo = await db.get(appId)
|
const appInfo = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
let componentLib = "componentlibrary"
|
let componentLib = "componentlibrary"
|
||||||
if (appInfo && appInfo.version) {
|
if (appInfo && appInfo.version) {
|
||||||
|
|
|
@ -13,6 +13,8 @@ if (env.isDev() || env.isTest()) {
|
||||||
.delete("/api/admin/:devPath(.*)", controller.redirectDelete)
|
.delete("/api/admin/:devPath(.*)", controller.redirectDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
router.delete("/api/dev/:appId/lock", authorized(BUILDER), controller.clearLock)
|
router
|
||||||
|
.delete("/api/dev/:appId/lock", authorized(BUILDER), controller.clearLock)
|
||||||
|
.post("/api/dev/:appId/revert", authorized(BUILDER), controller.revert)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -21,7 +21,7 @@ exports.clearAllApps = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (let app of apps) {
|
for (let app of apps) {
|
||||||
const appId = app._id
|
const { appId } = app
|
||||||
await appController.delete(new Request(null, { appId }))
|
await appController.delete(new Request(null, { appId }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ const AppStatus = {
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
APP: CoreDocTypes.APP,
|
APP: CoreDocTypes.APP,
|
||||||
APP_DEV: CoreDocTypes.APP_DEV,
|
APP_DEV: CoreDocTypes.APP_DEV,
|
||||||
|
APP_METADATA: CoreDocTypes.APP_METADATA,
|
||||||
ROLE: CoreDocTypes.ROLE,
|
ROLE: CoreDocTypes.ROLE,
|
||||||
TABLE: "ta",
|
TABLE: "ta",
|
||||||
ROW: "ro",
|
ROW: "ro",
|
||||||
|
|
|
@ -89,7 +89,7 @@ class TestConfiguration {
|
||||||
if (this.server) {
|
if (this.server) {
|
||||||
this.server.close()
|
this.server.close()
|
||||||
}
|
}
|
||||||
cleanup(this.allApps.map(app => app._id))
|
cleanup(this.allApps.map(app => app.appId))
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultHeaders() {
|
defaultHeaders() {
|
||||||
|
@ -141,7 +141,7 @@ class TestConfiguration {
|
||||||
|
|
||||||
async createApp(appName) {
|
async createApp(appName) {
|
||||||
this.app = await this._req({ name: appName }, null, controllers.app.create)
|
this.app = await this._req({ name: appName }, null, controllers.app.create)
|
||||||
this.appId = this.app._id
|
this.appId = this.app.appId
|
||||||
this.allApps.push(this.app)
|
this.allApps.push(this.app)
|
||||||
return this.app
|
return this.app
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { APP_PREFIX } = require("../db/utils")
|
|
||||||
const CouchDB = require("../db")
|
|
||||||
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
|
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
|
||||||
const { getAllApps } = require("@budibase/auth/db")
|
const { getAllApps } = require("@budibase/auth/db")
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
|
const { DocumentTypes } = require("@budibase/auth").db
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
|
|
||||||
|
@ -14,7 +15,9 @@ exports.getApps = async ctx => {
|
||||||
allDbs = await CouchDB.allDbs()
|
allDbs = await CouchDB.allDbs()
|
||||||
}
|
}
|
||||||
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
|
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
|
||||||
const appPromises = appDbNames.map(db => new CouchDB(db).get(db))
|
const appPromises = appDbNames.map(db =>
|
||||||
|
new CouchDB(db).get(DocumentTypes.APP_METADATA)
|
||||||
|
)
|
||||||
|
|
||||||
const apps = await Promise.allSettled(appPromises)
|
const apps = await Promise.allSettled(appPromises)
|
||||||
const body = {}
|
const body = {}
|
||||||
|
@ -26,7 +29,7 @@ exports.getApps = async ctx => {
|
||||||
let url = app.url || encodeURI(`${app.name}`)
|
let url = app.url || encodeURI(`${app.name}`)
|
||||||
url = `/${url.replace(URL_REGEX_SLASH, "")}`
|
url = `/${url.replace(URL_REGEX_SLASH, "")}`
|
||||||
body[url] = {
|
body[url] = {
|
||||||
appId: app._id,
|
appId: app.instance._id,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue