Added settings and improved websocket management.
This commit is contained in:
parent
e1a756ffc9
commit
2a05e26d6d
|
@ -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<LinkToken[]>}
|
||||
* @return {Promise}
|
||||
*/
|
||||
export function getLinkTokens(rs) {
|
||||
export function refreshLinkTokens(rs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.get(`${API_URL}/rs/${rs.id}/lt`)
|
||||
.then(response => {
|
||||
resolve(response.data.map(obj => new LinkToken(obj)));
|
||||
rs.linkTokens = response.data;
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new link token.
|
||||
* @param {RailSystem} rs
|
||||
|
@ -37,7 +39,8 @@ 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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 = [];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -1,23 +1,26 @@
|
|||
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.");
|
||||
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);
|
||||
}, 3000);
|
||||
establishWebsocketConnection(rs)
|
||||
.then(() => console.log("Successfully reestablished connection."));
|
||||
}, WS_RECONNECT_TIMEOUT);
|
||||
}
|
||||
console.log("Closed websocket connection to rail system " + rs.id);
|
||||
};
|
||||
rs.websocket.onmessage = msg => {
|
||||
console.log(msg);
|
||||
|
@ -25,15 +28,22 @@ export function establishWebsocketConnection(rs) {
|
|||
rs.websocket.onerror = 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.onclose = resolve;
|
||||
rs.websocket.close();
|
||||
rs.websocket = null;
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,41 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<q-list>
|
||||
<q-item-label header>General Settings</q-item-label>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label>Read Only</q-item-label>
|
||||
<q-item-label caption>Freeze this rail system and prevent all updates.</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-toggle v-model="settings.readOnly"/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label>Authentication Required</q-item-label>
|
||||
<q-item-label caption>Require users to login to view and manage the system.</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-toggle v-model="settings.authenticationRequired"/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator/>
|
||||
|
||||
<link-tokens-view :rail-system="railSystem"/>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { RailSystem } from "src/api/railSystems";
|
||||
import LinkTokensView from "components/rs/settings/LinkTokensView.vue";
|
||||
import { useQuasar } from "quasar";
|
||||
import { updateSettings } from "src/api/settings";
|
||||
|
||||
export default {
|
||||
name: "SettingsView",
|
||||
|
@ -16,6 +45,44 @@ export default {
|
|||
type: RailSystem,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const quasar = useQuasar();
|
||||
return {quasar};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
settings: {
|
||||
readOnly: this.railSystem.settings.readOnly,
|
||||
authenticationRequired: this.railSystem.settings.authenticationRequired
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
settings: {
|
||||
handler(newValue, oldValue) {
|
||||
this.update();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
updateSettings(this.railSystem, this.settings)
|
||||
.then(() => {
|
||||
this.quasar.notify({
|
||||
color: "positive",
|
||||
message: "Settings have been updated.",
|
||||
closeBtn: true
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.quasar.notify({
|
||||
color: "negative",
|
||||
message: "Settings could not be updated: " + error.response.data.message
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="token in linkTokens"
|
||||
v-for="token in railSystem.linkTokens"
|
||||
:key="token.id"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>{{token.label}}</q-item-label>
|
||||
<q-item-label caption>{{token.id}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label caption>Components</q-item-label>
|
||||
<q-item-label>
|
||||
<q-chip
|
||||
v-for="component in token.components"
|
||||
:key="component.id"
|
||||
:label="component.name"
|
||||
dense
|
||||
size="sm"
|
||||
/>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-if="linkTokens.length === 0">
|
||||
<q-item v-if="railSystem.linkTokens.length === 0">
|
||||
<q-item-section>
|
||||
<q-item-label caption>There are no link tokens. Add one via the <em>Add Link</em> button below.</q-item-label>
|
||||
</q-item-section>
|
||||
|
@ -91,7 +102,7 @@
|
|||
|
||||
<script>
|
||||
import { RailSystem } from "src/api/railSystems";
|
||||
import { createLinkToken, getLinkTokens } from "src/api/linkTokens";
|
||||
import { createLinkToken } from "src/api/linkTokens";
|
||||
import { useQuasar } from "quasar";
|
||||
|
||||
export default {
|
||||
|
@ -108,7 +119,6 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
linkTokens: [],
|
||||
addTokenDialog: false,
|
||||
addTokenData: {
|
||||
label: "",
|
||||
|
@ -119,12 +129,6 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
refreshLinkTokens() {
|
||||
getLinkTokens(this.railSystem)
|
||||
.then(tokens => {
|
||||
this.linkTokens = tokens;
|
||||
});
|
||||
},
|
||||
getEligibleComponents() {
|
||||
return this.railSystem.components.filter(component => {
|
||||
return component.type === "SIGNAL" || component.type === "SEGMENT_BOUNDARY" || component.type === "SWITCH";
|
||||
|
@ -144,7 +148,7 @@ export default {
|
|||
});
|
||||
},
|
||||
onReset() {
|
||||
this.addTokenData.name = "";
|
||||
this.addTokenData.label = "";
|
||||
this.addTokenData.selectedComponents.length = 0;
|
||||
this.addTokenData.componentIds.length = 0;
|
||||
this.token = null;
|
||||
|
|
|
@ -45,17 +45,19 @@ export default {
|
|||
linkTokens: []
|
||||
}
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
const id = parseInt(to.params.id);
|
||||
const rsStore = useRailSystemsStore();
|
||||
await rsStore.selectRailSystem(id);
|
||||
rsStore.selectRailSystem(id).then(() => {
|
||||
next(vm => vm.railSystem = rsStore.selectedRailSystem);
|
||||
});
|
||||
},
|
||||
async beforeRouteUpdate(to, from) {
|
||||
beforeRouteUpdate(to, from) {
|
||||
const id = parseInt(to.params.id);
|
||||
const rsStore = useRailSystemsStore();
|
||||
await rsStore.selectRailSystem(id);
|
||||
rsStore.selectRailSystem(id).then(() => {
|
||||
this.railSystem = rsStore.selectedRailSystem;
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
if (!rsId) return;
|
||||
return new Promise(resolve => {
|
||||
Promise.all(wsClosePromises).then(() => {
|
||||
refreshRailSystems(this).then(() => {
|
||||
const rs = this.railSystems.find(r => r.id === rsId);
|
||||
await refreshSegments(rs);
|
||||
await refreshComponents(rs);
|
||||
establishWebsocketConnection(rs);
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
|
Loading…
Reference in New Issue