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; export default MAINTENANCE_MODE;

View File

@@ -4,7 +4,13 @@
<v-col cols="12"> <v-col cols="12">
<v-card> <v-card>
<v-card-text> <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> <h4>Git version: {{ versions && versions.gitVersion }}</h4>
<v-alert <v-alert
v-if="versionError" v-if="versionError"
@@ -14,20 +20,18 @@
</v-alert> </v-alert>
<v-btn <v-btn
icon icon
:loading="loadingVersion"
@click="refreshVersions" @click="refreshVersions"
> >
<v-icon>mdi-refresh</v-icon> <v-icon>mdi-refresh</v-icon>
</v-btn> </v-btn>
<v-text-field <br>
v-model="migrationInput"
label="Database version to migrate to"
/>
<v-btn <v-btn
:disabled="!migrationInput" :disabled="!(schemaVersion > (versions && versions.dbVersion))"
:loading="loadingMigration" :loading="loadingMigration"
@click="migrate" @click="migrate"
> >
Migrate Migrate to database version {{ schemaVersion }}
</v-btn> </v-btn>
<v-alert <v-alert
v-if="migrateError" v-if="migrateError"
@@ -45,6 +49,7 @@
<script lang="js"> <script lang="js">
import getVersion from '/imports/migrations/methods/getVersion.js'; import getVersion from '/imports/migrations/methods/getVersion.js';
import migrateTo from '/imports/migrations/methods/migrateTo.js'; import migrateTo from '/imports/migrations/methods/migrateTo.js';
import SCHEMA_VERSION from '/imports/constants/SCHEMA_VERSION.js';
export default { export default {
data(){return { data(){return {
@@ -54,25 +59,24 @@ export default {
versionError: undefined, versionError: undefined,
migrateError: undefined, migrateError: undefined,
loadingMigration: false, loadingMigration: false,
schemaVersion: SCHEMA_VERSION,
}}, }},
mounted(){ mounted(){
this.refreshVersions(); this.refreshVersions();
}, },
methods: { methods: {
refreshVersions(){ refreshVersions(){
this.loadingVersion = true;
getVersion.call((error, result) => { getVersion.call((error, result) => {
this.loadingVersion = false;
this.versionError = error; this.versionError = error;
this.versions = result; this.versions = result;
}); });
}, },
migrate(){ migrate(){
let version = this.migrationInput;
if (Number.isFinite(+version)){
version = +version;
}
this.loadingMigration = true; this.loadingMigration = true;
migrateTo.call({ migrateTo.call({
version, version: SCHEMA_VERSION,
}, error => { }, error => {
this.loadingMigration = false; this.loadingMigration = false;
this.migrateError = error; 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 { RouterFactory, nativeScrollBehavior } from 'meteor/akryum:vue-router2';
import { acceptInviteToken } from '/imports/api/users/Invites.js'; import { acceptInviteToken } from '/imports/api/users/Invites.js';
import MAINTENANCE_MODE from '/imports/constants/MAINTENANCE_MODE.js';
// Components // Components
const Home = () => import('/imports/ui/pages/Home.vue'); const Home = () => import('/imports/ui/pages/Home.vue');
const About = () => import('/imports/ui/pages/About.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 TabletopToolbar = () => import('/imports/ui/tabletop/TabletopToolbar.vue');
const TabletopRightDrawer = () => import('/imports/ui/tabletop/TabletopRightDrawer.vue'); const TabletopRightDrawer = () => import('/imports/ui/tabletop/TabletopRightDrawer.vue');
const Admin = () => import('/imports/ui/pages/Admin.vue'); const Admin = () => import('/imports/ui/pages/Admin.vue');
const Maintenance = () => import('/imports/ui/pages/Maintenance.vue');
// Not found // Not found
const NotFound = () => import('/imports/ui/pages/NotFound.vue'); const NotFound = () => import('/imports/ui/pages/NotFound.vue');
@@ -92,8 +94,8 @@ function claimInvite(to, from, next){
}); });
} }
RouterFactory.configure(factory => { RouterFactory.configure(router => {
factory.addRoutes([{ router.addRoutes([{
path: '/', path: '/',
name: 'home', name: 'home',
components: { components: {
@@ -249,29 +251,40 @@ RouterFactory.configure(factory => {
name: 'admin', name: 'admin',
component: Admin, component: Admin,
beforeEnter: ensureAdmin, beforeEnter: ensureAdmin,
},{
path: '/maintenance',
name: 'maintenance',
component: Maintenance,
}, },
]); ]);
}); });
// Not found route has lowest priority // Not found route has lowest priority
RouterFactory.configure(factory => { RouterFactory.configure(router => {
factory.addRoute({ router.addRoute({
path: '*', path: '*',
component: NotFound, component: NotFound,
}); });
}, -1); }, -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 // Create the router instance
const router = routerFactory.create(); const router = routerFactory.create();
router.beforeEach((to, from, next) => { router.beforeEach(redirectIfMaintenance);
let user = Meteor.user();
if (
to.path === '/sign-in' ||
(user && user.roles && user.roles.includes('admin'))
){
next();
} else {
next();
}
});
export default router; 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; return response;
}).catch(() => { }).catch(() => {

View File

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