App can now go into maintenance mode locking out routing

This commit is contained in:
Stefan Zermatten
2021-12-29 14:25:01 +02:00
parent 2cf19d1ee5
commit ed1873babe
6 changed files with 109 additions and 30 deletions

View File

@@ -1,3 +1,19 @@
const MAINTENANCE_MODE = Meteor.settings?.public?.maintenanceMode || false;
import { Migrations } from 'meteor/percolate:migrations';
import SCHEMA_VERSION from '/imports/constants/SCHEMA_VERSION.js';
if (Meteor.isServer){
Meteor.startup(()=>{
const dbVersion = Migrations.getVersion();
if (
!Meteor.settings.public.maintenanceMode &&
SCHEMA_VERSION !== dbVersion
){
Meteor.settings.public.maintenanceMode = {
reason: 'App data needs to be migrated to the latest version'
};
}
});
}
const MAINTENANCE_MODE = Meteor.settings.public.maintenanceMode;
export default MAINTENANCE_MODE;

View File

@@ -4,7 +4,13 @@
<v-col cols="12">
<v-card>
<v-card-text>
<h4>Database version: {{ versions && versions.dbVersion }}</h4>
<h4>Current database version: {{ versions && versions.dbVersion }}</h4>
<h4 v-if="schemaVersion == versions.dbVersion ">
Database is up to date with latest version
</h4>
<h4 v-else>
Expected database version: {{ schemaVersion }}
</h4>
<h4>Git version: {{ versions && versions.gitVersion }}</h4>
<v-alert
v-if="versionError"
@@ -14,20 +20,18 @@
</v-alert>
<v-btn
icon
:loading="loadingVersion"
@click="refreshVersions"
>
<v-icon>mdi-refresh</v-icon>
</v-btn>
<v-text-field
v-model="migrationInput"
label="Database version to migrate to"
/>
<br>
<v-btn
:disabled="!migrationInput"
:disabled="!(schemaVersion > (versions && versions.dbVersion))"
:loading="loadingMigration"
@click="migrate"
>
Migrate
Migrate to database version {{ schemaVersion }}
</v-btn>
<v-alert
v-if="migrateError"
@@ -45,6 +49,7 @@
<script lang="js">
import getVersion from '/imports/migrations/methods/getVersion.js';
import migrateTo from '/imports/migrations/methods/migrateTo.js';
import SCHEMA_VERSION from '/imports/constants/SCHEMA_VERSION.js';
export default {
data(){return {
@@ -54,25 +59,24 @@ export default {
versionError: undefined,
migrateError: undefined,
loadingMigration: false,
schemaVersion: SCHEMA_VERSION,
}},
mounted(){
this.refreshVersions();
},
methods: {
refreshVersions(){
this.loadingVersion = true;
getVersion.call((error, result) => {
this.loadingVersion = false;
this.versionError = error;
this.versions = result;
});
},
migrate(){
let version = this.migrationInput;
if (Number.isFinite(+version)){
version = +version;
}
this.loadingMigration = true;
migrateTo.call({
version,
version: SCHEMA_VERSION,
}, error => {
this.loadingMigration = false;
this.migrateError = error;

View File

@@ -0,0 +1,43 @@
<template>
<v-layout
style="height: 100%;"
column
align-center
justify-center
>
<h1
v-if="maintenanceMode"
class="ma-4 text-h3"
>
DiceCloud is currently under maintenance
</h1>
<template v-else>
<h1
class="ma-4 text-h3"
>
DiceCloud is live.
</h1>
<v-btn
color="accent"
to="/"
>
Home
</v-btn>
</template>
<h1
v-if="maintenanceMode && maintenanceMode.reason"
class="ma-4 text-h4"
>
{{ maintenanceMode.reason }}
</h1>
</v-layout>
</template>
<script lang="js">
import MAINTENANCE_MODE from '/imports/constants/MAINTENANCE_MODE.js';
export default {
data(){return {
maintenanceMode: MAINTENANCE_MODE,
}},
}
</script>

View File

@@ -1,6 +1,6 @@
import { RouterFactory, nativeScrollBehavior } from 'meteor/akryum:vue-router2';
import { acceptInviteToken } from '/imports/api/users/Invites.js';
import MAINTENANCE_MODE from '/imports/constants/MAINTENANCE_MODE.js';
// Components
const Home = () => import('/imports/ui/pages/Home.vue');
const About = () => import('/imports/ui/pages/About.vue');
@@ -26,6 +26,8 @@ const Tabletop = () => import('/imports/ui/pages/Tabletop.vue');
const TabletopToolbar = () => import('/imports/ui/tabletop/TabletopToolbar.vue');
const TabletopRightDrawer = () => import('/imports/ui/tabletop/TabletopRightDrawer.vue');
const Admin = () => import('/imports/ui/pages/Admin.vue');
const Maintenance = () => import('/imports/ui/pages/Maintenance.vue');
// Not found
const NotFound = () => import('/imports/ui/pages/NotFound.vue');
@@ -92,8 +94,8 @@ function claimInvite(to, from, next){
});
}
RouterFactory.configure(factory => {
factory.addRoutes([{
RouterFactory.configure(router => {
router.addRoutes([{
path: '/',
name: 'home',
components: {
@@ -249,29 +251,40 @@ RouterFactory.configure(factory => {
name: 'admin',
component: Admin,
beforeEnter: ensureAdmin,
},{
path: '/maintenance',
name: 'maintenance',
component: Maintenance,
},
]);
});
// Not found route has lowest priority
RouterFactory.configure(factory => {
factory.addRoute({
RouterFactory.configure(router => {
router.addRoute({
path: '*',
component: NotFound,
});
}, -1);
function redirectIfMaintenance(to, from, next){
if (!MAINTENANCE_MODE) return next();
console.log(to);
if (to?.path === '/admin' || to?.path === '/maintenance') return next();
Tracker.autorun((computation) => {
if (userSubscription.ready()){
computation.stop();
const user = Meteor.user();
if (user && user.roles && user.roles.includes('admin')){
next({name: 'admin'})
} else {
next({name: 'maintenance'});
}
}
});
}
// Create the router instance
const router = routerFactory.create();
router.beforeEach((to, from, next) => {
let user = Meteor.user();
if (
to.path === '/sign-in' ||
(user && user.roles && user.roles.includes('admin'))
){
next();
} else {
next();
}
});
router.beforeEach(redirectIfMaintenance);
export default router;

View File

@@ -56,7 +56,9 @@ self.addEventListener('fetch', (event) => {
})));
}
caches.open(version).then(cache => cache.put(event.request, clonedResponse));
if (event.request.method !== 'POST') {
caches.open(version).then(cache => cache.put(event.request, clonedResponse));
}
}
return response;
}).catch(() => {

View File

@@ -9,3 +9,4 @@ import '/imports/api/parenting/organizeMethods.js';
import '/imports/api/users/patreon/updatePatreonOnLogin.js';
import '/imports/migrations/server/index.js';
import '/imports/migrations/methods/index.js'
import '/imports/constants/MAINTENANCE_MODE.js';