From 2a05e26d6de8ff66aeb5aa5513c11be1ec0f9d64 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 25 May 2022 13:15:21 +0200 Subject: [PATCH] Added settings and improved websocket management. --- quasar-app/src/api/linkTokens.js | 41 ++++++----- quasar-app/src/api/railSystems.js | 4 + quasar-app/src/api/settings.js | 23 ++++++ quasar-app/src/api/websocket.js | 42 +++++++---- quasar-app/src/components/rs/SettingsView.vue | 73 ++++++++++++++++++- .../components/rs/settings/LinkTokensView.vue | 28 ++++--- quasar-app/src/pages/RailSystem.vue | 14 ++-- quasar-app/src/stores/railSystemsStore.js | 54 +++++++++----- 8 files changed, 204 insertions(+), 75 deletions(-) create mode 100644 quasar-app/src/api/settings.js diff --git a/quasar-app/src/api/linkTokens.js b/quasar-app/src/api/linkTokens.js index 840dc9b..f0b2490 100644 --- a/quasar-app/src/api/linkTokens.js +++ b/quasar-app/src/api/linkTokens.js @@ -13,20 +13,22 @@ export class LinkToken { } /** - * Gets the list of link tokens in a rail system. + * Refreshes the list of link tokens in a rail system. * @param {RailSystem} rs - * @return {Promise} + * @return {Promise} */ -export function getLinkTokens(rs) { - return new Promise((resolve, reject) => { - axios.get(`${API_URL}/rs/${rs.id}/lt`) - .then(response => { - resolve(response.data.map(obj => new LinkToken(obj))); - }) - .catch(reject); - }); +export function refreshLinkTokens(rs) { + return new Promise((resolve, reject) => { + axios.get(`${API_URL}/rs/${rs.id}/lt`) + .then(response => { + rs.linkTokens = response.data; + resolve(); + }) + .catch(reject); + }); } + /** * Creates a new link token. * @param {RailSystem} rs @@ -34,13 +36,14 @@ export function getLinkTokens(rs) { * @return {Promise} A promise that resolves to the token that was created. */ export function createLinkToken(rs, data) { - return new Promise((resolve, reject) => { - axios.post(`${API_URL}/rs/${rs.id}/lt`, data) - .then(response => { - resolve(response.data.token); - }) - .catch(reject); - }); + return new Promise((resolve, reject) => { + axios.post(`${API_URL}/rs/${rs.id}/lt`, data) + .then(response => { + const token = response.data.token; + refreshLinkTokens(rs).then(() => resolve(token)).catch(reject); + }) + .catch(reject); + }); } /** @@ -51,7 +54,9 @@ export function createLinkToken(rs, data) { export function deleteToken(rs, tokenId) { return new Promise((resolve, reject) => { axios.delete(`${API_URL}/rs/${rs.id}/lt/${tokenId}`) - .then(resolve) + .then(() => { + refreshLinkTokens(rs).then(resolve).catch(reject); + }) .catch(reject); }); } diff --git a/quasar-app/src/api/railSystems.js b/quasar-app/src/api/railSystems.js index 1d80a21..b6de070 100644 --- a/quasar-app/src/api/railSystems.js +++ b/quasar-app/src/api/railSystems.js @@ -7,8 +7,12 @@ export class RailSystem { constructor(data) { this.id = data.id; this.name = data.name; + this.settings = null; + this.segments = []; this.components = []; + this.linkTokens = []; + this.websocket = null; this.selectedComponents = []; } diff --git a/quasar-app/src/api/settings.js b/quasar-app/src/api/settings.js new file mode 100644 index 0000000..ccdd508 --- /dev/null +++ b/quasar-app/src/api/settings.js @@ -0,0 +1,23 @@ +import {API_URL} from "src/api/constants"; +import axios from "axios"; + +export function refreshSettings(rs) { + return new Promise((resolve, reject) => { + axios.get(`${API_URL}/rs/${rs.id}/settings`) + .then(response => { + rs.settings = response.data; + resolve(); + }) + .catch(reject); + }); +} + +export function updateSettings(rs, newSettings) { + return new Promise((resolve, reject) => { + axios.post(`${API_URL}/rs/${rs.id}/settings`, newSettings) + .then(() => { + refreshSettings(rs).then(resolve).catch(reject); + }) + .catch(reject); + }); +} diff --git a/quasar-app/src/api/websocket.js b/quasar-app/src/api/websocket.js index 741c548..160700f 100644 --- a/quasar-app/src/api/websocket.js +++ b/quasar-app/src/api/websocket.js @@ -1,39 +1,49 @@ -import {WS_URL} from "./constants"; +import { WS_URL } from "./constants"; + +const WS_RECONNECT_TIMEOUT = 3000; /** * Establishes a websocket connection to the given rail system. * @param {RailSystem} rs + * @return {Promise} A promise that resolves when a connection is established. */ export function establishWebsocketConnection(rs) { - closeWebsocketConnection(rs); + return new Promise(resolve => { rs.websocket = new WebSocket(`${WS_URL}/${rs.id}`); - rs.websocket.onopen = () => { - console.log("Opened websocket connection to rail system " + rs.id); - }; + rs.websocket.onopen = resolve; rs.websocket.onclose = event => { - if (event.code !== 1000) { - console.warn("Lost websocket connection. Attempting to reestablish."); - setTimeout(() => { - establishWebsocketConnection(rs); - }, 3000); - } - console.log("Closed websocket connection to rail system " + rs.id); + if (event.code === 1000) { + console.log(`Closed websocket connection to rail system "${rs.name}" (${rs.id})`); + } else { + console.warn(`Unexpectedly lost websocket connection to rail system "${rs.name}" (${rs.id}). Attempting to reestablish in ${WS_RECONNECT_TIMEOUT} ms.`); + setTimeout(() => { + establishWebsocketConnection(rs) + .then(() => console.log("Successfully reestablished connection.")); + }, WS_RECONNECT_TIMEOUT); + } }; rs.websocket.onmessage = msg => { - console.log(msg); + console.log(msg); }; rs.websocket.onerror = error => { - console.log(error); + console.log(error); }; + }); } /** * Closes the websocket connection to a rail system, if possible. * @param {RailSystem} rs + * @return {Promise} A promise that resolves when the connection is closed. */ export function closeWebsocketConnection(rs) { + return new Promise(resolve => { if (rs.websocket) { - rs.websocket.close(); - rs.websocket = null; + rs.websocket.onclose = resolve; + rs.websocket.close(); + } else { + resolve(); } + }); + } diff --git a/quasar-app/src/components/rs/SettingsView.vue b/quasar-app/src/components/rs/SettingsView.vue index 4da3877..8689513 100644 --- a/quasar-app/src/components/rs/SettingsView.vue +++ b/quasar-app/src/components/rs/SettingsView.vue @@ -1,12 +1,41 @@ diff --git a/quasar-app/src/components/rs/settings/LinkTokensView.vue b/quasar-app/src/components/rs/settings/LinkTokensView.vue index cd44422..c6957a4 100644 --- a/quasar-app/src/components/rs/settings/LinkTokensView.vue +++ b/quasar-app/src/components/rs/settings/LinkTokensView.vue @@ -3,21 +3,32 @@ expand-separator label="Component Links" caption="Link components to your system." - @before-show="refreshLinkTokens" switch-toggle-side :content-inset-level="0.5" > {{token.label}} {{token.id}} + + Components + + + + - + There are no link tokens. Add one via the Add Link button below. @@ -91,7 +102,7 @@ diff --git a/quasar-app/src/stores/railSystemsStore.js b/quasar-app/src/stores/railSystemsStore.js index 972d7f1..d86195b 100644 --- a/quasar-app/src/stores/railSystemsStore.js +++ b/quasar-app/src/stores/railSystemsStore.js @@ -1,8 +1,10 @@ -import {defineStore} from "pinia"; -import {refreshSegments} from "../api/segments" -import {refreshComponents} from "../api/components"; -import {closeWebsocketConnection, establishWebsocketConnection} from "../api/websocket"; +import { defineStore } from "pinia"; +import { refreshSegments } from "../api/segments"; +import { refreshComponents } from "../api/components"; +import { closeWebsocketConnection, establishWebsocketConnection } from "../api/websocket"; import { refreshRailSystems } from "src/api/railSystems"; +import { refreshLinkTokens } from "src/api/linkTokens"; +import { refreshSettings } from "src/api/settings"; export const useRailSystemsStore = defineStore('RailSystemsStore', { state: () => ({ @@ -13,26 +15,38 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', { /** * @type {RailSystem | null} */ - selectedRailSystem: null, - - loaded: false + selectedRailSystem: null }), actions: { - async selectRailSystem(rsId) { - if (!this.loaded) { - await refreshRailSystems(this); + /** + * Updates the selected rail system. + * @param rsId {Number} The new rail system id. + * @returns {Promise} A promise that resolves when the new rail system is + * fully loaded and ready. + */ + selectRailSystem(rsId) { + // Close any existing websocket connections prior to refreshing. + const wsClosePromises = []; + if (this.selectedRailSystem) { + wsClosePromises.push(closeWebsocketConnection(this.selectedRailSystem)); } - this.railSystems.forEach(r => { - r.components.length = 0; - r.segments.length = 0; - closeWebsocketConnection(r); + return new Promise(resolve => { + Promise.all(wsClosePromises).then(() => { + refreshRailSystems(this).then(() => { + const rs = this.railSystems.find(r => r.id === rsId); + const updatePromises = []; + updatePromises.push(refreshSegments(rs)); + updatePromises.push(refreshComponents(rs)); + updatePromises.push(refreshLinkTokens(rs)); + updatePromises.push(refreshSettings(rs)); + updatePromises.push(establishWebsocketConnection(rs)); + Promise.all(updatePromises).then(() => { + this.selectedRailSystem = rs; + resolve(); + }); + }); + }); }); - if (!rsId) return; - const rs = this.railSystems.find(r => r.id === rsId); - await refreshSegments(rs); - await refreshComponents(rs); - establishWebsocketConnection(rs); - this.selectedRailSystem = rs; } }, getters: {