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
|
* @param {RailSystem} rs
|
||||||
* @return {Promise<LinkToken[]>}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
export function getLinkTokens(rs) {
|
export function refreshLinkTokens(rs) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.get(`${API_URL}/rs/${rs.id}/lt`)
|
axios.get(`${API_URL}/rs/${rs.id}/lt`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
resolve(response.data.map(obj => new LinkToken(obj)));
|
rs.linkTokens = response.data;
|
||||||
})
|
resolve();
|
||||||
.catch(reject);
|
})
|
||||||
});
|
.catch(reject);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new link token.
|
* Creates a new link token.
|
||||||
* @param {RailSystem} rs
|
* @param {RailSystem} rs
|
||||||
|
@ -34,13 +36,14 @@ export function getLinkTokens(rs) {
|
||||||
* @return {Promise<string>} A promise that resolves to the token that was created.
|
* @return {Promise<string>} A promise that resolves to the token that was created.
|
||||||
*/
|
*/
|
||||||
export function createLinkToken(rs, data) {
|
export function createLinkToken(rs, data) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.post(`${API_URL}/rs/${rs.id}/lt`, data)
|
axios.post(`${API_URL}/rs/${rs.id}/lt`, data)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
resolve(response.data.token);
|
const token = response.data.token;
|
||||||
})
|
refreshLinkTokens(rs).then(() => resolve(token)).catch(reject);
|
||||||
.catch(reject);
|
})
|
||||||
});
|
.catch(reject);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,7 +54,9 @@ export function createLinkToken(rs, data) {
|
||||||
export function deleteToken(rs, tokenId) {
|
export function deleteToken(rs, tokenId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.delete(`${API_URL}/rs/${rs.id}/lt/${tokenId}`)
|
axios.delete(`${API_URL}/rs/${rs.id}/lt/${tokenId}`)
|
||||||
.then(resolve)
|
.then(() => {
|
||||||
|
refreshLinkTokens(rs).then(resolve).catch(reject);
|
||||||
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,12 @@ export class RailSystem {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
this.name = data.name;
|
this.name = data.name;
|
||||||
|
this.settings = null;
|
||||||
|
|
||||||
this.segments = [];
|
this.segments = [];
|
||||||
this.components = [];
|
this.components = [];
|
||||||
|
this.linkTokens = [];
|
||||||
|
|
||||||
this.websocket = null;
|
this.websocket = null;
|
||||||
this.selectedComponents = [];
|
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,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.
|
* Establishes a websocket connection to the given rail system.
|
||||||
* @param {RailSystem} rs
|
* @param {RailSystem} rs
|
||||||
|
* @return {Promise} A promise that resolves when a connection is established.
|
||||||
*/
|
*/
|
||||||
export function establishWebsocketConnection(rs) {
|
export function establishWebsocketConnection(rs) {
|
||||||
closeWebsocketConnection(rs);
|
return new Promise(resolve => {
|
||||||
rs.websocket = new WebSocket(`${WS_URL}/${rs.id}`);
|
rs.websocket = new WebSocket(`${WS_URL}/${rs.id}`);
|
||||||
rs.websocket.onopen = () => {
|
rs.websocket.onopen = resolve;
|
||||||
console.log("Opened websocket connection to rail system " + rs.id);
|
|
||||||
};
|
|
||||||
rs.websocket.onclose = event => {
|
rs.websocket.onclose = event => {
|
||||||
if (event.code !== 1000) {
|
if (event.code === 1000) {
|
||||||
console.warn("Lost websocket connection. Attempting to reestablish.");
|
console.log(`Closed websocket connection to rail system "${rs.name}" (${rs.id})`);
|
||||||
setTimeout(() => {
|
} else {
|
||||||
establishWebsocketConnection(rs);
|
console.warn(`Unexpectedly lost websocket connection to rail system "${rs.name}" (${rs.id}). Attempting to reestablish in ${WS_RECONNECT_TIMEOUT} ms.`);
|
||||||
}, 3000);
|
setTimeout(() => {
|
||||||
}
|
establishWebsocketConnection(rs)
|
||||||
console.log("Closed websocket connection to rail system " + rs.id);
|
.then(() => console.log("Successfully reestablished connection."));
|
||||||
|
}, WS_RECONNECT_TIMEOUT);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
rs.websocket.onmessage = msg => {
|
rs.websocket.onmessage = msg => {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
};
|
};
|
||||||
rs.websocket.onerror = error => {
|
rs.websocket.onerror = error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
};
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the websocket connection to a rail system, if possible.
|
* Closes the websocket connection to a rail system, if possible.
|
||||||
* @param {RailSystem} rs
|
* @param {RailSystem} rs
|
||||||
|
* @return {Promise} A promise that resolves when the connection is closed.
|
||||||
*/
|
*/
|
||||||
export function closeWebsocketConnection(rs) {
|
export function closeWebsocketConnection(rs) {
|
||||||
|
return new Promise(resolve => {
|
||||||
if (rs.websocket) {
|
if (rs.websocket) {
|
||||||
rs.websocket.close();
|
rs.websocket.onclose = resolve;
|
||||||
rs.websocket = null;
|
rs.websocket.close();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,41 @@
|
||||||
<template>
|
<template>
|
||||||
<q-list>
|
<div class="row">
|
||||||
<link-tokens-view :rail-system="railSystem"/>
|
<div class="col-sm-4">
|
||||||
</q-list>
|
<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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { RailSystem } from "src/api/railSystems";
|
import { RailSystem } from "src/api/railSystems";
|
||||||
import LinkTokensView from "components/rs/settings/LinkTokensView.vue";
|
import LinkTokensView from "components/rs/settings/LinkTokensView.vue";
|
||||||
|
import { useQuasar } from "quasar";
|
||||||
|
import { updateSettings } from "src/api/settings";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SettingsView",
|
name: "SettingsView",
|
||||||
|
@ -16,6 +45,44 @@ export default {
|
||||||
type: RailSystem,
|
type: RailSystem,
|
||||||
required: true
|
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>
|
</script>
|
||||||
|
|
|
@ -3,21 +3,32 @@
|
||||||
expand-separator
|
expand-separator
|
||||||
label="Component Links"
|
label="Component Links"
|
||||||
caption="Link components to your system."
|
caption="Link components to your system."
|
||||||
@before-show="refreshLinkTokens"
|
|
||||||
switch-toggle-side
|
switch-toggle-side
|
||||||
:content-inset-level="0.5"
|
:content-inset-level="0.5"
|
||||||
>
|
>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="token in linkTokens"
|
v-for="token in railSystem.linkTokens"
|
||||||
:key="token.id"
|
:key="token.id"
|
||||||
>
|
>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{token.label}}</q-item-label>
|
<q-item-label>{{token.label}}</q-item-label>
|
||||||
<q-item-label caption>{{token.id}}</q-item-label>
|
<q-item-label caption>{{token.id}}</q-item-label>
|
||||||
</q-item-section>
|
</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>
|
||||||
<q-item v-if="linkTokens.length === 0">
|
<q-item v-if="railSystem.linkTokens.length === 0">
|
||||||
<q-item-section>
|
<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-label caption>There are no link tokens. Add one via the <em>Add Link</em> button below.</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
@ -91,7 +102,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { RailSystem } from "src/api/railSystems";
|
import { RailSystem } from "src/api/railSystems";
|
||||||
import { createLinkToken, getLinkTokens } from "src/api/linkTokens";
|
import { createLinkToken } from "src/api/linkTokens";
|
||||||
import { useQuasar } from "quasar";
|
import { useQuasar } from "quasar";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -108,7 +119,6 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
linkTokens: [],
|
|
||||||
addTokenDialog: false,
|
addTokenDialog: false,
|
||||||
addTokenData: {
|
addTokenData: {
|
||||||
label: "",
|
label: "",
|
||||||
|
@ -119,12 +129,6 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
refreshLinkTokens() {
|
|
||||||
getLinkTokens(this.railSystem)
|
|
||||||
.then(tokens => {
|
|
||||||
this.linkTokens = tokens;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getEligibleComponents() {
|
getEligibleComponents() {
|
||||||
return this.railSystem.components.filter(component => {
|
return this.railSystem.components.filter(component => {
|
||||||
return component.type === "SIGNAL" || component.type === "SEGMENT_BOUNDARY" || component.type === "SWITCH";
|
return component.type === "SIGNAL" || component.type === "SEGMENT_BOUNDARY" || component.type === "SWITCH";
|
||||||
|
@ -144,7 +148,7 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onReset() {
|
onReset() {
|
||||||
this.addTokenData.name = "";
|
this.addTokenData.label = "";
|
||||||
this.addTokenData.selectedComponents.length = 0;
|
this.addTokenData.selectedComponents.length = 0;
|
||||||
this.addTokenData.componentIds.length = 0;
|
this.addTokenData.componentIds.length = 0;
|
||||||
this.token = null;
|
this.token = null;
|
||||||
|
|
|
@ -45,17 +45,19 @@ export default {
|
||||||
linkTokens: []
|
linkTokens: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
const id = parseInt(to.params.id);
|
const id = parseInt(to.params.id);
|
||||||
const rsStore = useRailSystemsStore();
|
const rsStore = useRailSystemsStore();
|
||||||
await rsStore.selectRailSystem(id);
|
rsStore.selectRailSystem(id).then(() => {
|
||||||
next(vm => vm.railSystem = rsStore.selectedRailSystem);
|
next(vm => vm.railSystem = rsStore.selectedRailSystem);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async beforeRouteUpdate(to, from) {
|
beforeRouteUpdate(to, from) {
|
||||||
const id = parseInt(to.params.id);
|
const id = parseInt(to.params.id);
|
||||||
const rsStore = useRailSystemsStore();
|
const rsStore = useRailSystemsStore();
|
||||||
await rsStore.selectRailSystem(id);
|
rsStore.selectRailSystem(id).then(() => {
|
||||||
this.railSystem = rsStore.selectedRailSystem;
|
this.railSystem = rsStore.selectedRailSystem;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import {defineStore} from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import {refreshSegments} from "../api/segments"
|
import { refreshSegments } from "../api/segments";
|
||||||
import {refreshComponents} from "../api/components";
|
import { refreshComponents } from "../api/components";
|
||||||
import {closeWebsocketConnection, establishWebsocketConnection} from "../api/websocket";
|
import { closeWebsocketConnection, establishWebsocketConnection } from "../api/websocket";
|
||||||
import { refreshRailSystems } from "src/api/railSystems";
|
import { refreshRailSystems } from "src/api/railSystems";
|
||||||
|
import { refreshLinkTokens } from "src/api/linkTokens";
|
||||||
|
import { refreshSettings } from "src/api/settings";
|
||||||
|
|
||||||
export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
@ -13,26 +15,38 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||||
/**
|
/**
|
||||||
* @type {RailSystem | null}
|
* @type {RailSystem | null}
|
||||||
*/
|
*/
|
||||||
selectedRailSystem: null,
|
selectedRailSystem: null
|
||||||
|
|
||||||
loaded: false
|
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async selectRailSystem(rsId) {
|
/**
|
||||||
if (!this.loaded) {
|
* Updates the selected rail system.
|
||||||
await refreshRailSystems(this);
|
* @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 => {
|
return new Promise(resolve => {
|
||||||
r.components.length = 0;
|
Promise.all(wsClosePromises).then(() => {
|
||||||
r.segments.length = 0;
|
refreshRailSystems(this).then(() => {
|
||||||
closeWebsocketConnection(r);
|
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: {
|
getters: {
|
||||||
|
|
Loading…
Reference in New Issue