aboutsummaryrefslogtreecommitdiffstats
path: root/fg21sim/webui
diff options
context:
space:
mode:
authorAaron LI <aaronly.me@outlook.com>2016-11-07 15:57:35 +0800
committerAaron LI <aaronly.me@outlook.com>2016-11-07 15:57:35 +0800
commitcae20fa89b395290ec8b47796da4775524845b1c (patch)
tree388a8be88d4fdb6c3ad1ddf2b29a0abd2d6b1b3d /fg21sim/webui
parent2d0fa905d69bc8177d2253308cf67c6950f404af (diff)
downloadfg21sim-cae20fa89b395290ec8b47796da4775524845b1c.tar.bz2
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
Diffstat (limited to 'fg21sim/webui')
-rw-r--r--fg21sim/webui/static/js/configs.js151
-rw-r--r--fg21sim/webui/static/js/websocket.js84
-rw-r--r--fg21sim/webui/templates/configs.html62
-rw-r--r--fg21sim/webui/templates/index.html1
4 files changed, 238 insertions, 60 deletions
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
@@ -10,13 +10,48 @@
/**
+ * 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 @@
<div class="row">
<div class="column column-50 form-group">
<label for="conf-workdir">Working Directory:</label>
- <input class="form-control" type="text" id="conf-workdir" name="workdir" />
+ <input class="form-control" type="text" id="conf-workdir" name="workdir" autocomplete />
</div>
<div class="column column-40 form-group">
<label for="conf-configfile">Configuration File:</label>
- <input class="form-control" type="text" id="conf-configfile" name="configfile" />
+ <input class="form-control" type="text" id="conf-configfile" name="configfile" autocomplete />
</div>
</div>
<div class="button-group">
<!-- NOTE: HTML5 "button" element has a default behavior of submit.
Credit: https://stackoverflow.com/a/10836076/4856091
-->
- <button type="button" id="load-configfile"><span class="fa fa-download" aria-hidden="true"></span> Load from Configuration File</button>
- <button type="button" id="save-configfile"><span class="fa fa-save" aria-hidden="true"></span> Save to Configuration File</button>
- <button type="button" class="button-warning" id="reset-defaults" disabled="disabled"><span class="fa fa-undo" aria-hidden="true"></span> Reset to Defaults</button>
+ <button type="button" id="load-configfile"><span class="fa fa-download" aria-hidden="true"></span> Load Configurations</button>
+ <button type="button" id="save-configfile"><span class="fa fa-save" aria-hidden="true"></span> Save Configurations</button>
+ <button type="button" class="button-warning" id="reset-defaults"><span class="fa fa-undo" aria-hidden="true"></span> Reset to Defaults</button>
</div>
</fieldset>
@@ -39,15 +39,15 @@
<div class="row">
<div class="column column-30 form-group">
<label for="conf-common-nside"><i>N</i><sub>side</sub>:</label>
- <input class="form-control" type="number" id="conf-common-nside" name="common/nside" />
+ <input class="form-control" type="number" id="conf-common-nside" name="common/nside" min="1" />
</div>
<div class="column column-30 form-group">
<label for="conf-common-lmin"><i>l</i><sub>min</sub>:</label>
- <input class="form-control" type="number" id="conf-common-lmin" name="common/lmin" />
+ <input class="form-control" type="number" id="conf-common-lmin" name="common/lmin" min="0" />
</div>
<div class="column column-30 form-group">
<label for="conf-common-lmax"><i>l</i><sub>max</sub>:</label>
- <input class="form-control" type="number" id="conf-common-lmax" name="common/lmax" />
+ <input class="form-control" type="number" id="conf-common-lmax" name="common/lmax" min="1" />
</div>
</div>
<div class="row">
@@ -79,17 +79,21 @@
<h4><span class="fa fa-asterisk" aria-hidden="true"></span> Frequency</h4>
<hr class="hr-thin hr-condensed hr-dashed" />
<div class="row">
- <div class="column column-30 form-group">
- <label for="conf-frequency-unit">Unit:</label>
- <input class="form-control" type="text" id="conf-frequency-unit" name="frequency/unit" />
- </div>
+ <fieldset id="conf-frequency-unit" class="column radios">
+ <!-- XXX: cannot inline if use "legend" tag -->
+ <label class="legend">Unit:</label>
+ <div class="form-group">
+ <input class="form-control" type="radio" id="conf-frequency-unit-mhz" name="frequency/unit" value="MHz" checked />
+ <label for="conf-frequency-unit-mhz">MHz</label>
+ </div>
+ </fieldset>
</div>
<div class="row">
<fieldset id="conf-frequency-type" class="column radios">
<!-- XXX: cannot inline if use "legend" tag -->
<label class="legend">Type:</label>
<div class="form-group">
- <input class="form-control" type="radio" id="conf-frequency-type-custom" name="frequency/type" value="custom" />
+ <input class="form-control" type="radio" id="conf-frequency-type-custom" name="frequency/type" value="custom" checked />
<label for="conf-frequency-type-custom">custom</label>
</div>
<div class="form-group">
@@ -101,7 +105,7 @@
<div class="row">
<div class="column column-60 form-group">
<label for="conf-frequency-frequencies">Custom Frequencies:</label>
- <input class="form-control" type="text" id="conf-frequency-frequencies" name="frequency/frequencies" placeholder="comma-separated list of frequencies" />
+ <input class="form-control" type="text" id="conf-frequency-frequencies" name="frequency/frequencies" placeholder="comma-separated list of frequencies" data-type="array" />
</div>
</div>
<div class="row">
@@ -129,17 +133,21 @@
<h4><span class="fa fa-asterisk" aria-hidden="true"></span> Output</h4>
<hr class="hr-thin hr-condensed hr-dashed" />
<div class="row">
- <div class="column column-30 form-group">
- <label for="conf-output-unit">Unit:</label>
- <input class="form-control" type="text" id="conf-output-unit" name="output/unit" />
- </div>
+ <fieldset id="conf-output-unit" class="column radios">
+ <!-- XXX: cannot inline if use "legend" tag -->
+ <label class="legend">Unit:</label>
+ <div class="form-group">
+ <input class="form-control" type="radio" id="conf-output-unit-k" name="output/unit" value="K" checked />
+ <label for="conf-output-unit-k">K</label>
+ </div>
+ </fieldset>
</div>
<div class="row">
<fieldset id="conf-output-filetype" class="column radios">
<!-- XXX: cannot inline if use "legend" tag -->
<label class="legend">File Type:</label>
<div class="form-group">
- <input class="form-control" type="radio" id="conf-output-filetype-fits" name="output/filetype" value="fits" checked="checked" />
+ <input class="form-control" type="radio" id="conf-output-filetype-fits" name="output/filetype" value="fits" checked />
<label for="conf-output-filetype-fits">FITS</label>
</div>
</fieldset>
@@ -152,7 +160,7 @@
</div>
<div class="row">
<div class="column form-group">
- <input class="form-control" type="checkbox" id="conf-output-use-float" name="output/use_float" value="true" checked="checked" />
+ <input class="form-control" type="checkbox" id="conf-output-use-float" name="output/use_float" value="true" checked />
<label for="conf-output-use-float">Use single-precision float instead of double</label>
</div>
</div>
@@ -217,7 +225,7 @@
<label for="conf-logging-level-debug">debug</label>
</div>
<div class="form-group">
- <input class="form-control" type="radio" id="conf-logging-level-info" name="logging/level" value="INFO" checked="checked" />
+ <input class="form-control" type="radio" id="conf-logging-level-info" name="logging/level" value="INFO" checked />
<label for="conf-logging-level-info">info</label>
</div>
<div class="form-group">
@@ -253,7 +261,7 @@
<input class="form-control" type="text" id="conf-logging-logfile" name="logging/filename" />
</div>
<div class="form-group">
- <input class="form-control" type="checkbox" id="conf-logging-logfile-append" name="logging/filemode" value="a" checked="checked" />
+ <input class="form-control" type="checkbox" id="conf-logging-logfile-append" name="logging/appendmode" value="true" checked />
<label for="conf-logging-logfile-append">Append mode</label>
</div>
</div>
@@ -290,13 +298,13 @@
</div>
<div class="row">
<div class="column form-group">
- <input class="form-control" type="checkbox" id="conf-g-synchrotron-smallscales" name="galactic/synchrotron/add_smallscales" value="true" checked="checked" />
+ <input class="form-control" type="checkbox" id="conf-g-synchrotron-smallscales" name="galactic/synchrotron/add_smallscales" value="true" checked />
<label for="conf-g-synchrotron-smallscales">Add fluctuations on the small scales based on the angular power spectrum</label>
</div>
</div>
<div class="row">
<div class="column form-group">
- <input class="form-control" type="checkbox" id="conf-g-synchrotron-save" name="galactic/synchrotron/save" value="true" checked="checked" />
+ <input class="form-control" type="checkbox" id="conf-g-synchrotron-save" name="galactic/synchrotron/save" value="true" checked />
<label for="conf-g-synchrotron-save">Save this component standalone</label>
</div>
</div>
@@ -339,7 +347,7 @@
</div>
<div class="row">
<div class="column form-group">
- <input class="form-control" type="checkbox" id="conf-g-freefree-save" name="galactic/freefree/save" value="true" checked="checked" />
+ <input class="form-control" type="checkbox" id="conf-g-freefree-save" name="galactic/freefree/save" value="true" checked />
<label for="conf-g-freefree-save">Save this component standalone</label>
</div>
</div>
@@ -378,7 +386,7 @@
</div>
<div class="row">
<div class="column form-group">
- <input class="form-control" type="checkbox" id="conf-g-snr-save" name="galactic/snr/save" value="true" checked="checked" />
+ <input class="form-control" type="checkbox" id="conf-g-snr-save" name="galactic/snr/save" value="true" checked />
<label for="conf-g-snr-save">Save this component standalone</label>
</div>
</div>
@@ -429,7 +437,7 @@
</div>
<div class="row">
<div class="column form-group">
- <input class="form-control" type="checkbox" id="conf-eg-clusters-save" name="extragalactic/clusters/save" value="true" checked="checked" />
+ <input class="form-control" type="checkbox" id="conf-eg-clusters-save" name="extragalactic/clusters/save" value="true" checked />
<label for="conf-eg-clusters-save">Save this component standalone</label>
</div>
</div>
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 %}
+ <script src="{{ static_url('js/configs.js') }}"></script>
<script src="{{ static_url('js/websocket.js') }}"></script>
{% end %}