From e35254cb6f88d522486efeb04e842351f241f9e9 Mon Sep 17 00:00:00 2001 From: Shweelan Samson Date: Mon, 8 Jul 2019 23:02:11 -0400 Subject: [PATCH] Server availability and version notification --- lib/ui.ts | 7 ++++ ui/app.ts | 1 + ui/config.ts | 1 + ui/dynamic-loader.ts | 28 +++++++++++++-- ui/store.ts | 84 +++++++++++++++++++++++++++++++++++--------- 5 files changed, 102 insertions(+), 19 deletions(-) diff --git a/lib/ui.ts b/lib/ui.ts index 96968bc..93b4060 100644 --- a/lib/ui.ts +++ b/lib/ui.ts @@ -33,6 +33,7 @@ import * as localCache from "./local-cache"; import { PermissionSet } from "./types"; import { authSimple } from "./ui/api-functions"; import * as init from "./init"; +import { version as VERSION } from "../package.json"; declare module "koa" { interface Request { @@ -64,6 +65,7 @@ koa.use(async (ctx, next) => { const configSnapshot = await localCache.getCurrentSnapshot(); ctx.state.configSnapshot = configSnapshot; ctx.set("X-Config-Snapshot", configSnapshot); + ctx.set("GenieACS-Version", VERSION); return next(); }); @@ -143,6 +145,10 @@ koa.use(async (ctx, next) => { koa.use(koaBodyParser()); router.use("/api", api.routes(), api.allowedMethods()); +router.get("/status", ctx => { + ctx.body = "OK"; +}); + router.get("/init", async ctx => { const status = await init.getStatus(); if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) { @@ -207,6 +213,7 @@ router.get("/", async ctx => { ui: localCache.getUiConfig(ctx.state.configSnapshot) })}; window.configSnapshot = ${JSON.stringify(ctx.state.configSnapshot)}; + window.genieacsVersion = ${JSON.stringify(VERSION)}; window.username = ${JSON.stringify( ctx.state.user ? ctx.state.user.username : "" )}; diff --git a/ui/app.ts b/ui/app.ts index 136b24f..5aee77c 100644 --- a/ui/app.ts +++ b/ui/app.ts @@ -45,6 +45,7 @@ declare global { username: string; clientConfig: any; configSnapshot: string; + genieacsVersion: string; } } diff --git a/ui/config.ts b/ui/config.ts index 454b1e5..b3ab39b 100644 --- a/ui/config.ts +++ b/ui/config.ts @@ -19,3 +19,4 @@ export default window.clientConfig; export const configSnapshot = window.configSnapshot; +export const genieacsVersion = window.genieacsVersion; diff --git a/ui/dynamic-loader.ts b/ui/dynamic-loader.ts index 34964af..7d2a875 100644 --- a/ui/dynamic-loader.ts +++ b/ui/dynamic-loader.ts @@ -17,9 +17,27 @@ * along with GenieACS. If not, see . */ +import * as notifications from "./notifications"; + export let codeMirror; export let yaml; +let note; + +function onError(): void { + if (!note) { + note = notifications.push( + "error", + "Error loading JS resource, please reload the page", + { + Reload: () => { + window.location.reload(); + } + } + ); + } +} + export function loadCodeMirror(): Promise { if (codeMirror) return Promise.resolve(); @@ -36,7 +54,10 @@ export function loadCodeMirror(): Promise { codeMirror = modules[0]; resolve(); }) - .catch(reject); + .catch(err => { + onError(); + reject(err); + }); }); } @@ -49,6 +70,9 @@ export function loadYaml(): Promise { yaml = module; resolve(); }) - .catch(reject); + .catch(err => { + onError(); + reject(err); + }); }); } diff --git a/ui/store.ts b/ui/store.ts index f040a03..b714a36 100644 --- a/ui/store.ts +++ b/ui/store.ts @@ -23,13 +23,13 @@ import { or, and, not, evaluate, subset } from "../lib/common/expression"; import memoize from "../lib/common/memoize"; import { QueryOptions, Expression } from "../lib/types"; import * as notifications from "./notifications"; -import { configSnapshot } from "./config"; +import { configSnapshot, genieacsVersion } from "./config"; const memoizedStringify = memoize(stringify); const memoizedEvaluate = memoize(evaluate); let fulfillTimestamp = 0; -let refreshNotification; +let connectionNotification, configNotification, versionNotification; const queries = { filter: new WeakMap(), @@ -89,6 +89,71 @@ class QueryResponse { } } +function checkConnection(): void { + m.request({ + url: "/status", + method: "GET", + background: true, + extract: xhr => { + if (xhr.status !== 200) { + if (!connectionNotification) { + connectionNotification = notifications.push( + "warning", + "Server is unreachable", + {} + ); + } + } else { + if (connectionNotification) { + notifications.dismiss(connectionNotification); + connectionNotification = null; + } + + const configChanged = + xhr.getResponseHeader("x-config-snapshot") !== configSnapshot; + const versionChanged = + xhr.getResponseHeader("genieacs-version") !== genieacsVersion; + + if (!configNotification !== !configChanged) { + if (configNotification) { + notifications.dismiss(configNotification); + configNotification = null; + } else { + configNotification = notifications.push( + "warning", + "Configuration has been modified, please reload the page", + { + Reload: () => { + window.location.reload(); + } + } + ); + } + } + + if (!versionNotification !== !versionChanged) { + if (versionNotification) { + notifications.dismiss(versionNotification); + versionNotification = null; + } else { + versionNotification = notifications.push( + "warning", + "Server has been updated, please reload the page", + { + Reload: () => { + window.location.reload(); + } + } + ); + } + } + } + } + }); +} + +setInterval(checkConnection, 3000); + export async function xhrRequest( options: { url: string } & m.RequestOptions<{}> ): Promise { @@ -99,21 +164,6 @@ export async function xhrRequest( xhr: XMLHttpRequest, _options?: { url: string } & m.RequestOptions<{}> ): any => { - if ( - !refreshNotification && - configSnapshot !== xhr.getResponseHeader("x-config-snapshot") - ) { - refreshNotification = notifications.push( - "warning", - "Configuration has been modified, please reload the page", - { - Reload: () => { - window.location.reload(); - } - } - ); - } - if (typeof extract === "function") return extract(xhr, _options); let response: any;