diff options
Diffstat (limited to 'fg21sim/webui')
| -rw-r--r-- | fg21sim/webui/static/js/configs.js | 151 | ||||
| -rw-r--r-- | fg21sim/webui/static/js/websocket.js | 84 | ||||
| -rw-r--r-- | fg21sim/webui/templates/configs.html | 62 | ||||
| -rw-r--r-- | fg21sim/webui/templates/index.html | 1 | 
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 %}  | 
