From cae20fa89b395290ec8b47796da4775524845b1c Mon Sep 17 00:00:00 2001 From: Aaron LI Date: Mon, 7 Nov 2016 15:57:35 +0800 Subject: webui: Implement reset form and load configuration files functions * Rewrite "configs.js" to be more modular and generic * Bind functions to button click event * Implement the function to set form fields to given configuration data * Implement the function to reset server-side configurations * Implement the function to load user configuration file on server * Implement get the configuration data from the server --- fg21sim/webui/static/js/configs.js | 151 +++++++++++++++++++++++++++++++---- fg21sim/webui/static/js/websocket.js | 84 ++++++++++++++----- fg21sim/webui/templates/configs.html | 62 +++++++------- fg21sim/webui/templates/index.html | 1 + 4 files changed, 238 insertions(+), 60 deletions(-) (limited to 'fg21sim/webui') diff --git a/fg21sim/webui/static/js/configs.js b/fg21sim/webui/static/js/configs.js index 8a43a4a..15d4641 100644 --- a/fg21sim/webui/static/js/configs.js +++ b/fg21sim/webui/static/js/configs.js @@ -9,14 +9,49 @@ "use strict"; +/** + * Generic utilities + */ + +/** + * Get the basename of a path + * FIXME: only support "/" as the path separator + */ +var basename = function (path) { + return path.replace(/^.*\//, ""); +}; + +/** + * Get the dirname of a path + * FIXME: only support "/" as the path separator + */ +var dirname = function (path) { + var dir = path.replace(/\/[^\/]*\/?$/, ""); + if (dir === "") { + dir = "/"; + } + return dir; +}; + +/** + * Join the two path + * FIXME: only support "/" as the path separator + */ +var joinPath = function (path1, path2) { + // Strip the trailing path separator + path1 = path1.replace(/\/$/, ""); + return (path1 + "/" + path2); +}; + + + /** * Clear the error states previously marked on the fields with invalid values. */ var clearConfigFormErrors = function () { // TODO - $("#conf-form").find(":input").each(function () { - $(this); + // TODO }); }; @@ -44,6 +79,81 @@ var resetConfigForm = function () { var setConfigForm = function (data, errors) { // Clear previously marked errors clearConfigFormErrors(); + + // Set the values of form field to the input configurations data + for (var key in data) { + if (! data.hasOwnProperty(key)) { + /** + * NOTE: Skip if the property is from prototype + * Credit: http://stackoverflow.com/a/921808 + */ + continue; + } + var value = data[key]; + if (key === "userconfig" && value) { + // Split the absolute path to "workdir" and "configfile" + var workdir = dirname(value); + var configfile = basename(value); + $("input[name=workdir]").val(workdir).trigger("change"); + $("input[name=configfile]").val(configfile).trigger("change"); + } + else { + var selector = "input[name='" + key + "']"; + var target = $(selector); + if (target.length) { + if (target.is(":radio")) { + var val_old = target.filter(":checked").val(); + target.val([value]).trigger("change"); // Use Array in "val()" + } else if (target.is(":checkbox")) { + // Get values of checked checkboxes into array + // Credit: https://stackoverflow.com/a/16171146/4856091 + var val_old = target.filter(":checked").map( + function () { return $(this).val(); }).get(); + // Convert value to an Array + if (! Array.isArray(value)) { + value = [value]; + } + target.val(value).trigger("change"); + } else if (target.is(":text") && target.data("type") == "array") { + // This field is a string that is ", "-joined from an Array + var val_old = target.val(); + // The received value is already an Array + value = value.join(", "); + target.val(value).trigger("change"); + } else { + var val_old = target.val(); + target.val(value).trigger("change"); + } + console.debug("Set input '" + key + "' to:", value, " <-", val_old); + } + else { + console.error("No such element:", selector); + } + } + } + + // Mark error states on fields with invalid values + for (var key in errors) { + if (! errors.hasOwnProperty(key)) { + // NOTE: Skip if the property is from prototype + continue; + } + var value = errors[key]; + // TODO: mark the error states + } +}; + + +/** + * Get the filepath to the user configuration file from the form fields + * "workdir" and "configfile". + * + * @returns {String} - Absolute path to the user configuration file. + */ +var getFormUserconfig = function () { + var userconfig = joinPath($("input[name=workdir]").val(), + $("input[name=configfile]").val()); + return userconfig; }; @@ -95,13 +205,16 @@ var setServerConfigs = function (ws, data) { * Request the server side configurations with user configuration file merged. * When the response arrived, the bound function will delegate an appropriate * function (i.e., `setConfigForm()`) to update the form contents. + * + * @param {Object} userconfig - Absolute path to the user config file on the + * server. If not specified, then determine from + * the form fields "workdir" and "configfile". */ -var loadServerConfigFile = function (ws) { - var workdir = $("input[name=workdir]").val().replace(/[\\\/]$/, ""); - var configfile = $("input[name=configfile]").val().replace(/^.*[\\\/]/, ""); - // FIXME: should use the native system path separator! - var filepath = workdir + "/" + configfile; - var msg = {type: "configs", action: "load", userconfig: filepath}; +var loadServerConfigFile = function (ws, userconfig) { + if (typeof userconfig === "undefined") { + userconfig = getFormUserconfig(); + } + var msg = {type: "configs", action: "load", userconfig: userconfig}; ws.send(JSON.stringify(msg)); }; @@ -115,13 +228,23 @@ var loadServerConfigFile = function (ws) { */ var saveServerConfigFile = function (ws, clobber) { clobber = typeof clobber !== "undefined" ? clobber : false; - var workdir = $("input[name=workdir]").val().replace(/[\\\/]$/, ""); - var configfile = $("input[name=configfile]").val().replace(/^.*[\\\/]/, ""); - // FIXME: should use the native system path separator! - var filepath = workdir + "/" + configfile; + var userconfig = getFormUserconfig(); var msg = {type: "configs", - action: "load", - outfile: filepath, + action: "save", + outfile: userconfig, clobber: clobber}; ws.send(JSON.stringify(msg)); }; + + +/** + * Handle the received message of type "configs".replace + */ +var handleMsgConfigs = function (msg) { + if (msg.success) { + setConfigForm(msg.data, msg.errors); + } else { + console.error("WebSocket 'configs' request failed with error:", msg.error); + // TODO: add error code support and handle each specific error ... + } +}; diff --git a/fg21sim/webui/static/js/websocket.js b/fg21sim/webui/static/js/websocket.js index ae44410..07de907 100644 --- a/fg21sim/webui/static/js/websocket.js +++ b/fg21sim/webui/static/js/websocket.js @@ -11,13 +11,9 @@ * Global variable * FIXME: try to avoid this ... */ -var ws = null; /* WebSocket */ +var g_ws = null; /* WebSocket */ /* WebSocket reconnection settings */ -var ws_reconnect = { - maxTry: 100, - tried: 0, - timeout: 3000, /* ms */ -}; +var g_ws_reconnect = {maxTry: 100, tried: 0, timeout: 3000}; /** @@ -103,36 +99,57 @@ var toggleWSReconnect = function (action) { * Connect to WebSocket and bind functions to events */ var connectWebSocket = function (url) { - ws = new WebSocket(url); - ws.onopen = function () { - console.log("Opened WebSocket:", ws.url); + g_ws = new WebSocket(url); + g_ws.onopen = function () { + console.log("Opened WebSocket:", g_ws.url); updateWSStatus("open"); toggleWSReconnect("hide"); }; - ws.onclose = function (e) { + g_ws.onclose = function (e) { console.log("WebSocket closed: code:", e.code, ", reason:", e.reason); updateWSStatus("close"); // Reconnect - if (ws_reconnect.tried < ws_reconnect.maxTry) { - ws_reconnect.tried++; - console.log("Try reconnect the WebSocket: No." + ws_reconnect.tried); + if (g_ws_reconnect.tried < g_ws_reconnect.maxTry) { + g_ws_reconnect.tried++; + console.log("Try reconnect the WebSocket: No." + g_ws_reconnect.tried); setTimeout(function () { connectWebSocket(url); }, - ws_reconnect.timeout); + g_ws_reconnect.timeout); } else { console.error("WebSocket already tried allowed maximum times:", - ws_reconnect.maxTry); + g_ws_reconnect.maxTry); toggleWSReconnect("show"); } }; - ws.onerror = function (e) { + g_ws.onerror = function (e) { console.error("WebSocket encountered error:", e.message); updateWSStatus("error"); toggleWSReconnect("show"); }; - ws.onmessage = function (e) { + g_ws.onmessage = function (e) { var msg = JSON.parse(e.data); console.log("WebSocket received message: type:", msg.type, - ", status:", msg.status); + ", success:", msg.success); + console.debug(msg); + // Delegate appropriate actions to handle the received message + if (msg.type === "configs") { + handleMsgConfigs(msg); + } + else if (msg.type === "console") { + console.error("NotImplementedError"); + // handleMsgConsole(msg); + } + else if (msg.type === "results") { + console.error("NotImplementedError"); + // handleMsgResults(msg); + } + else { + // Unknown/unsupported message type + console.error("WebSocket received message of unknown type:", msg.type); + if (! msg.success) { + console.error("WebSocket request failed with error:", msg.error); + // TODO: add error codes support and handle each specific error + } + } }; }; @@ -148,7 +165,7 @@ $(document).ready(function () { var ws_url = getWebSocketURL("/ws"); connectWebSocket(ws_url); - // Bind event to the "#ws-reconnect" button + // Manually reconnect the WebSocket after tried allowed maximum times $("#ws-reconnect").on("click", function () { console.log("WebSocket: reset the tried reconnection counter"); ws_reconnect.tried = 0; @@ -156,6 +173,35 @@ $(document).ready(function () { connectWebSocket(ws_url); }); + // Reset the configurations to the defaults + $("#reset-defaults").on("click", function () { + // TODO: + // * add a confirmation dialog; + // * add pop up to indicate success/fail + resetConfigForm(g_ws); + resetServerConfigs(g_ws); + getServerConfigs(g_ws); + }); + + // Load the configurations from the specified user configuration file + $("#load-configfile").on("click", function () { + // TODO: + // * add pop up to indicate success/fail + var userconfig = getFormUserconfig(); + resetConfigForm(g_ws); + loadServerConfigFile(g_ws, userconfig); + getServerConfigs(g_ws); + }); + + // Save the current configurations to file + $("#save-configfile").on("click", function () { + // TODO: + // * validate the whole configurations before save + // * add a confirmation on overwrite + // * add pop up to indicate success/fail + saveServerConfigFile(g_ws, true); // clobber=true + }); + } else { // WebSocket NOT supported console.error("Oops, WebSocket is NOT supported!"); diff --git a/fg21sim/webui/templates/configs.html b/fg21sim/webui/templates/configs.html index b86a72c..9321d9d 100644 --- a/fg21sim/webui/templates/configs.html +++ b/fg21sim/webui/templates/configs.html @@ -15,20 +15,20 @@
- +
- +
- - - + + +
@@ -39,15 +39,15 @@
- +
- +
- +
@@ -79,17 +79,21 @@

Frequency


-
- - -
+
+ + +
+ + +
+
- +
@@ -101,7 +105,7 @@
- +
@@ -129,17 +133,21 @@

Output


-
- - -
+
+ + +
+ + +
+
- +
@@ -152,7 +160,7 @@
- +
@@ -217,7 +225,7 @@
- +
@@ -253,7 +261,7 @@
- +
@@ -290,13 +298,13 @@
- +
- +
@@ -339,7 +347,7 @@
- +
@@ -378,7 +386,7 @@
- +
@@ -429,7 +437,7 @@
- +
diff --git a/fg21sim/webui/templates/index.html b/fg21sim/webui/templates/index.html index 8eb65cd..4c1dff1 100644 --- a/fg21sim/webui/templates/index.html +++ b/fg21sim/webui/templates/index.html @@ -30,5 +30,6 @@ {% end %} {% block extra_script %} + {% end %} -- cgit v1.2.2