summaryrefslogtreecommitdiffstats
path: root/src/mixins/socket.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-11-26 09:28:28 +0100
committerDaniel Baumann <daniel@debian.org>2024-11-26 12:25:58 +0100
commita1882b67c41fe9901a0cd8059b5cc78a5beadec0 (patch)
tree2a24507c67aa99a15416707b2f7e645142230ed8 /src/mixins/socket.js
parentInitial commit. (diff)
downloaduptime-kuma-a1882b67c41fe9901a0cd8059b5cc78a5beadec0.tar.xz
uptime-kuma-a1882b67c41fe9901a0cd8059b5cc78a5beadec0.zip
Adding upstream version 2.0.0~beta.0+dfsg.upstream/2.0.0_beta.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'src/mixins/socket.js')
-rw-r--r--src/mixins/socket.js879
1 files changed, 879 insertions, 0 deletions
diff --git a/src/mixins/socket.js b/src/mixins/socket.js
new file mode 100644
index 0000000..3272e04
--- /dev/null
+++ b/src/mixins/socket.js
@@ -0,0 +1,879 @@
+import { io } from "socket.io-client";
+import { useToast } from "vue-toastification";
+import jwtDecode from "jwt-decode";
+import Favico from "favico.js";
+import dayjs from "dayjs";
+import mitt from "mitt";
+
+import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
+import { getDevContainerServerHostname, isDevContainer, getToastSuccessTimeout, getToastErrorTimeout } from "../util-frontend.js";
+const toast = useToast();
+
+let socket;
+
+const noSocketIOPages = [
+ /^\/status-page$/, // /status-page
+ /^\/status/, // /status**
+ /^\/$/ // /
+];
+
+const favicon = new Favico({
+ animation: "none"
+});
+
+export default {
+
+ data() {
+ return {
+ info: { },
+ socket: {
+ token: null,
+ firstConnect: true,
+ connected: false,
+ connectCount: 0,
+ initedSocketIO: false,
+ },
+ username: null,
+ remember: (localStorage.remember !== "0"),
+ allowLoginDialog: false, // Allowed to show login dialog, but "loggedIn" have to be true too. This exists because prevent the login dialog show 0.1s in first before the socket server auth-ed.
+ loggedIn: false,
+ monitorList: { },
+ monitorTypeList: {},
+ maintenanceList: {},
+ apiKeyList: {},
+ heartbeatList: { },
+ avgPingList: { },
+ uptimeList: { },
+ tlsInfoList: {},
+ notificationList: [],
+ dockerHostList: [],
+ remoteBrowserList: [],
+ statusPageListLoaded: false,
+ statusPageList: [],
+ proxyList: [],
+ connectionErrorMsg: `${this.$t("Cannot connect to the socket server.")} ${this.$t("Reconnecting...")}`,
+ showReverseProxyGuide: true,
+ cloudflared: {
+ cloudflareTunnelToken: "",
+ installed: null,
+ running: false,
+ message: "",
+ errorMessage: "",
+ currentPassword: "",
+ },
+ faviconUpdateDebounce: null,
+ emitter: mitt(),
+ };
+ },
+
+ created() {
+ this.initSocketIO();
+ },
+
+ methods: {
+
+ /**
+ * Initialize connection to socket server
+ * @param {boolean} bypass Should the check for if we
+ * are on a status page be bypassed?
+ * @returns {void}
+ */
+ initSocketIO(bypass = false) {
+ // No need to re-init
+ if (this.socket.initedSocketIO) {
+ return;
+ }
+
+ // No need to connect to the socket.io for status page
+ if (! bypass && location.pathname) {
+ for (let page of noSocketIOPages) {
+ if (location.pathname.match(page)) {
+ return;
+ }
+ }
+ }
+
+ // Also don't need to connect to the socket.io for setup database page
+ if (location.pathname === "/setup-database") {
+ return;
+ }
+
+ this.socket.initedSocketIO = true;
+
+ let protocol = location.protocol + "//";
+
+ let url;
+ const env = process.env.NODE_ENV || "production";
+ if (env === "development" && isDevContainer()) {
+ url = protocol + getDevContainerServerHostname();
+ } else if (env === "development" || localStorage.dev === "dev") {
+ url = protocol + location.hostname + ":3001";
+ } else {
+ // Connect to the current url
+ url = undefined;
+ }
+
+ socket = io(url);
+
+ socket.on("info", (info) => {
+ this.info = info;
+ });
+
+ socket.on("setup", (monitorID, data) => {
+ this.$router.push("/setup");
+ });
+
+ socket.on("autoLogin", (monitorID, data) => {
+ this.loggedIn = true;
+ this.storage().token = "autoLogin";
+ this.socket.token = "autoLogin";
+ this.allowLoginDialog = false;
+ });
+
+ socket.on("loginRequired", () => {
+ let token = this.storage().token;
+ if (token && token !== "autoLogin") {
+ this.loginByToken(token);
+ } else {
+ this.$root.storage().removeItem("token");
+ this.allowLoginDialog = true;
+ }
+ });
+
+ socket.on("monitorList", (data) => {
+ this.assignMonitorUrlParser(data);
+ this.monitorList = data;
+ });
+
+ socket.on("updateMonitorIntoList", (data) => {
+ this.assignMonitorUrlParser(data);
+ Object.entries(data).forEach(([ monitorID, updatedMonitor ]) => {
+ this.monitorList[monitorID] = updatedMonitor;
+ });
+ });
+
+ socket.on("deleteMonitorFromList", (monitorID) => {
+ if (this.monitorList[monitorID]) {
+ delete this.monitorList[monitorID];
+ }
+ });
+
+ socket.on("monitorTypeList", (data) => {
+ this.monitorTypeList = data;
+ });
+
+ socket.on("maintenanceList", (data) => {
+ this.maintenanceList = data;
+ });
+
+ socket.on("apiKeyList", (data) => {
+ this.apiKeyList = data;
+ });
+
+ socket.on("notificationList", (data) => {
+ this.notificationList = data;
+ });
+
+ socket.on("statusPageList", (data) => {
+ this.statusPageListLoaded = true;
+ this.statusPageList = data;
+ });
+
+ socket.on("proxyList", (data) => {
+ this.proxyList = data.map(item => {
+ item.auth = !!item.auth;
+ item.active = !!item.active;
+ item.default = !!item.default;
+
+ return item;
+ });
+ });
+
+ socket.on("dockerHostList", (data) => {
+ this.dockerHostList = data;
+ });
+
+ socket.on("remoteBrowserList", (data) => {
+ this.remoteBrowserList = data;
+ });
+
+ socket.on("heartbeat", (data) => {
+ if (! (data.monitorID in this.heartbeatList)) {
+ this.heartbeatList[data.monitorID] = [];
+ }
+
+ this.heartbeatList[data.monitorID].push(data);
+
+ if (this.heartbeatList[data.monitorID].length >= 150) {
+ this.heartbeatList[data.monitorID].shift();
+ }
+
+ // Add to important list if it is important
+ // Also toast
+ if (data.important) {
+
+ if (this.monitorList[data.monitorID] !== undefined) {
+ if (data.status === 0) {
+ toast.error(`[${this.monitorList[data.monitorID].name}] [DOWN] ${data.msg}`, {
+ timeout: getToastErrorTimeout(),
+ });
+ } else if (data.status === 1) {
+ toast.success(`[${this.monitorList[data.monitorID].name}] [Up] ${data.msg}`, {
+ timeout: getToastSuccessTimeout(),
+ });
+ } else {
+ toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`);
+ }
+ }
+
+ this.emitter.emit("newImportantHeartbeat", data);
+ }
+ });
+
+ socket.on("heartbeatList", (monitorID, data, overwrite = false) => {
+ if (! (monitorID in this.heartbeatList) || overwrite) {
+ this.heartbeatList[monitorID] = data;
+ } else {
+ this.heartbeatList[monitorID] = data.concat(this.heartbeatList[monitorID]);
+ }
+ });
+
+ socket.on("avgPing", (monitorID, data) => {
+ this.avgPingList[monitorID] = data;
+ });
+
+ socket.on("uptime", (monitorID, type, data) => {
+ this.uptimeList[`${monitorID}_${type}`] = data;
+ });
+
+ socket.on("certInfo", (monitorID, data) => {
+ this.tlsInfoList[monitorID] = JSON.parse(data);
+ });
+
+ socket.on("connect_error", (err) => {
+ console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`);
+ this.connectionErrorMsg = `${this.$t("Cannot connect to the socket server.")} [${err}] ${this.$t("Reconnecting...")}`;
+ this.showReverseProxyGuide = true;
+ this.socket.connected = false;
+ this.socket.firstConnect = false;
+ });
+
+ socket.on("disconnect", () => {
+ console.log("disconnect");
+ this.connectionErrorMsg = `${this.$t("Lost connection to the socket server.")} ${this.$t("Reconnecting...")}`;
+ this.socket.connected = false;
+ });
+
+ socket.on("connect", () => {
+ console.log("Connected to the socket server");
+ this.socket.connectCount++;
+ this.socket.connected = true;
+ this.showReverseProxyGuide = false;
+
+ // Reset Heartbeat list if it is re-connect
+ if (this.socket.connectCount >= 2) {
+ this.clearData();
+ }
+
+ this.socket.firstConnect = false;
+ });
+
+ // cloudflared
+ socket.on("cloudflared_installed", (res) => this.cloudflared.installed = res);
+ socket.on("cloudflared_running", (res) => this.cloudflared.running = res);
+ socket.on("cloudflared_message", (res) => this.cloudflared.message = res);
+ socket.on("cloudflared_errorMessage", (res) => this.cloudflared.errorMessage = res);
+ socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res);
+
+ socket.on("initServerTimezone", () => {
+ socket.emit("initServerTimezone", dayjs.tz.guess());
+ });
+
+ socket.on("refresh", () => {
+ location.reload();
+ });
+ },
+ /**
+ * parse all urls from list.
+ * @param {object} data Monitor data to modify
+ * @returns {object} list
+ */
+ assignMonitorUrlParser(data) {
+ Object.entries(data).forEach(([ monitorID, monitor ]) => {
+ monitor.getUrl = () => {
+ try {
+ return new URL(monitor.url);
+ } catch (_) {
+ return null;
+ }
+ };
+ });
+ return data;
+ },
+
+ /**
+ * The storage currently in use
+ * @returns {Storage} Current storage
+ */
+ storage() {
+ return (this.remember) ? localStorage : sessionStorage;
+ },
+
+ /**
+ * Get payload of JWT cookie
+ * @returns {(object | undefined)} JWT payload
+ */
+ getJWTPayload() {
+ const jwtToken = this.$root.storage().token;
+
+ if (jwtToken && jwtToken !== "autoLogin") {
+ return jwtDecode(jwtToken);
+ }
+ return undefined;
+ },
+
+ /**
+ * Get current socket
+ * @returns {Socket} Current socket
+ */
+ getSocket() {
+ return socket;
+ },
+
+ /**
+ * Show success or error toast dependent on response status code
+ * @param {object} res Response object
+ * @returns {void}
+ */
+ toastRes(res) {
+ let msg = res.msg;
+ if (res.msgi18n) {
+ if (msg != null && typeof msg === "object") {
+ msg = this.$t(msg.key, msg.values);
+ } else {
+ msg = this.$t(msg);
+ }
+ }
+
+ if (res.ok) {
+ toast.success(msg);
+ } else {
+ toast.error(msg);
+ }
+ },
+
+ /**
+ * Show a success toast
+ * @param {string} msg Message to show
+ * @returns {void}
+ */
+ toastSuccess(msg) {
+ toast.success(this.$t(msg));
+ },
+
+ /**
+ * Show an error toast
+ * @param {string} msg Message to show
+ * @returns {void}
+ */
+ toastError(msg) {
+ toast.error(this.$t(msg));
+ },
+
+ /**
+ * Callback for login
+ * @callback loginCB
+ * @param {object} res Response object
+ */
+
+ /**
+ * Send request to log user in
+ * @param {string} username Username to log in with
+ * @param {string} password Password to log in with
+ * @param {string} token User token
+ * @param {loginCB} callback Callback to call with result
+ * @returns {void}
+ */
+ login(username, password, token, callback) {
+ socket.emit("login", {
+ username,
+ password,
+ token,
+ }, (res) => {
+ if (res.tokenRequired) {
+ callback(res);
+ }
+
+ if (res.ok) {
+ this.storage().token = res.token;
+ this.socket.token = res.token;
+ this.loggedIn = true;
+ this.username = this.getJWTPayload()?.username;
+
+ // Trigger Chrome Save Password
+ history.pushState({}, "");
+ }
+
+ callback(res);
+ });
+ },
+
+ /**
+ * Log in using a token
+ * @param {string} token Token to log in with
+ * @returns {void}
+ */
+ loginByToken(token) {
+ socket.emit("loginByToken", token, (res) => {
+ this.allowLoginDialog = true;
+
+ if (! res.ok) {
+ this.logout();
+ } else {
+ this.loggedIn = true;
+ this.username = this.getJWTPayload()?.username;
+ }
+ });
+ },
+
+ /**
+ * Log out of the web application
+ * @returns {void}
+ */
+ logout() {
+ socket.emit("logout", () => { });
+ this.storage().removeItem("token");
+ this.socket.token = null;
+ this.loggedIn = false;
+ this.username = null;
+ this.clearData();
+ },
+
+ /**
+ * Callback for general socket requests
+ * @callback socketCB
+ * @param {object} res Result of operation
+ */
+ /**
+ * Prepare 2FA configuration
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ prepare2FA(callback) {
+ socket.emit("prepare2FA", callback);
+ },
+
+ /**
+ * Save the current 2FA configuration
+ * @param {any} secret Unused
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ save2FA(secret, callback) {
+ socket.emit("save2FA", callback);
+ },
+
+ /**
+ * Disable 2FA for this user
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ disable2FA(callback) {
+ socket.emit("disable2FA", callback);
+ },
+
+ /**
+ * Verify the provided 2FA token
+ * @param {string} token Token to verify
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ verifyToken(token, callback) {
+ socket.emit("verifyToken", token, callback);
+ },
+
+ /**
+ * Get current 2FA status
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ twoFAStatus(callback) {
+ socket.emit("twoFAStatus", callback);
+ },
+
+ /**
+ * Get list of monitors
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ getMonitorList(callback) {
+ if (! callback) {
+ callback = () => { };
+ }
+ socket.emit("getMonitorList", callback);
+ },
+
+ /**
+ * Get list of maintenances
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ getMaintenanceList(callback) {
+ if (! callback) {
+ callback = () => { };
+ }
+ socket.emit("getMaintenanceList", callback);
+ },
+
+ /**
+ * Send list of API keys
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ getAPIKeyList(callback) {
+ if (!callback) {
+ callback = () => { };
+ }
+ socket.emit("getAPIKeyList", callback);
+ },
+
+ /**
+ * Add a monitor
+ * @param {object} monitor Object representing monitor to add
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ add(monitor, callback) {
+ socket.emit("add", monitor, callback);
+ },
+
+ /**
+ * Adds a maintenance
+ * @param {object} maintenance Maintenance to add
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ addMaintenance(maintenance, callback) {
+ socket.emit("addMaintenance", maintenance, callback);
+ },
+
+ /**
+ * Add monitors to maintenance
+ * @param {number} maintenanceID Maintenance to modify
+ * @param {number[]} monitors IDs of monitors to add
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ addMonitorMaintenance(maintenanceID, monitors, callback) {
+ socket.emit("addMonitorMaintenance", maintenanceID, monitors, callback);
+ },
+
+ /**
+ * Add status page to maintenance
+ * @param {number} maintenanceID Maintenance to modify
+ * @param {number} statusPages ID of status page to add
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ addMaintenanceStatusPage(maintenanceID, statusPages, callback) {
+ socket.emit("addMaintenanceStatusPage", maintenanceID, statusPages, callback);
+ },
+
+ /**
+ * Get monitors affected by maintenance
+ * @param {number} maintenanceID Maintenance to read
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ getMonitorMaintenance(maintenanceID, callback) {
+ socket.emit("getMonitorMaintenance", maintenanceID, callback);
+ },
+
+ /**
+ * Get status pages where maintenance is shown
+ * @param {number} maintenanceID Maintenance to read
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ getMaintenanceStatusPage(maintenanceID, callback) {
+ socket.emit("getMaintenanceStatusPage", maintenanceID, callback);
+ },
+
+ /**
+ * Delete monitor by ID
+ * @param {number} monitorID ID of monitor to delete
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ deleteMonitor(monitorID, callback) {
+ socket.emit("deleteMonitor", monitorID, callback);
+ },
+
+ /**
+ * Delete specified maintenance
+ * @param {number} maintenanceID Maintenance to delete
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ deleteMaintenance(maintenanceID, callback) {
+ socket.emit("deleteMaintenance", maintenanceID, callback);
+ },
+
+ /**
+ * Add an API key
+ * @param {object} key API key to add
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ addAPIKey(key, callback) {
+ socket.emit("addAPIKey", key, callback);
+ },
+
+ /**
+ * Delete specified API key
+ * @param {int} keyID ID of key to delete
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ deleteAPIKey(keyID, callback) {
+ socket.emit("deleteAPIKey", keyID, callback);
+ },
+
+ /**
+ * Clear the hearbeat list
+ * @returns {void}
+ */
+ clearData() {
+ console.log("reset heartbeat list");
+ this.heartbeatList = {};
+ },
+
+ /**
+ * Upload the provided backup
+ * @param {string} uploadedJSON JSON to upload
+ * @param {string} importHandle Type of import. If set to
+ * most data in database will be replaced
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ uploadBackup(uploadedJSON, importHandle, callback) {
+ socket.emit("uploadBackup", uploadedJSON, importHandle, callback);
+ },
+
+ /**
+ * Clear events for a specified monitor
+ * @param {number} monitorID ID of monitor to clear
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ clearEvents(monitorID, callback) {
+ socket.emit("clearEvents", monitorID, callback);
+ },
+
+ /**
+ * Clear the heartbeats of a specified monitor
+ * @param {number} monitorID Id of monitor to clear
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ clearHeartbeats(monitorID, callback) {
+ socket.emit("clearHeartbeats", monitorID, callback);
+ },
+
+ /**
+ * Clear all statistics
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ clearStatistics(callback) {
+ socket.emit("clearStatistics", callback);
+ },
+
+ /**
+ * Get monitor beats for a specific monitor in a time range
+ * @param {number} monitorID ID of monitor to fetch
+ * @param {number} period Time in hours from now
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ getMonitorBeats(monitorID, period, callback) {
+ socket.emit("getMonitorBeats", monitorID, period, callback);
+ },
+
+ /**
+ * Retrieves monitor chart data.
+ * @param {string} monitorID - The ID of the monitor.
+ * @param {number} period - The time period for the chart data, in hours.
+ * @param {socketCB} callback - The callback function to handle the chart data.
+ * @returns {void}
+ */
+ getMonitorChartData(monitorID, period, callback) {
+ socket.emit("getMonitorChartData", monitorID, period, callback);
+ }
+ },
+
+ computed: {
+
+ usernameFirstChar() {
+ if (typeof this.username == "string" && this.username.length >= 1) {
+ return this.username.charAt(0).toUpperCase();
+ } else {
+ return "🐻";
+ }
+ },
+
+ lastHeartbeatList() {
+ let result = {};
+
+ for (let monitorID in this.heartbeatList) {
+ let index = this.heartbeatList[monitorID].length - 1;
+ result[monitorID] = this.heartbeatList[monitorID][index];
+ }
+
+ return result;
+ },
+
+ statusList() {
+ let result = {};
+
+ let unknown = {
+ text: this.$t("Unknown"),
+ color: "secondary",
+ };
+
+ for (let monitorID in this.lastHeartbeatList) {
+ let lastHeartBeat = this.lastHeartbeatList[monitorID];
+
+ if (! lastHeartBeat) {
+ result[monitorID] = unknown;
+ } else if (lastHeartBeat.status === UP) {
+ result[monitorID] = {
+ text: this.$t("Up"),
+ color: "primary",
+ };
+ } else if (lastHeartBeat.status === DOWN) {
+ result[monitorID] = {
+ text: this.$t("Down"),
+ color: "danger",
+ };
+ } else if (lastHeartBeat.status === PENDING) {
+ result[monitorID] = {
+ text: this.$t("Pending"),
+ color: "warning",
+ };
+ } else if (lastHeartBeat.status === MAINTENANCE) {
+ result[monitorID] = {
+ text: this.$t("statusMaintenance"),
+ color: "maintenance",
+ };
+ } else {
+ result[monitorID] = unknown;
+ }
+ }
+
+ return result;
+ },
+
+ stats() {
+ let result = {
+ active: 0,
+ up: 0,
+ down: 0,
+ maintenance: 0,
+ pending: 0,
+ unknown: 0,
+ pause: 0,
+ };
+
+ for (let monitorID in this.$root.monitorList) {
+ let beat = this.$root.lastHeartbeatList[monitorID];
+ let monitor = this.$root.monitorList[monitorID];
+
+ if (monitor && ! monitor.active) {
+ result.pause++;
+ } else if (beat) {
+ result.active++;
+ if (beat.status === UP) {
+ result.up++;
+ } else if (beat.status === DOWN) {
+ result.down++;
+ } else if (beat.status === PENDING) {
+ result.pending++;
+ } else if (beat.status === MAINTENANCE) {
+ result.maintenance++;
+ } else {
+ result.unknown++;
+ }
+ } else {
+ result.unknown++;
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Frontend Version
+ * It should be compiled to a static value while building the frontend.
+ * Please see ./config/vite.config.js, it is defined via vite.js
+ * @returns {string} Current version
+ */
+ frontendVersion() {
+ // eslint-disable-next-line no-undef
+ return FRONTEND_VERSION;
+ },
+
+ /**
+ * Are both frontend and backend in the same version?
+ * @returns {boolean} The frontend and backend match?
+ */
+ isFrontendBackendVersionMatched() {
+ if (!this.info.version) {
+ return true;
+ }
+ return this.info.version === this.frontendVersion;
+ }
+ },
+
+ watch: {
+
+ // Update Badge
+ "stats.down"(to, from) {
+ if (to !== from) {
+ if (this.faviconUpdateDebounce != null) {
+ clearTimeout(this.faviconUpdateDebounce);
+ }
+ this.faviconUpdateDebounce = setTimeout(() => {
+ favicon.badge(to);
+ }, 1000);
+ }
+ },
+
+ // Reload the SPA if the server version is changed.
+ "info.version"(to, from) {
+ if (from && from !== to) {
+ window.location.reload();
+ }
+ },
+
+ remember() {
+ localStorage.remember = (this.remember) ? "1" : "0";
+ },
+
+ // Reconnect the socket io, if status-page to dashboard
+ "$route.fullPath"(newValue, oldValue) {
+
+ if (newValue) {
+ for (let page of noSocketIOPages) {
+ if (newValue.match(page)) {
+ return;
+ }
+ }
+ }
+
+ this.initSocketIO();
+ },
+
+ },
+
+};