Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#331 Websocket aware configure page #332

Merged
merged 2 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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