diff --git a/packages/account-portal b/packages/account-portal index 8ee2734e77..bcd86d9034 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit 8ee2734e77709438cbcaaabc024f677c7b24c883 +Subproject commit bcd86d9034ba954f013da4c10171bf495ab88189 diff --git a/packages/builder/src/api.js b/packages/builder/src/api.js index 37894d9bbc..ac878bf82f 100644 --- a/packages/builder/src/api.js +++ b/packages/builder/src/api.js @@ -5,7 +5,7 @@ import { } from "@budibase/frontend-core" import { store } from "./builderStore" import { get } from "svelte/store" -import { auth } from "./stores/portal" +import { auth, navigation } from "./stores/portal" export const API = createAPIClient({ attachHeaders: headers => { @@ -45,4 +45,15 @@ export const API = createAPIClient({ } } }, + onMigrationDetected: appId => { + const updatingUrl = `/builder/app/updating/${appId}` + + if (window.location.pathname === updatingUrl) { + return + } + + get(navigation).goto( + `${updatingUrl}?returnUrl=${encodeURIComponent(window.location.pathname)}` + ) + }, }) diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index b216958045..62d3951fb5 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -1,6 +1,6 @@ + + diff --git a/packages/builder/src/stores/portal/index.js b/packages/builder/src/stores/portal/index.js index e70df5c3ee..7f1b9e10f0 100644 --- a/packages/builder/src/stores/portal/index.js +++ b/packages/builder/src/stores/portal/index.js @@ -16,5 +16,6 @@ export { environment } from "./environment" export { menu } from "./menu" export { auditLogs } from "./auditLogs" export { features } from "./features" +export { navigation } from "./navigation" export const sideBarCollapsed = writable(false) diff --git a/packages/builder/src/stores/portal/navigation.js b/packages/builder/src/stores/portal/navigation.js new file mode 100644 index 0000000000..67a06eff53 --- /dev/null +++ b/packages/builder/src/stores/portal/navigation.js @@ -0,0 +1,31 @@ +import { writable } from "svelte/store" + +export function createNavigationStore() { + const store = writable({ + initialisated: false, + goto: undefined, + }) + const { set, subscribe } = store + + const init = gotoFunc => { + if (typeof gotoFunc !== "function") { + throw new Error( + `gotoFunc must be a function, found a "${typeof gotoFunc}" instead` + ) + } + + set({ + initialisated: true, + goto: gotoFunc, + }) + } + + return { + subscribe, + actions: { + init, + }, + } +} + +export const navigation = createNavigationStore() diff --git a/packages/client/package.json b/packages/client/package.json index 39ddb4bd49..227c7b25d4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -37,7 +37,6 @@ "downloadjs": "1.4.7", "html5-qrcode": "^2.2.1", "leaflet": "^1.7.1", - "regexparam": "^1.3.0", "sanitize-html": "^2.7.0", "screenfull": "^6.0.1", "shortid": "^2.2.15", diff --git a/packages/client/src/api/api.js b/packages/client/src/api/api.js index 8488b702b6..d4c8faa4d2 100644 --- a/packages/client/src/api/api.js +++ b/packages/client/src/api/api.js @@ -77,4 +77,10 @@ export const API = createAPIClient({ // Log all errors to console console.warn(`[Client] HTTP ${status} on ${method}:${url}\n\t${message}`) }, + onMigrationDetected: _appId => { + if (!window.MIGRATING_APP) { + // We will force a reload, that will display the updating screen until the migration is running + window.location.reload() + } + }, }) diff --git a/packages/client/src/components/UpdatingApp.svelte b/packages/client/src/components/UpdatingApp.svelte new file mode 100644 index 0000000000..74e5500715 --- /dev/null +++ b/packages/client/src/components/UpdatingApp.svelte @@ -0,0 +1,23 @@ + + +
+ +
+ + diff --git a/packages/client/src/index.js b/packages/client/src/index.js index a3cb4206c3..f6ed23b2a9 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,4 +1,5 @@ import ClientApp from "./components/ClientApp.svelte" +import UpdatingApp from "./components/UpdatingApp.svelte" import { builderStore, appStore, @@ -52,6 +53,13 @@ const loadBudibase = async () => { window["##BUDIBASE_APP_EMBEDDED##"] === "true" ) + if (window.MIGRATING_APP) { + new UpdatingApp({ + target: window.document.body, + }) + return + } + // Fetch environment info if (!get(environmentStore)?.loaded) { await environmentStore.actions.fetchEnvironment() diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js index d4b4f3636e..066ab16f6e 100644 --- a/packages/frontend-core/src/api/index.js +++ b/packages/frontend-core/src/api/index.js @@ -33,6 +33,7 @@ import { buildEnvironmentVariableEndpoints } from "./environmentVariables" import { buildEventEndpoints } from "./events" import { buildAuditLogsEndpoints } from "./auditLogs" import { buildLogsEndpoints } from "./logs" +import { buildMigrationEndpoints } from "./migrations" /** * Random identifier to uniquely identify a session in a tab. This is @@ -298,6 +299,7 @@ export const createAPIClient = config => { ...buildEventEndpoints(API), ...buildAuditLogsEndpoints(API), ...buildLogsEndpoints(API), + ...buildMigrationEndpoints(API), viewV2: buildViewV2Endpoints(API), } } diff --git a/packages/frontend-core/src/api/migrations.js b/packages/frontend-core/src/api/migrations.js new file mode 100644 index 0000000000..2da70d6fcb --- /dev/null +++ b/packages/frontend-core/src/api/migrations.js @@ -0,0 +1,10 @@ +export const buildMigrationEndpoints = API => ({ + /** + * Gets the info about the current app migration + */ + getMigrationStatus: async () => { + return await API.get({ + url: "/api/migrations/status", + }) + }, +}) diff --git a/packages/frontend-core/src/components/Updating.svelte b/packages/frontend-core/src/components/Updating.svelte new file mode 100644 index 0000000000..7d4a101fee --- /dev/null +++ b/packages/frontend-core/src/components/Updating.svelte @@ -0,0 +1,79 @@ + + +
+ + {#if !timedOut} + System update + {:else} + Something went wrong! + {/if} + + + {#if !timedOut} + Please wait and we will be back in a second! + {:else} + An error occurred, please try again later. +
+ Contact + support if the + issue persists. + {/if}
+
+ + diff --git a/packages/frontend-core/src/components/index.js b/packages/frontend-core/src/components/index.js index 01a7c78cb8..f724e1e4d9 100644 --- a/packages/frontend-core/src/components/index.js +++ b/packages/frontend-core/src/components/index.js @@ -3,4 +3,5 @@ export { default as TestimonialPage } from "./TestimonialPage.svelte" export { default as Testimonial } from "./Testimonial.svelte" export { default as UserAvatar } from "./UserAvatar.svelte" export { default as UserAvatars } from "./UserAvatars.svelte" +export { default as Updating } from "./Updating.svelte" export { Grid } from "./grid" diff --git a/packages/server/nodemon.json b/packages/server/nodemon.json index 33d277dd64..5535e0772e 100644 --- a/packages/server/nodemon.json +++ b/packages/server/nodemon.json @@ -7,7 +7,7 @@ "../shared-core", "../string-templates" ], - "ext": "js,ts,json", + "ext": "js,ts,json,svelte", "ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"], "exec": "yarn build && node ./dist/index.js" } diff --git a/packages/server/src/api/controllers/static/index.ts b/packages/server/src/api/controllers/static/index.ts index 2963546e7f..5f383e837d 100644 --- a/packages/server/src/api/controllers/static/index.ts +++ b/packages/server/src/api/controllers/static/index.ts @@ -25,8 +25,12 @@ import fs from "fs" import sdk from "../../../sdk" import * as pro from "@budibase/pro" import { App, Ctx, ProcessAttachmentResponse } from "@budibase/types" +import { + getAppMigrationVersion, + getLatestMigrationId, +} from "../../../appMigrations" -const send = require("koa-send") +import send from "koa-send" export const toggleBetaUiFeature = async function (ctx: Ctx) { const cookieName = `beta:${ctx.params.feature}` @@ -125,7 +129,26 @@ export const deleteObjects = async function (ctx: Ctx) { ) } +const requiresMigration = async (ctx: Ctx) => { + const appId = context.getAppId() + if (!appId) { + ctx.throw("AppId could not be found") + } + + const latestMigration = getLatestMigrationId() + if (!latestMigration) { + return false + } + + const latestMigrationApplied = await getAppMigrationVersion(appId) + + const requiresMigrations = latestMigrationApplied !== latestMigration + return requiresMigrations +} + export const serveApp = async function (ctx: Ctx) { + const needMigrations = await requiresMigration(ctx) + const bbHeaderEmbed = ctx.request.get("x-budibase-embed")?.toLowerCase() === "true" @@ -145,8 +168,8 @@ export const serveApp = async function (ctx: Ctx) { let appId = context.getAppId() if (!env.isJest()) { - const App = require("./templates/BudibaseApp.svelte").default const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins) + const App = require("./templates/BudibaseApp.svelte").default const { head, html, css } = App.render({ metaImage: branding?.metaImageUrl || @@ -167,6 +190,7 @@ export const serveApp = async function (ctx: Ctx) { config?.logoUrl !== "" ? objectStore.getGlobalFileUrl("settings", "logoUrl") : "", + appMigrating: needMigrations, }) const appHbs = loadHandlebarsFile(appHbsPath) ctx.body = await processString(appHbs, { @@ -273,7 +297,6 @@ export const getSignedUploadURL = async function (ctx: Ctx) { const { bucket, key } = ctx.request.body || {} if (!bucket || !key) { ctx.throw(400, "bucket and key values are required") - return } try { const s3 = new AWS.S3({ diff --git a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte index 32edb6dc7b..7819368fc0 100644 --- a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte +++ b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte @@ -8,6 +8,7 @@ export let clientLibPath export let usedPlugins + export let appMigrating @@ -110,6 +111,11 @@ + {#if appMigrating} + + {/if} diff --git a/packages/server/src/appMigrations/index.ts b/packages/server/src/appMigrations/index.ts index b382d8b533..0758b9f324 100644 --- a/packages/server/src/appMigrations/index.ts +++ b/packages/server/src/appMigrations/index.ts @@ -17,7 +17,7 @@ export const getLatestMigrationId = () => .sort() .reverse()[0] -const getTimestamp = (versionId: string) => versionId?.split("_")[0] +const getTimestamp = (versionId: string) => versionId?.split("_")[0] || "" export async function checkMissingMigrations( ctx: UserCtx, diff --git a/yarn.lock b/yarn.lock index 150585ce1a..85dd6ab251 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18312,11 +18312,6 @@ regexparam@2.0.1: resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-2.0.1.tgz#c912f5dae371e3798100b3c9ce22b7414d0889fa" integrity sha512-zRgSaYemnNYxUv+/5SeoHI0eJIgTL/A2pUtXUPLHQxUldagouJ9p+K6IbIZ/JiQuCEv2E2B1O11SjVQy3aMCkw== -regexparam@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" - integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== - regexpu-core@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.1.tgz#66900860f88def39a5cb79ebd9490e84f17bcdfb"