Merge pull request #12914 from Budibase/budi-7664-sqs-self-host-ui-for-detecting-lack-of-sqs-support-2
Plumbing for showing a maintenance page when SQS is required but missing.
This commit is contained in:
commit
7a7a30b8f5
|
@ -71,6 +71,10 @@
|
||||||
await auth.getSelf()
|
await auth.getSelf()
|
||||||
await admin.init()
|
await admin.init()
|
||||||
|
|
||||||
|
if ($admin.maintenance.length > 0) {
|
||||||
|
$redirect("./maintenance")
|
||||||
|
}
|
||||||
|
|
||||||
if ($auth.user) {
|
if ($auth.user) {
|
||||||
await licensing.init()
|
await licensing.init()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
<script>
|
||||||
|
import { MaintenanceType } from "@budibase/types"
|
||||||
|
import { Heading, Body, Button, Layout } from "@budibase/bbui"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
|
import BudibaseLogo from "../portal/_components/BudibaseLogo.svelte"
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if ($admin.maintenance.length === 0) {
|
||||||
|
window.location = "/builder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<div class="content">
|
||||||
|
<div class="hero">
|
||||||
|
<BudibaseLogo />
|
||||||
|
</div>
|
||||||
|
<div class="inner-content">
|
||||||
|
{#each $admin.maintenance as maintenance}
|
||||||
|
{#if maintenance.type === MaintenanceType.SQS_MISSING}
|
||||||
|
<Layout>
|
||||||
|
<Heading>Please upgrade your Budibase installation</Heading>
|
||||||
|
<Body>
|
||||||
|
We've detected that the version of Budibase you're using depends
|
||||||
|
on a more recent version of the CouchDB database than what you
|
||||||
|
have installed.
|
||||||
|
</Body>
|
||||||
|
<Body>
|
||||||
|
To resolve this, you can either rollback to a previous version of
|
||||||
|
Budibase, or follow the migration guide to update to a later
|
||||||
|
version of CouchDB.
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
<Button
|
||||||
|
on:click={() => (window.location = "https://docs.budibase.com")}
|
||||||
|
>Migration guide</Button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.main {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: auto;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.hero {
|
||||||
|
margin: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.content {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.main {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script>
|
||||||
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions-->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<img src={Logo} alt="Budibase Logo" on:click={() => $goto("./apps")} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -17,6 +17,7 @@ export const DEFAULT_CONFIG = {
|
||||||
adminUser: { checked: false },
|
adminUser: { checked: false },
|
||||||
sso: { checked: false },
|
sso: { checked: false },
|
||||||
},
|
},
|
||||||
|
maintenance: [],
|
||||||
offlineMode: false,
|
offlineMode: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@ export function createAdminStore() {
|
||||||
store.isDev = environment.isDev
|
store.isDev = environment.isDev
|
||||||
store.baseUrl = environment.baseUrl
|
store.baseUrl = environment.baseUrl
|
||||||
store.offlineMode = environment.offlineMode
|
store.offlineMode = environment.offlineMode
|
||||||
|
store.maintenance = environment.maintenance
|
||||||
return store
|
return store
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
appStore,
|
appStore,
|
||||||
devToolsStore,
|
devToolsStore,
|
||||||
devToolsEnabled,
|
devToolsEnabled,
|
||||||
|
environmentStore,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
|
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
|
||||||
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
|
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
import DevToolsHeader from "components/devtools/DevToolsHeader.svelte"
|
import DevToolsHeader from "components/devtools/DevToolsHeader.svelte"
|
||||||
import DevTools from "components/devtools/DevTools.svelte"
|
import DevTools from "components/devtools/DevTools.svelte"
|
||||||
import FreeFooter from "components/FreeFooter.svelte"
|
import FreeFooter from "components/FreeFooter.svelte"
|
||||||
|
import MaintenanceScreen from "components/MaintenanceScreen.svelte"
|
||||||
import licensing from "../licensing"
|
import licensing from "../licensing"
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
|
@ -111,122 +113,128 @@
|
||||||
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
||||||
class:builder={$builderStore.inBuilder}
|
class:builder={$builderStore.inBuilder}
|
||||||
>
|
>
|
||||||
<DeviceBindingsProvider>
|
{#if $environmentStore.maintenance.length > 0}
|
||||||
<UserBindingsProvider>
|
<MaintenanceScreen maintenanceList={$environmentStore.maintenance} />
|
||||||
<StateBindingsProvider>
|
{:else}
|
||||||
<RowSelectionProvider>
|
<DeviceBindingsProvider>
|
||||||
<QueryParamsProvider>
|
<UserBindingsProvider>
|
||||||
<!-- Settings bar can be rendered outside of device preview -->
|
<StateBindingsProvider>
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
<RowSelectionProvider>
|
||||||
{#key $builderStore.selectedComponentId}
|
<QueryParamsProvider>
|
||||||
{#if $builderStore.inBuilder}
|
<!-- Settings bar can be rendered outside of device preview -->
|
||||||
<SettingsBar />
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
{/if}
|
{#key $builderStore.selectedComponentId}
|
||||||
{/key}
|
{#if $builderStore.inBuilder}
|
||||||
|
<SettingsBar />
|
||||||
<!-- Clip boundary for selection indicators -->
|
|
||||||
<div
|
|
||||||
id="clip-root"
|
|
||||||
class:preview={$builderStore.inBuilder}
|
|
||||||
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
|
||||||
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
|
||||||
>
|
|
||||||
<!-- Actual app -->
|
|
||||||
<div id="app-root">
|
|
||||||
{#if showDevTools}
|
|
||||||
<DevToolsHeader />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
{/key}
|
||||||
|
|
||||||
<div id="app-body">
|
<!-- Clip boundary for selection indicators -->
|
||||||
{#if permissionError}
|
<div
|
||||||
<div class="error">
|
id="clip-root"
|
||||||
<Layout justifyItems="center" gap="S">
|
class:preview={$builderStore.inBuilder}
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
class:tablet-preview={$builderStore.previewDevice ===
|
||||||
{@html ErrorSVG}
|
"tablet"}
|
||||||
<Heading size="L">
|
class:mobile-preview={$builderStore.previewDevice ===
|
||||||
You don't have permission to use this app
|
"mobile"}
|
||||||
</Heading>
|
>
|
||||||
<Body size="S">
|
<!-- Actual app -->
|
||||||
Ask your administrator to grant you access
|
<div id="app-root">
|
||||||
</Body>
|
{#if showDevTools}
|
||||||
</Layout>
|
<DevToolsHeader />
|
||||||
</div>
|
{/if}
|
||||||
{:else if !$screenStore.activeLayout}
|
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
Something went wrong rendering your app
|
|
||||||
</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Get in touch with support if this issue persists
|
|
||||||
</Body>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else if embedNoScreens}
|
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
This Budibase app is not publicly accessible
|
|
||||||
</Heading>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<CustomThemeWrapper>
|
|
||||||
{#key $screenStore.activeLayout._id}
|
|
||||||
<Component
|
|
||||||
isLayout
|
|
||||||
instance={$screenStore.activeLayout.props}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
|
|
||||||
<!--
|
<div id="app-body">
|
||||||
|
{#if permissionError}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
You don't have permission to use this app
|
||||||
|
</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Ask your administrator to grant you access
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else if !$screenStore.activeLayout}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
Something went wrong rendering your app
|
||||||
|
</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Get in touch with support if this issue persists
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else if embedNoScreens}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
This Budibase app is not publicly accessible
|
||||||
|
</Heading>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<CustomThemeWrapper>
|
||||||
|
{#key $screenStore.activeLayout._id}
|
||||||
|
<Component
|
||||||
|
isLayout
|
||||||
|
instance={$screenStore.activeLayout.props}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<!--
|
||||||
Flatpickr needs to be inside the theme wrapper.
|
Flatpickr needs to be inside the theme wrapper.
|
||||||
It also needs its own container because otherwise it hijacks
|
It also needs its own container because otherwise it hijacks
|
||||||
key events on the whole page. It is painful to work with.
|
key events on the whole page. It is painful to work with.
|
||||||
-->
|
-->
|
||||||
<div id="flatpickr-root" />
|
<div id="flatpickr-root" />
|
||||||
|
|
||||||
<!-- Modal container to ensure they sit on top -->
|
<!-- Modal container to ensure they sit on top -->
|
||||||
<div class="modal-container" />
|
<div class="modal-container" />
|
||||||
|
|
||||||
<!-- Layers on top of app -->
|
<!-- Layers on top of app -->
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
<ConfirmationDisplay />
|
<ConfirmationDisplay />
|
||||||
<PeekScreenDisplay />
|
<PeekScreenDisplay />
|
||||||
</CustomThemeWrapper>
|
</CustomThemeWrapper>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showDevTools}
|
{#if showDevTools}
|
||||||
<DevTools />
|
<DevTools />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if !$builderStore.inBuilder && licensing.logoEnabled()}
|
||||||
|
<FreeFooter />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if !$builderStore.inBuilder && licensing.logoEnabled()}
|
<!-- Preview and dev tools utilities -->
|
||||||
<FreeFooter />
|
{#if $appStore.isDevApp}
|
||||||
|
<SelectionIndicator />
|
||||||
|
{/if}
|
||||||
|
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||||
|
<HoverIndicator />
|
||||||
|
{/if}
|
||||||
|
{#if $builderStore.inBuilder}
|
||||||
|
<DNDHandler />
|
||||||
|
<GridDNDHandler />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</QueryParamsProvider>
|
||||||
<!-- Preview and dev tools utilities -->
|
</RowSelectionProvider>
|
||||||
{#if $appStore.isDevApp}
|
</StateBindingsProvider>
|
||||||
<SelectionIndicator />
|
</UserBindingsProvider>
|
||||||
{/if}
|
</DeviceBindingsProvider>
|
||||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
{/if}
|
||||||
<HoverIndicator />
|
|
||||||
{/if}
|
|
||||||
{#if $builderStore.inBuilder}
|
|
||||||
<DNDHandler />
|
|
||||||
<GridDNDHandler />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</QueryParamsProvider>
|
|
||||||
</RowSelectionProvider>
|
|
||||||
</StateBindingsProvider>
|
|
||||||
</UserBindingsProvider>
|
|
||||||
</DeviceBindingsProvider>
|
|
||||||
</div>
|
</div>
|
||||||
<KeyboardManager />
|
<KeyboardManager />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<!--
|
||||||
|
This is the public facing maintenance screen. It is displayed when there is
|
||||||
|
required maintenance to be done on the Budibase installation. We only use this
|
||||||
|
if we detect that the Budibase installation is in a state where the vast
|
||||||
|
majority of apps would not function correctly.
|
||||||
|
|
||||||
|
The builder-facing maintenance screen is in
|
||||||
|
packages/builder/src/pages/builder/maintenance/index.svelte, and tends to
|
||||||
|
contain more detailed information and actions for the installation owner to
|
||||||
|
take.
|
||||||
|
-->
|
||||||
|
<script>
|
||||||
|
import { MaintenanceType } from "@budibase/types"
|
||||||
|
import { Heading, Body, Layout } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let maintenanceList
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{#each maintenanceList as maintenance}
|
||||||
|
{#if maintenance.type === MaintenanceType.SQS_MISSING}
|
||||||
|
<Layout>
|
||||||
|
<Heading>Budibase installation requires maintenance</Heading>
|
||||||
|
<Body>
|
||||||
|
The administrator of this Budibase installation needs to take actions
|
||||||
|
to update components that are out of date. Please contact them and
|
||||||
|
show them this warning. More information will be available when they
|
||||||
|
log into their account.
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: auto;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.content {
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,3 +2,7 @@ export enum ServiceType {
|
||||||
WORKER = "worker",
|
WORKER = "worker",
|
||||||
APPS = "apps",
|
APPS = "apps",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MaintenanceType {
|
||||||
|
SQS_MISSING = "sqs_missing",
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
import { Ctx } from "@budibase/types"
|
import { Ctx, MaintenanceType } from "@budibase/types"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import { env as coreEnv } from "@budibase/backend-core"
|
import { env as coreEnv } from "@budibase/backend-core"
|
||||||
import nodeFetch from "node-fetch"
|
import nodeFetch from "node-fetch"
|
||||||
|
|
||||||
|
// When we come to move to SQS fully and move away from Clouseau, we will need
|
||||||
|
// to flip this to true (or remove it entirely). This will then be used to
|
||||||
|
// determine if we should show the maintenance page that links to the SQS
|
||||||
|
// migration docs.
|
||||||
|
const sqsRequired = false
|
||||||
|
|
||||||
let sqsAvailable: boolean
|
let sqsAvailable: boolean
|
||||||
async function isSqsAvailable() {
|
async function isSqsAvailable() {
|
||||||
|
// We cache this value for the duration of the Node process because we don't
|
||||||
|
// want every page load to be making this relatively expensive check.
|
||||||
if (sqsAvailable !== undefined) {
|
if (sqsAvailable !== undefined) {
|
||||||
return sqsAvailable
|
return sqsAvailable
|
||||||
}
|
}
|
||||||
|
@ -21,6 +29,10 @@ async function isSqsAvailable() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function isSqsMissing() {
|
||||||
|
return sqsRequired && !(await isSqsAvailable())
|
||||||
|
}
|
||||||
|
|
||||||
export const fetch = async (ctx: Ctx) => {
|
export const fetch = async (ctx: Ctx) => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
multiTenancy: !!env.MULTI_TENANCY,
|
multiTenancy: !!env.MULTI_TENANCY,
|
||||||
|
@ -30,11 +42,12 @@ export const fetch = async (ctx: Ctx) => {
|
||||||
disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL,
|
disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL,
|
||||||
baseUrl: env.PLATFORM_URL,
|
baseUrl: env.PLATFORM_URL,
|
||||||
isDev: env.isDev() && !env.isTest(),
|
isDev: env.isDev() && !env.isTest(),
|
||||||
|
maintenance: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
ctx.body.infrastructure = {
|
if (await isSqsMissing()) {
|
||||||
sqs: await isSqsAvailable(),
|
ctx.body.maintenance.push({ type: MaintenanceType.SQS_MISSING })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ describe("/api/system/environment", () => {
|
||||||
multiTenancy: true,
|
multiTenancy: true,
|
||||||
baseUrl: "http://localhost:10000",
|
baseUrl: "http://localhost:10000",
|
||||||
offlineMode: false,
|
offlineMode: false,
|
||||||
|
maintenance: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -40,9 +41,7 @@ describe("/api/system/environment", () => {
|
||||||
multiTenancy: true,
|
multiTenancy: true,
|
||||||
baseUrl: "http://localhost:10000",
|
baseUrl: "http://localhost:10000",
|
||||||
offlineMode: false,
|
offlineMode: false,
|
||||||
infrastructure: {
|
maintenance: [],
|
||||||
sqs: false,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue