Skip to content

Commit

Permalink
#331 Websocket aware configure page (#332)
Browse files Browse the repository at this point in the history
* #331 Websocket aware configure

* Remove console.log
  • Loading branch information
tariqksoliman authored Feb 14, 2023
1 parent ae72d16 commit 50d46e2
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 7 deletions.
9 changes: 8 additions & 1 deletion API/Backend/Config/routes/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ function upsert(req, res, next, cb, info) {
let hasVersion = false;
req.body = req.body || {};

info = info || {
type: "upsert",
};
info.route = "config";
info.id = req.body.id;
info.mission = req.body.mission;

if (req.body.version != null) hasVersion = true;
let versionConfig = null;

Expand Down Expand Up @@ -697,7 +704,7 @@ function addLayer(req, res, next, cb, forceConfig, caller = "addLayer") {
// user defined UUIDs. We remove the proposed_uuid key after using it to check for unique UUIDs.
Utils.traverseLayers([req.body.layer], (layer) => {
if (layer.uuid != null) {
layer.proposed_uuid = layer.uuid;
layer.proposed_uuid = layer.uuid;
}
});

Expand Down
6 changes: 6 additions & 0 deletions API/Backend/Config/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ let setup = {
user: user,
AUTH: process.env.AUTH,
NODE_ENV: process.env.NODE_ENV,
PORT: process.env.PORT || "8888",
ENABLE_CONFIG_WEBSOCKETS: process.env.ENABLE_CONFIG_WEBSOCKETS,
ROOT_PATH:
process.env.NODE_ENV === "development"
? ""
: process.env.ROOT_PATH || "",
});
}
);
Expand Down
2 changes: 1 addition & 1 deletion config/css/config.css
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ textarea {

#toast-container {
pointer-events: none;
top: 110px !important;
top: 48px !important;
right: 6px !important;
}

Expand Down
65 changes: 63 additions & 2 deletions config/js/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//So that each layer bar will always have a unique id
var grandLayerCounter = 0;
var mission = "";
var configId = -1;
var lockConfig = false;
var lockConfigCount;
//The active mission filepath
var missionPath = "";
var tData;
Expand Down Expand Up @@ -260,6 +263,7 @@ function initialize() {

mission = $(this).find("a").html();
missionPath = calls.missionPath + mission + "/config.json";
configId = parseInt(Math.random() * 100000);

$.ajax({
type: calls.get.type,
Expand All @@ -272,6 +276,8 @@ function initialize() {
if (data.status == "success") {
var cData = data.config;

clearLockConfig();

for (var e in tabEditors) {
tabEditors[e].setValue("");
}
Expand Down Expand Up @@ -2519,12 +2525,27 @@ function addMission() {
}

function saveConfig(json) {
if (lockConfig === true) {
toast(
"error",
`This configuartion changed while you were working on it. Cannot save without refresh or ${lockConfigCount} more attempt${
lockConfigCount != 1 ? "s" : ""
} at saving to force it.`,
5000
);
lockConfigCount--;
if (lockConfigCount <= 0) {
clearLockConfig();
}
return;
}
$.ajax({
type: calls.upsert.type,
url: calls.upsert.url,
data: {
mission: mission,
config: JSON.stringify(json),
id: configId,
},
success: function (data) {
if (data.status == "success") {
Expand Down Expand Up @@ -2928,6 +2949,7 @@ function populateVersions(versions) {
data: {
mission: $(this).attr("mission"),
version: $(this).attr("version"),
id: configId,
},
success: function (data) {
if (data.status == "success") {
Expand Down Expand Up @@ -3046,7 +3068,7 @@ function getDuplicatedNames(json) {
}

let toastId = 0;
function toast(type, message, duration) {
function toast(type, message, duration, className) {
let color = "#000000";
switch (type) {
case "success":
Expand All @@ -3062,8 +3084,47 @@ function toast(type, message, duration) {
return;
}
const id = `toast_${type}_${toastId}`;
Materialize.toast(`<span id='${id}'>${message}</span>`, duration || 4000);
Materialize.toast(
`<span id='${id}'${
className != null ? ` class='${className}'` : ""
}>${message}</span>`,
duration || 4000
);
$(`#${id}`).parent().css("background-color", color);

toastId++;
}

const lockConfigTypes = {
main: null,
disconnect: null,
};
function setLockConfig(type) {
clearLockConfig(type);
lockConfig = true;
lockConfigTypes[type || "main"] = false;
lockConfigCount = 4;

toast(
"warning",
type === "disconnect"
? "Websocket disconnected. You will not be able to save until it reconnects."
: "This configuration changed while you were working on it. You must refresh.",
100000000000,
"lockConfigToast"
);
}
function clearLockConfig(type) {
lockConfigTypes[type || "main"] = false;

let canUnlock = true;
Object.keys(lockConfigTypes).forEach((k) => {
if (lockConfigTypes[k] === true) canUnlock = false;
});
if (canUnlock) {
lockConfig = false;
document
.querySelectorAll(".lockConfigToast")
.forEach((el) => el.parentNode.remove());
}
}
61 changes: 61 additions & 0 deletions config/js/websocket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const Websocket = {
initialWebSocketRetryInterval: 60000, // 1 minute
webSocketRetryInterval: 60000, // Start with this time and double if disconnected
webSocketPingInterval: null,
init: function () {
if (typeof window.setLockConfig === "function")
window.clearLockConfig("disconnect");

const port = parseInt(window.mmgisglobal.PORT || "8888", 10);
const protocol =
window.location.protocol.indexOf("https") !== -1 ? "wss" : "ws";

const path =
window.mmgisglobal.NODE_ENV === "development"
? `${protocol}://localhost:${port}${window.mmgisglobal.ROOT_PATH}/`
: `${protocol}://${window.location.host}${window.mmgisglobal.ROOT_PATH}/`;

// Create WebSocket connection.
const socket = new WebSocket(path);

// Connection opened
socket.addEventListener("open", (event) => {
Websocket.webSocketRetryInterval =
Websocket.initialWebSocketRetryInterval;
clearInterval(Websocket.webSocketPingInterval);
});

// Listen for messages
socket.addEventListener("message", (event) => {
try {
const data = JSON.parse(event.data);
if (
data?.info?.route === "config" &&
parseInt(data?.info?.id || -1) !== window.configId &&
data?.info?.mission === window.mission
) {
if (typeof window.setLockConfig === "function")
window.setLockConfig();
}
} catch (err) {}
});

socket.addEventListener("close", (event) => {
if (typeof window.setLockConfig === "function")
window.setLockConfig("disconnect");

clearInterval(Websocket.webSocketPingInterval);
Websocket.webSocketPingInterval = setInterval(
Websocket.init,
Websocket.webSocketRetryInterval
); // 1 minute
Websocket.webSocketRetryInterval *= 2;
});
},
};

if (
mmgisglobal.ENABLE_CONFIG_WEBSOCKETS === "true" ||
mmgisglobal.ENABLE_CONFIG_WEBSOCKETS === true
)
Websocket.init();
4 changes: 4 additions & 0 deletions docs/pages/Setup/ENVs/ENVs.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,7 @@ LDAP group of leads (users with elevated permissions) | string | default `''`
#### `ENABLE_MMGIS_WEBSOCKETS=`

If true, enables the backend MMGIS websockets to tell clients to update layers | boolean | default `false`

#### `ENABLE_CONFIG_WEBSOCKETS=`

If true, notifications are sent to /configure users whenever the current mission's configuration object changes out from under them and thne puts (overridable) limits on saving | boolean | default `false`
2 changes: 2 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ LEADS=["user1"]

# If true, enables the backend MMGIS websockets to tell clients to update layers
ENABLE_MMGIS_WEBSOCKETS=false
# If true, notifications are sent to /configure users whenever the configuration objects changes out from under them and puts (overridable) limits on saving.
ENABLE_CONFIG_WEBSOCKETS=false
14 changes: 11 additions & 3 deletions src/essence/essence.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ var essence = {
)
}
)
} else {
if (parsed.body && parsed.body.config) {
UserInterface_.updateLayerUpdateButton('RELOAD')
}
}
} else {
if (parsed.body && parsed.body.config) {
Expand Down Expand Up @@ -370,13 +374,17 @@ var essence = {
window.mmgisglobal.PORT &&
window.mmgisglobal.ENABLE_MMGIS_WEBSOCKETS === 'true'
) {
const port = parseInt(process.env.PORT || '8888', 10)
const port = parseInt(window.mmgisglobal.PORT || '8888', 10)
const protocol =
window.location.protocol.indexOf('https') !== -1 ? 'wss' : 'ws'
const path =
window.mmgisglobal.NODE_ENV === 'development'
? `${protocol}://localhost:${port}/`
: `${protocol}://${window.location.host}/`
? `${protocol}://localhost:${port}${
window.mmgisglobal.ROOT_PATH || ''
}/`
: `${protocol}://${window.location.host}${
window.mmgisglobal.ROOT_PATH || ''
}/`

essence.connectWebSocket(path, true)
essence.webSocketPingInterval = setInterval(
Expand Down
4 changes: 4 additions & 0 deletions views/configure.pug
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ script.
mmgisglobal.SHOW_AUTH_TIMEOUT = true;
mmgisglobal.user = '#{user}';
mmgisglobal.NODE_ENV = '#{NODE_ENV}';
mmgisglobal.ROOT_PATH = '#{ROOT_PATH}';
mmgisglobal.PORT = '#{PORT}'
mmgisglobal.ENABLE_CONFIG_WEBSOCKETS = '#{ENABLE_CONFIG_WEBSOCKETS}'
script(type='text/javascript' src='config/js/calls.js')
script(type='text/javascript' src='config/js/keys.js')
script(type='text/javascript' src='config/js/datasets.js')
script(type='text/javascript' src='config/js/geodatasets.js')
script(type='text/javascript' src='config/js/webhooks.js')
script(type='text/javascript' src='config/js/config.js')
script(type='text/javascript' src='config/js/websocket.js')
script(type='text/javascript' src='config/pre/RefreshAuth.js')

#leftPanel
Expand Down

0 comments on commit 50d46e2

Please sign in to comment.