Browse Source

HPCC-8044 Redesign WU Details Page

Signed-off-by: Gordon Smith <gordon.smith@lexisnexis.com>
Gordon Smith 12 years ago
parent
commit
067460bca6
45 changed files with 2204 additions and 255 deletions
  1. 4 0
      esp/eclwatch/ws_XSLT/workunits.xslt
  2. 45 0
      esp/files/CodeMirror2/mode/xml/index.html
  3. 318 0
      esp/files/CodeMirror2/mode/xml/xml.js
  4. 106 0
      esp/files/css/hpcc.css
  5. BIN
      esp/files/img/locked.png
  6. BIN
      esp/files/img/refresh2.png
  7. BIN
      esp/files/img/unlocked.png
  8. BIN
      esp/files/img/workunit.png
  9. BIN
      esp/files/img/workunit_aborting.png
  10. BIN
      esp/files/img/workunit_completed.png
  11. BIN
      esp/files/img/workunit_deleted.png
  12. BIN
      esp/files/img/workunit_edit.png
  13. BIN
      esp/files/img/workunit_failed.png
  14. BIN
      esp/files/img/workunit_running.png
  15. BIN
      esp/files/img/workunit_submitted.png
  16. BIN
      esp/files/img/workunit_warning.png
  17. 24 18
      esp/files/scripts/CMakeLists.txt
  18. 25 25
      esp/files/scripts/ECLPlaygroundWidget.js
  19. 12 7
      esp/files/scripts/ECLSourceWidget.js
  20. 224 51
      esp/files/scripts/ESPResult.js
  21. 217 34
      esp/files/scripts/ESPWorkunit.js
  22. 24 38
      esp/files/scripts/GraphPageWidget.js
  23. 164 0
      esp/files/scripts/InfoGridWidget.js
  24. 189 0
      esp/files/scripts/LogsWidget.js
  25. 22 12
      esp/files/scripts/ResultsControl.js
  26. 39 4
      esp/files/scripts/ResultsWidget.js
  27. 0 0
      esp/files/scripts/SampleSelectControl.js
  28. 134 0
      esp/files/scripts/TimingGridWidget.js
  29. 73 0
      esp/files/scripts/TimingPageWidget.js
  30. 46 46
      esp/files/scripts/TimingTreeMapWidget.js
  31. 373 0
      esp/files/scripts/WUDetailsWidget.js
  32. 1 0
      esp/files/stub.htm
  33. 7 4
      esp/files/stub.js
  34. 5 0
      esp/files/templates/CMakeLists.txt
  35. 1 0
      esp/files/templates/ECLPlaygroundWidget.html
  36. 1 1
      esp/files/templates/ECLSourceWidget.html
  37. 3 9
      esp/files/templates/GraphPageWidget.html
  38. 4 0
      esp/files/templates/InfoGridWidget.html
  39. 13 0
      esp/files/templates/LogsWidget.html
  40. 11 1
      esp/files/templates/ResultsWidget.html
  41. 1 1
      esp/files/templates/TargetSelectWidget.html
  42. 10 0
      esp/files/templates/TimingGridWidget.html
  43. 8 0
      esp/files/templates/TimingPageWidget.html
  44. 3 4
      esp/files/templates/TimingTreeMapWidget.html
  45. 97 0
      esp/files/templates/WUDetailsWidget.html

+ 4 - 0
esp/eclwatch/ws_XSLT/workunits.xslt

@@ -484,6 +484,10 @@
                     </a>
                 </xsl:otherwise>
             </xsl:choose>
+           -
+           <a href="javascript:go('/esp/files/stub.htm?Widget=WUDetailsWidget&amp;Wuid={Wuid}')">
+             Show
+           </a>
          </td>
          <td>
          <xsl:choose>

+ 45 - 0
esp/files/CodeMirror2/mode/xml/index.html

@@ -0,0 +1,45 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CodeMirror: XML mode</title>
+    <link rel="stylesheet" href="../../lib/codemirror.css">
+    <script src="../../lib/codemirror.js"></script>
+    <script src="xml.js"></script>
+    <style type="text/css">.foo{border-right: 1px solid red} .CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
+    <link rel="stylesheet" href="../../doc/docs.css">
+  </head>
+  <body>
+    <h1>CodeMirror: XML mode</h1>
+    <form><textarea id="code" name="code">
+&lt;html style="color: green"&gt;
+  &lt;!-- this is a comment --&gt;
+  &lt;head&gt;
+    &lt;title&gt;HTML Example&lt;/title&gt;
+  &lt;/head&gt;
+  &lt;body&gt;
+    The indentation tries to be &lt;em&gt;somewhat &amp;quot;do what
+    I mean&amp;quot;&lt;/em&gt;... but might not match your style.
+  &lt;/body&gt;
+&lt;/html&gt;
+</textarea></form>
+    <script>
+      var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
+        mode: {name: "xml", alignCDATA: true},
+        lineNumbers: true
+      });
+    </script>
+    <p>The XML mode supports two configuration parameters:</p>
+    <dl>
+      <dt><code>htmlMode (boolean)</code></dt>
+      <dd>This switches the mode to parse HTML instead of XML. This
+      means attributes do not have to be quoted, and some elements
+      (such as <code>br</code>) do not require a closing tag.</dd>
+      <dt><code>alignCDATA (boolean)</code></dt>
+      <dd>Setting this to true will force the opening tag of CDATA
+      blocks to not be indented.</dd>
+    </dl>
+
+    <p><strong>MIME types defined:</strong> <code>application/xml</code>, <code>text/html</code>.</p>
+  </body>
+</html>

+ 318 - 0
esp/files/CodeMirror2/mode/xml/xml.js

@@ -0,0 +1,318 @@
+CodeMirror.defineMode("xml", function(config, parserConfig) {
+  var indentUnit = config.indentUnit;
+  var Kludges = parserConfig.htmlMode ? {
+    autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
+                      'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
+                      'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
+                      'track': true, 'wbr': true},
+    implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
+                       'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
+                       'th': true, 'tr': true},
+    contextGrabbers: {
+      'dd': {'dd': true, 'dt': true},
+      'dt': {'dd': true, 'dt': true},
+      'li': {'li': true},
+      'option': {'option': true, 'optgroup': true},
+      'optgroup': {'optgroup': true},
+      'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
+            'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
+            'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
+            'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
+            'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
+      'rp': {'rp': true, 'rt': true},
+      'rt': {'rp': true, 'rt': true},
+      'tbody': {'tbody': true, 'tfoot': true},
+      'td': {'td': true, 'th': true},
+      'tfoot': {'tbody': true},
+      'th': {'td': true, 'th': true},
+      'thead': {'tbody': true, 'tfoot': true},
+      'tr': {'tr': true}
+    },
+    doNotIndent: {"pre": true},
+    allowUnquoted: true,
+    allowMissing: true
+  } : {
+    autoSelfClosers: {},
+    implicitlyClosed: {},
+    contextGrabbers: {},
+    doNotIndent: {},
+    allowUnquoted: false,
+    allowMissing: false
+  };
+  var alignCDATA = parserConfig.alignCDATA;
+
+  // Return variables for tokenizers
+  var tagName, type;
+
+  function inText(stream, state) {
+    function chain(parser) {
+      state.tokenize = parser;
+      return parser(stream, state);
+    }
+
+    var ch = stream.next();
+    if (ch == "<") {
+      if (stream.eat("!")) {
+        if (stream.eat("[")) {
+          if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
+          else return null;
+        }
+        else if (stream.match("--")) return chain(inBlock("comment", "-->"));
+        else if (stream.match("DOCTYPE", true, true)) {
+          stream.eatWhile(/[\w\._\-]/);
+          return chain(doctype(1));
+        }
+        else return null;
+      }
+      else if (stream.eat("?")) {
+        stream.eatWhile(/[\w\._\-]/);
+        state.tokenize = inBlock("meta", "?>");
+        return "meta";
+      }
+      else {
+        type = stream.eat("/") ? "closeTag" : "openTag";
+        stream.eatSpace();
+        tagName = "";
+        var c;
+        while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
+        state.tokenize = inTag;
+        return "tag";
+      }
+    }
+    else if (ch == "&") {
+      var ok;
+      if (stream.eat("#")) {
+        if (stream.eat("x")) {
+          ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");          
+        } else {
+          ok = stream.eatWhile(/[\d]/) && stream.eat(";");
+        }
+      } else {
+        ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
+      }
+      return ok ? "atom" : "error";
+    }
+    else {
+      stream.eatWhile(/[^&<]/);
+      return null;
+    }
+  }
+
+  function inTag(stream, state) {
+    var ch = stream.next();
+    if (ch == ">" || (ch == "/" && stream.eat(">"))) {
+      state.tokenize = inText;
+      type = ch == ">" ? "endTag" : "selfcloseTag";
+      return "tag";
+    }
+    else if (ch == "=") {
+      type = "equals";
+      return null;
+    }
+    else if (/[\'\"]/.test(ch)) {
+      state.tokenize = inAttribute(ch);
+      return state.tokenize(stream, state);
+    }
+    else {
+      stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
+      return "word";
+    }
+  }
+
+  function inAttribute(quote) {
+    return function(stream, state) {
+      while (!stream.eol()) {
+        if (stream.next() == quote) {
+          state.tokenize = inTag;
+          break;
+        }
+      }
+      return "string";
+    };
+  }
+
+  function inBlock(style, terminator) {
+    return function(stream, state) {
+      while (!stream.eol()) {
+        if (stream.match(terminator)) {
+          state.tokenize = inText;
+          break;
+        }
+        stream.next();
+      }
+      return style;
+    };
+  }
+  function doctype(depth) {
+    return function(stream, state) {
+      var ch;
+      while ((ch = stream.next()) != null) {
+        if (ch == "<") {
+          state.tokenize = doctype(depth + 1);
+          return state.tokenize(stream, state);
+        } else if (ch == ">") {
+          if (depth == 1) {
+            state.tokenize = inText;
+            break;
+          } else {
+            state.tokenize = doctype(depth - 1);
+            return state.tokenize(stream, state);
+          }
+        }
+      }
+      return "meta";
+    };
+  }
+
+  var curState, setStyle;
+  function pass() {
+    for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
+  }
+  function cont() {
+    pass.apply(null, arguments);
+    return true;
+  }
+
+  function pushContext(tagName, startOfLine) {
+    var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
+    curState.context = {
+      prev: curState.context,
+      tagName: tagName,
+      indent: curState.indented,
+      startOfLine: startOfLine,
+      noIndent: noIndent
+    };
+  }
+  function popContext() {
+    if (curState.context) curState.context = curState.context.prev;
+  }
+
+  function element(type) {
+    if (type == "openTag") {
+      curState.tagName = tagName;
+      return cont(attributes, endtag(curState.startOfLine));
+    } else if (type == "closeTag") {
+      var err = false;
+      if (curState.context) {
+        if (curState.context.tagName != tagName) {
+          if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
+            popContext();
+          }
+          err = !curState.context || curState.context.tagName != tagName;
+        }
+      } else {
+        err = true;
+      }
+      if (err) setStyle = "error";
+      return cont(endclosetag(err));
+    }
+    return cont();
+  }
+  function endtag(startOfLine) {
+    return function(type) {
+      if (type == "selfcloseTag" ||
+          (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
+        maybePopContext(curState.tagName.toLowerCase());
+        return cont();
+      }
+      if (type == "endTag") {
+        maybePopContext(curState.tagName.toLowerCase());
+        pushContext(curState.tagName, startOfLine);
+        return cont();
+      }
+      return cont();
+    };
+  }
+  function endclosetag(err) {
+    return function(type) {
+      if (err) setStyle = "error";
+      if (type == "endTag") { popContext(); return cont(); }
+      setStyle = "error";
+      return cont(arguments.callee);
+    };
+  }
+  function maybePopContext(nextTagName) {
+    var parentTagName;
+    while (true) {
+      if (!curState.context) {
+        return;
+      }
+      parentTagName = curState.context.tagName.toLowerCase();
+      if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
+          !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
+        return;
+      }
+      popContext();
+    }
+  }
+
+  function attributes(type) {
+    if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
+    if (type == "endTag" || type == "selfcloseTag") return pass();
+    setStyle = "error";
+    return cont(attributes);
+  }
+  function attribute(type) {
+    if (type == "equals") return cont(attvalue, attributes);
+    if (!Kludges.allowMissing) setStyle = "error";
+    return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
+  }
+  function attvalue(type) {
+    if (type == "string") return cont(attvaluemaybe);
+    if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
+    setStyle = "error";
+    return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
+  }
+  function attvaluemaybe(type) {
+    if (type == "string") return cont(attvaluemaybe);
+    else return pass();
+  }
+
+  return {
+    startState: function() {
+      return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
+    },
+
+    token: function(stream, state) {
+      if (stream.sol()) {
+        state.startOfLine = true;
+        state.indented = stream.indentation();
+      }
+      if (stream.eatSpace()) return null;
+
+      setStyle = type = tagName = null;
+      var style = state.tokenize(stream, state);
+      state.type = type;
+      if ((style || type) && style != "comment") {
+        curState = state;
+        while (true) {
+          var comb = state.cc.pop() || element;
+          if (comb(type || style)) break;
+        }
+      }
+      state.startOfLine = false;
+      return setStyle || style;
+    },
+
+    indent: function(state, textAfter, fullLine) {
+      var context = state.context;
+      if ((state.tokenize != inTag && state.tokenize != inText) ||
+          context && context.noIndent)
+        return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
+      if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
+      if (context && /^<\//.test(textAfter))
+        context = context.prev;
+      while (context && !context.startOfLine)
+        context = context.prev;
+      if (context) return context.indent + indentUnit;
+      else return 0;
+    },
+
+    electricChars: "/"
+  };
+});
+
+CodeMirror.defineMIME("text/xml", "xml");
+CodeMirror.defineMIME("application/xml", "xml");
+//if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
+//  CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});

+ 106 - 0
esp/files/css/hpcc.css

@@ -28,6 +28,12 @@ html, body {
     padding-left: 0px;
 }
 
+
+#borderContainer { width:100%; height:100% }
+
+#helpAction, #helpScope{
+width:12px; height:12px; }
+
 button {
     background-position: top;
     padding: 2px 8px 4px;
@@ -97,3 +103,103 @@ h1 {
     padding: 0px;
     overflow: hidden;
 }
+
+
+/*start styles for WUDetails*/
+    #container{
+        width:960px;
+        
+
+    }
+
+    #mainMenu{
+        margin-bottom:10px;
+    }
+
+    #widgetWUInfoResponse{
+        padding-left:10px;
+    }
+
+    table{
+        margin-left:5px;
+    }
+
+
+   table tr td:first-child{
+    padding : 10px 20px 10px 0px;
+    font-size:13px;
+    font-weight:normal;
+}
+
+
+
+
+form {
+    margin-left:10px;
+}
+
+form textarea{
+    font-size: 13px;
+    font-family: Arial;
+    width:170px;}
+
+
+
+form div{
+    padding:5px 0px 10px 0px;  
+}
+
+form h2{
+    padding:5px 0px;
+}
+
+
+.dijitValidationTextBoxLabel, .dijitInputField {
+    width:170px;
+    text-align: left;
+
+}
+
+
+.iconAlign{
+    vertical-align: middle;
+}
+
+.iconRefresh {
+	background-image: url("../img/refresh2.png"); 
+	width: 16px;
+	height: 16px;
+}
+
+/*dijit specific styles*/
+
+.ErrorCell{
+background: red;
+color: white;
+}
+
+.WarningCell{
+background: yellow;
+}
+
+.showDescription{
+    padding:10px;
+    height:100px;   
+}
+
+
+
+/* CAN WE MOVE AND REPLACE Prompt with this instead?.bold{
+
+    font-weight:bold;
+}
+*/
+
+.Prompt{
+    font-weight:bold;
+}
+
+.resultGridCell {
+    vertical-align:text-top;
+    font-family: monospace;
+}

BIN
esp/files/img/locked.png


BIN
esp/files/img/refresh2.png


BIN
esp/files/img/unlocked.png


BIN
esp/files/img/workunit.png


BIN
esp/files/img/workunit_aborting.png


BIN
esp/files/img/workunit_completed.png


BIN
esp/files/img/workunit_deleted.png


BIN
esp/files/img/workunit_edit.png


BIN
esp/files/img/workunit_failed.png


BIN
esp/files/img/workunit_running.png


BIN
esp/files/img/workunit_submitted.png


BIN
esp/files/img/workunit_warning.png


+ 24 - 18
esp/files/scripts/CMakeLists.txt

@@ -16,44 +16,50 @@
 add_subdirectory (configmgr)
 
 set ( SCRIPTS_FILES
-    ${CMAKE_CURRENT_SOURCE_DIR}/espdefault.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/CMultiSelect.js
     ${CMAKE_CURRENT_SOURCE_DIR}/bpsreport.js
     ${CMAKE_CURRENT_SOURCE_DIR}/builder.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/CMultiSelect.js
+
     ${CMAKE_CURRENT_SOURCE_DIR}/controls.js
     ${CMAKE_CURRENT_SOURCE_DIR}/dragdrop.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ECLPlaygroundWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ECLSourceWidget.js
     ${CMAKE_CURRENT_SOURCE_DIR}/effects.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ESPBase.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/espdefault.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ESPResult.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ESPWorkunit.js
     ${CMAKE_CURRENT_SOURCE_DIR}/fixedTables.js
     ${CMAKE_CURRENT_SOURCE_DIR}/graphgvc.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/GraphPageWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/GraphWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/InfoGridWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/LogsWidget.js
     ${CMAKE_CURRENT_SOURCE_DIR}/multiselect.js
     ${CMAKE_CURRENT_SOURCE_DIR}/objtree.js
     ${CMAKE_CURRENT_SOURCE_DIR}/prototype.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/range.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/timer.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/slider.js
     ${CMAKE_CURRENT_SOURCE_DIR}/prototype_helpers.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/range.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ResultsControl.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ResultsWidget.js
     ${CMAKE_CURRENT_SOURCE_DIR}/rightSideBar.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/SampleSelectControl.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/SampleSelectWidget.js
     ${CMAKE_CURRENT_SOURCE_DIR}/scriptaculous.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/slider.js
     ${CMAKE_CURRENT_SOURCE_DIR}/sortabletable.js
     ${CMAKE_CURRENT_SOURCE_DIR}/tabularForm.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/TargetSelectWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/timer.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/TimingGridWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/TimingPageWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/TimingTreeMapWidget.js
     ${CMAKE_CURRENT_SOURCE_DIR}/tooltip.js
     ${CMAKE_CURRENT_SOURCE_DIR}/tree.js
     ${CMAKE_CURRENT_SOURCE_DIR}/tree_template.js
     ${CMAKE_CURRENT_SOURCE_DIR}/ui_engine.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/ESPBase.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/ESPWorkunit.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/ESPResult.js
     ${CMAKE_CURRENT_SOURCE_DIR}/WsWorkunits.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/ResultsControl.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/SampleSelectControl.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/ECLPlaygroundWidget.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/GraphPageWidget.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/GraphWidget.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/ResultsWidget.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/SampleSelectWidget.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/TargetSelectWidget.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/TimingTreeMapWidget.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/ECLSourceWidget.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/WUDetailsWidget.js
 )
 set ( SCRIPTS_FILES ${SCRIPTS_FILES} PARENT_SCOPE)
 

+ 25 - 25
esp/files/scripts/ECLPlaygroundWidget.js

@@ -14,30 +14,30 @@
 #    limitations under the License.
 ############################################################################## */
 define([
-	"dojo/_base/declare",
-	"dojo/_base/xhr",
-	"dojo/dom",
-
-	"dijit/layout/_LayoutWidget",
-	"dijit/_TemplatedMixin",
-	"dijit/_WidgetsInTemplateMixin",
-	"dijit/layout/BorderContainer",
-	"dijit/layout/TabContainer",
-	"dijit/layout/ContentPane",
-	"dijit/registry",
-
-	"hpcc/ECLSourceWidget",
-	"hpcc/TargetSelectWidget",
-	"hpcc/SampleSelectWidget",
-	"hpcc/GraphWidget",
-	"hpcc/ResultsWidget",
-	"hpcc/ESPWorkunit",
+    "dojo/_base/declare",
+    "dojo/_base/xhr",
+    "dojo/dom",
+
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/layout/BorderContainer",
+    "dijit/layout/TabContainer",
+    "dijit/layout/ContentPane",
+    "dijit/registry",
+
+    "hpcc/ECLSourceWidget",
+    "hpcc/TargetSelectWidget",
+    "hpcc/SampleSelectWidget",
+    "hpcc/GraphWidget",
+    "hpcc/ResultsWidget",
+    "hpcc/ESPWorkunit",
 
     "dojo/text!../templates/ECLPlaygroundWidget.html"
 ], function (declare, xhr, dom,
-				_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, TabContainer, ContentPane, registry,
-				EclSourceWidget, TargetSelectWidget, SampleSelectWidget, GraphWidget, ResultsWidget, Workunit,
-				template) {
+                _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, TabContainer, ContentPane, registry,
+                EclSourceWidget, TargetSelectWidget, SampleSelectWidget, GraphWidget, ResultsWidget, Workunit,
+                template) {
     return declare("ECLPlaygroundWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
         templateString: template,
         baseClass: "ECLPlaygroundWidget",
@@ -96,6 +96,7 @@ define([
             this.targetSelectWidget.setValue(params.Target);
 
             this.initEditor();
+            this.editorControl.init(params);
 
             var context = this;
             this.initGraph();
@@ -103,9 +104,6 @@ define([
                 this.wu = new Workunit({
                     wuid: params.Wuid
                 });
-                this.wu.fetchText(function (text) {
-                    context.editorControl.setText(text);
-                });
                 this.wu.monitor(function () {
                     context.monitorEclPlayground();
                 });
@@ -211,7 +209,9 @@ define([
             var context = this;
             this.wu = new Workunit({
                 onCreate: function () {
-                    context.wu.update(context.editorControl.getText());
+                    context.wu.update({
+                        QueryText: context.editorControl.getText()
+                    });
                 },
                 onUpdate: function () {
                     context.wu.submit(context.targetSelectWidget.getValue());

+ 12 - 7
esp/files/scripts/ECLSourceWidget.js

@@ -60,7 +60,6 @@ define([
 
             startup: function (args) {
                 this.inherited(arguments);
-                this.initEditor();
             },
 
             resize: function (args) {
@@ -73,25 +72,31 @@ define([
             },
 
             //  Plugin wrapper  ---
-            initEditor: function () {
+            init: function (params) {
                 this.editor = CodeMirror.fromTextArea(document.getElementById(this.id + "EclCode"), {
                     tabMode: "indent",
                     matchBrackets: true,
                     gutter: true,
                     lineNumbers: true,
+                    mode: this.WUXml ? "xml" : "ecl",
                     readOnly: this.readOnly
                 });
-            },
 
-            init: function (params) {
                 var context = this;
                 if (params.Wuid) {
                     this.wu = new ESPWorkunit({
                         wuid: params.Wuid
                     });
-                    this.wu.fetchText(function (text) {
-                        context.editor.setValue(text);
-                    });
+                    if (this.WUXml) {
+                        this.wu.fetchXML(function (xml) {
+                            context.editor.setValue(xml);
+                        });
+                    }
+                    else {
+                        this.wu.fetchText(function (text) {
+                            context.editor.setValue(text);
+                        });
+                    }
                 }
             },
 

+ 224 - 51
esp/files/scripts/ESPResult.js

@@ -14,12 +14,20 @@
 #    limitations under the License.
 ############################################################################## */
 define([
-	"dojo/_base/declare",
-	"dojo/_base/Deferred",
-	"dojo/data/ObjectStore",
-	"hpcc/WsWorkunits",
-	"hpcc/ESPBase"
-], function (declare, Deferred, ObjectStore, WsWorkunits, ESPBase) {
+    "dojo/_base/declare",
+    "dojo/_base/Deferred",
+    "dojo/data/ObjectStore",
+    "dojo/dom-construct",
+
+    "dojox/xml/parser",
+    "dojox/xml/DomParser",
+    "dojox/html/entities",
+
+    "hpcc/WsWorkunits",
+    "hpcc/ESPBase"
+], function (declare, Deferred, ObjectStore, domConstruct,
+            parser, DomParser, entities, 
+            WsWorkunits, ESPBase) {
 	return declare(ESPBase, {
 		store: null,
 		Total: "-1",
@@ -56,59 +64,224 @@ define([
 			return this.Total != "-1";
 		},
 
-		getStructure: function () {
-			var retVal = [];
-			retVal.push({
-				name: "##",
-				field: this.store.idProperty,
-				width: "40px"
-			});
-			if (this.ECLSchemas) {
-				for (var i = 0; i < this.ECLSchemas.ECLSchemaItem.length; ++i) {
-					retVal.push({
-						name: this.ECLSchemas.ECLSchemaItem[i].ColumnName,
-						field: this.ECLSchemas.ECLSchemaItem[i].ColumnName,
-						width: this.extractWidth(this.ECLSchemas.ECLSchemaItem[i].ColumnType, this.ECLSchemas.ECLSchemaItem[i].ColumnName)
-					});
-				}
-			} else {
-				var context = this;
-				Deferred.when(this.store.query("*", {
-					start: 0,
-					count: 1,
-					sync: true
-				}), function (rows) {
-					if (rows.length) {
-						for (var key in rows[0]) {
-							if (key != "myInjectedRowNum") {
-								retVal.push({
-									name: key,
-									field: key,
-									width: context.extractWidth("string12", key)
-								});
-							}
-						}
-					}
-				});
-			}
-			return retVal;
-		},
+        getFirstSchemaNode: function (node, name) {
+            if (node && node.attributes) {
+                if ((node.baseName && node.baseName == name) || (node.localName && node.localName == name) || (typeof(node.getAttribute) != "undefined" && node.getAttribute("name") == name)) {
+                    return node;
+                }
+            }
+            for (var i = 0; i < node.childNodes.length; ++i) {
+                var retVal = this.getFirstSchemaNode(node.childNodes[i], name);
+                if (retVal) {
+                    return retVal;
+                }
+            }
+            return null;
+        },
+
+        getFirstSequenceNode: function (schemaNode) {
+            var row = this.getFirstSchemaNode(schemaNode, "Row");
+            if (!row)
+                return null;
+            var complexType = this.getFirstSchemaNode(row, "complexType");
+            if (!complexType)
+                return null;
+            return this.getFirstSchemaNode(complexType, "sequence");
+        },
+
+        rowToTable: function (cell) {
+            var table = domConstruct.create("table", { border: 1, cellspacing: 0, width: "100%" });
+            if (cell && cell.Row) {
+                if (!cell.Row.length) {
+                    cell.Row = [cell.Row];
+                }
+
+                for (i = 0; i < cell.Row.length; ++i) {
+                    if (i == 0) {
+                        var tr = domConstruct.create("tr", null, table);
+                        for (key in cell.Row[i]) {
+                            var th = domConstruct.create("th", { innerHTML: entities.encode(key) }, tr);
+                        }
+                    }
+                    var tr = domConstruct.create("tr", null, table);
+                    for (key in cell.Row[i]) {
+                        if (cell.Row[i][key]) {
+                            if (cell.Row[i][key].Row) {
+                                var td = domConstruct.create("td", null, tr);
+                                td.appendChild(this.rowToTable(cell.Row[i][key]));
+                            } else {
+                                var td = domConstruct.create("td", { innerHTML: entities.encode(cell.Row[i][key]) }, tr);
+                            }
+                        } else {
+                            var td = domConstruct.create("td", { innerHTML: "" }, tr);
+                        }
+                    }
+                }
+            }
+            return table;
+        },
+
+        getRowStructureFromSchema: function (parentNode) {
+            var retVal = [];
+            var sequence = this.getFirstSequenceNode(parentNode, "sequence");
+            if (!sequence)
+                return retVal;
+
+            for (var i = 0; i < sequence.childNodes.length; ++i) {
+                var node = sequence.childNodes[i];
+                if (typeof (node.getAttribute) != "undefined") {
+                    var name = node.getAttribute("name");
+                    var type = node.getAttribute("type");
+                    if (name && type) {
+                        retVal.push({
+                            name: name,
+                            field: name,
+                            width: this.extractWidth(type, name),
+                            classes: "resultGridCell"
+                        });
+                    }
+                    if (node.hasChildNodes()) {
+                        var context = this;
+                        retVal.push({
+                            name: name,
+                            field: name,
+                            formatter: function (cell, row, grid) {
+                                var div = document.createElement("div");
+                                div.appendChild(context.rowToTable(cell));
+                                return div.innerHTML;
+                            },
+                            width: this.getRowWidth(node),
+                            classes: "resultGridCell"
+                        });
+                    }
+                }
+            }
+            return retVal;
+        },
+
+        getRowStructureFromData: function (rows) {
+            var retVal = [];
+            for (var key in rows[0]) {
+                if (key != "myInjectedRowNum") {
+                    var context = this;
+                    retVal.push({
+                        name: key,
+                        field: key,
+                        formatter: function (cell, row, grid) {
+                            if (cell && cell.Row) {
+                                var div = document.createElement("div");
+                                div.appendChild(context.rowToTable(cell));
+                                return div.innerHTML;
+                            }
+                            return cell;
+                        },
+                        width: context.extractWidth("string12", key),
+                        classes: "resultGridCell"
+                    });
+                }
+            }
+            return retVal;
+        },
+
+        getStructure: function () {
+            var structure = [
+                {
+                    cells: [
+                        [
+                            {
+                                name: "##", field: this.store.idProperty, width: "40px", classes: "resultGridCell"
+                            }
+                        ]
+                    ]
+                }
+            ];
+
+            if (this.XmlSchema) {
+                var dom = parser.parse(this.XmlSchema);
+                var dataset = this.getFirstSchemaNode(dom, "Dataset");
+                var innerStruct = this.getRowStructureFromSchema(dataset);
+                for (var i = 0; i < innerStruct.length; ++i) {
+                    structure[0].cells[structure[0].cells.length - 1].push(innerStruct[i]);
+                }
+            } else {
+                var context = this;
+                Deferred.when(this.store.query("*", {
+                    start: 0,
+                    count: 1,
+                    sync: true
+                }), function (rows) {
+                    if (rows.length) {
+                        var innerStruct = context.getRowStructureFromData(rows);
+                        for (var i = 0; i < innerStruct.length; ++i) {
+                            structure[0].cells[structure[0].cells.length - 1].push(innerStruct[i]);
+                        }
+                    }
+                });
+            }
+            return structure;
+        },
+
+        getRowWidth: function (parentNode) {
+            var retVal = 0;
+            var sequence = this.getFirstSequenceNode(parentNode, "sequence");
+            if (!sequence)
+                return retVal;
+
+            for (var i = 0; i < sequence.childNodes.length; ++i) {
+                var node = sequence.childNodes[i];
+                if (typeof (node.getAttribute) != "undefined") {
+                    var name = node.getAttribute("name");
+                    var type = node.getAttribute("type");
+                    if (name && type) {
+                        retVal += this.extractWidth(type, name);
+                    } else if (node.hasChildNodes()) {
+                        retVal += this.getRowWidth(node);
+                    }
+                }
+            }
+            return retVal;
+        },
 
 		extractWidth: function (type, name) {
-			var numStr = "0123456789";
 			var retVal = -1;
-			var i = type.length;
-			while (i >= 0) {
-				if (numStr.indexOf(type.charAt(--i)) == -1)
+
+			switch (type) {
+				case "xs:boolean":
+					retVal = 5;
+					break;
+				case "xs:integer":
+					retVal = 8;
+					break;
+				case "xs:nonNegativeInteger":
+					retVal = 8;
+					break;
+				case "xs:double":
+					retVal = 8;
+					break;
+				case "xs:string":
+					retVal = 32;
+					break;
+				default:
+					var numStr = "0123456789";
+					var underbarPos = type.lastIndexOf("_");
+					var length = underbarPos > 0 ? underbarPos : type.length;
+					var i = length - 1;
+					for (; i >= 0; --i) {
+						if (numStr.indexOf(type.charAt(i)) == -1)
+							break;
+					}
+					if (i + 1 < length) {
+						retVal = parseInt(type.substring(i + 1, length));
+					}
+					if (type.indexOf("data") == 0) {
+						retVal *= 2;
+					}
 					break;
 			}
-			if (i > 0)
-				retVal = parseInt(type.substring(i + 1, type.length));
-
 			if (retVal < name.length)
 				retVal = name.length;
 
-			return Math.round(retVal * 2 / 3);
+			return retVal;
 		},
 
 		getObjectStore: function () {

+ 217 - 34
esp/files/scripts/ESPWorkunit.js

@@ -51,6 +51,11 @@ define([
 		},
 		isComplete: function () {
 			switch (this.stateID) {
+				case 1: //WUStateCompiled
+					if (lang.exists("WUInfoResponse.ActionEx", this) && this.WUInfoResponse.ActionEx == "compile") {
+						return true;
+					}
+					break;
 				case 3:	//WUStateCompleted:
 				case 4:	//WUStateFailed:
 				case 5:	//WUStateArchived:
@@ -75,8 +80,9 @@ define([
 					var workunit = response.WUQueryResponse.Workunits.ECLWorkunit[0];
 					context.stateID = workunit.StateID;
 					context.state = workunit.State;
+					context.protected = workunit.Protected;
 					if (callback) {
-						callback(context);
+						callback(workunit);
 					}
 
 					if (!context.isComplete()) {
@@ -120,29 +126,31 @@ define([
 				}
 			});
 		},
-		update: function (ecl, graphName, svg) {
-			var request = {};
-			request['Wuid'] = this.wuid;
-			if (ecl) {
-				request['QueryText'] = ecl;
+		update: function (request, appData, callback) {
+			lang.mixin(request, {
+				Wuid: this.wuid,
+				rawxml_: true
+			});
+			if (this.WUInfoResponse) {
+				lang.mixin(request, {
+					StateOrig: this.WUInfoResponse.State,
+					JobnameOrig: this.WUInfoResponse.Jobname,
+					DescriptionOrig: this.WUInfoResponse.Description,
+					ProtectedOrig: this.WUInfoResponse.Protected,
+					ScopeOrig: this.WUInfoResponse.Scope,
+					ClusterOrig: this.WUInfoResponse.Cluster
+				});
 			}
-			if (graphName && svg) {
-				/*
-				request['ApplicationValues'] = {
-					ApplicationValue: {
-						itemcount: 1,
-						Application: "ESPWorkunit.js",
-						Name: graphName + "_SVG",
-						Value: svg
-					}
+			if (appData) {
+				request['ApplicationValues.ApplicationValue.itemcount'] = appData.length;
+				var i = 0;
+				for (key in appData) {
+					request['ApplicationValues.ApplicationValue.' + i + '.Application'] = "ESPWorkunit.js";
+					request['ApplicationValues.ApplicationValue.' + i + '.Name'] = key;
+					request['ApplicationValues.ApplicationValue.' + i + '.Value'] = appData[key];
+					++i;
 				}
-				*/
-				request['ApplicationValues.ApplicationValue.itemcount'] = 1;
-				request['ApplicationValues.ApplicationValue.0.Application'] = "ESPWorkunit.js";
-				request['ApplicationValues.ApplicationValue.0.Name'] = graphName + "_SVG";
-				request['ApplicationValues.ApplicationValue.0.Value'] = svg;
 			}
-			request['rawxml_'] = "1";
 
 			var context = this;
 			xhr.post({
@@ -150,9 +158,16 @@ define([
 				handleAs: "json",
 				content: request,
 				load: function (response) {
+					context.WUInfoResponse = lang.mixin(context.WUInfoResponse, response.WUUpdateResponse.Workunit);
 					context.onUpdate();
+					if (callback && callback.load) {
+						callback.load(response);
+					}
 				},
 				error: function (error) {
+					if (callback && callback.error) {
+						callback.error(e);
+					}
 				}
 			});
 		},
@@ -175,6 +190,91 @@ define([
 				}
 			});
 		},
+		_resubmit: function (clone, resetWorkflow, callback) {
+			var request = {
+				Wuids: this.wuid,
+				CloneWorkunit: clone,
+				ResetWorkflow: resetWorkflow,
+				rawxml_: true
+			};
+
+			var context = this;
+			xhr.post({
+				url: this.getBaseURL() + "/WUResubmit.json",
+				handleAs: "json",
+				content: request,
+				load: function (response) {
+					if (callback && callback.load) {
+						callback.load(response);
+					}
+				},
+				error: function (e) {
+					if (callback && callback.error) {
+						callback.error(e);
+					}
+				}
+			});
+		},
+		clone: function (callback) {
+			this._resubmit(true, false, callback);
+		},
+		resubmit: function (callback) {
+			this._resubmit(false, false, callback);
+		},
+		restart: function (callback) {
+			this._resubmit(false, true, callback);
+		},
+		_action: function (action, callback) {
+			var request = {
+				Wuids: this.wuid,
+				ActionType: action,
+				rawxml_: true
+			};
+
+			var context = this;
+			xhr.post({
+				url: this.getBaseURL() + "/WUAction.json",
+				handleAs: "json",
+				content: request,
+				load: function (response) {
+					if (callback && callback.load) {
+						callback.load(response);
+					}
+				},
+				error: function (e) {
+					if (callback && callback.error) {
+						callback.error(e);
+					}
+				}
+			});
+		},
+		abort: function (callback) {
+			this._action("Abort", callback);
+		},
+		doDelete: function (callback) {
+			this._action("Delete", callback);
+		},
+		publish: function (jobName) {
+			var request = {
+				Wuid: this.wuid,
+				JobName: jobName,
+				Activate: 1, 
+				UpdateWorkUnitName: 1, 
+				Wait: 5000,
+				rawxml_: true
+			};
+
+			var context = this;
+			xhr.post({
+				url: this.getBaseURL() + "/WUPublishWorkunit.json",
+				handleAs: "json",
+				content: request,
+				load: function (response) {
+				},
+				error: function (e) {
+				}
+			});
+		},
 		getInfo: function (args) {
 			var request = {
 				Wuid: this.wuid,
@@ -184,15 +284,15 @@ define([
 				IncludeSourceFiles: args.onGetSourceFiles ? true : false,
 				IncludeResults: args.onGetResults ? true : false,
 				IncludeResultsViewNames: false,
-				IncludeVariables: false,
+				IncludeVariables: args.onGetVariables ? true : false,
 				IncludeTimers: args.onGetTimers ? true : false,
 				IncludeDebugValues: false,
 				IncludeApplicationValues: args.onGetApplicationValues ? true : false,
 				IncludeWorkflows: false,
 				IncludeXmlSchemas: args.onGetResults ? true : false,
 				SuppressResultSchemas: args.onGetResults ? false : true,
+				rawxml_: true
 			};
-			request['rawxml_'] = "1";
 
 			var context = this;
 			xhr.post({
@@ -202,6 +302,8 @@ define([
 				load: function (response) {
 					//var workunit = context.getValue(xmlDom, "Workunit", ["ECLException", "ECLResult", "ECLGraph", "ECLTimer", "ECLSchemaItem", "ApplicationValue"]);
 					var workunit = response.WUInfoResponse.Workunit;
+					context.WUInfoResponse = workunit;
+		
 					if (args.onGetText && workunit.Query.Text) {
 						context.text = workunit.Query.Text;
 						args.onGetText(context.text);
@@ -209,8 +311,6 @@ define([
 					if (args.onGetExceptions && workunit.Exceptions && workunit.Exceptions.ECLException) {
 						context.exceptions = [];
 						for (var i = 0; i < workunit.Exceptions.ECLException.length; ++i) {
-							if (workunit.Exceptions.ECLException[i].Severity == "Error" || 
-								workunit.Exceptions.ECLException[i].Severity == "Warning")
 							context.exceptions.push(workunit.Exceptions.ECLException[i]);						
 						}
 						args.onGetExceptions(context.exceptions);
@@ -219,6 +319,16 @@ define([
 						context.applicationValues = workunit.ApplicationValues.ApplicationValue;
 						args.onGetApplicationValues(context.applicationValues)
 					}
+					if (args.onGetVariables && workunit.Variables && workunit.Variables.ECLResult) {
+						context.variables = [];
+						var variables = workunit.Variables.ECLResult;
+						for (var i = 0; i < variables.length; ++i) {
+							context.variables.push(lang.mixin({
+								ColumnType: variables[i].ECLSchemas && variables[i].ECLSchemas.ECLSchemaItem.length ? variables[i].ECLSchemas.ECLSchemaItem[0].ColumnType : "unknown"
+							}, variables[i]));
+						}
+						args.onGetVariables(context.variables);
+					}
 					if (args.onGetResults && workunit.Results && workunit.Results.ECLResult) {
 						context.results = [];
 						var results = workunit.Results.ECLResult;
@@ -238,15 +348,16 @@ define([
 					if (args.onGetTimers && workunit.Timers && workunit.Timers.ECLTimer) {
 						context.timers = [];
 						for (var i = 0; i < workunit.Timers.ECLTimer.length; ++i) {
-							if (workunit.Timers.ECLTimer[i].GraphName && workunit.Timers.ECLTimer[i].SubGraphId) {
-								var timeParts = workunit.Timers.ECLTimer[i].Value.split(":");
-								var secs = 0;
-								for (var j = 0; j < timeParts.length; ++j) {
-									secs = secs * 60 + timeParts[j] * 1;
-								}
-
-								context.timers.push(lang.mixin(workunit.Timers.ECLTimer[i], { Seconds: Math.round(secs * 1000) / 1000 }));
+							var timeParts = workunit.Timers.ECLTimer[i].Value.split(":");
+							var secs = 0;
+							for (var j = 0; j < timeParts.length; ++j) {
+								secs = secs * 60 + timeParts[j] * 1;
 							}
+
+							context.timers.push(lang.mixin(workunit.Timers.ECLTimer[i], {
+								Seconds: Math.round(secs * 1000) / 1000,
+								HasSubGraphId: workunit.Timers.ECLTimer[i].SubGraphId && workunit.Timers.ECLTimer[i].SubGraphId != "" ? true : false
+							}));
 						}
 						args.onGetTimers(context.timers);
 					}
@@ -297,6 +408,52 @@ define([
 			}
 			return -1;
 		},
+		getState: function () {
+			return this.state;
+		},
+		getStateImage: function () {
+			switch (this.stateID) {
+				case 1: 
+					return "img/workunit_completed.png";
+				case 2:
+					return "img/workunit_running.png";
+				case 3:
+					return "img/workunit_completed.png";
+				case 4:
+					return "img/workunit_failed.png";
+				case 5:
+					return "img/workunit_warning.png";
+				case 6:
+					return "img/workunit_aborting.png";
+				case 7:
+					return "img/workunit_failed.png";
+				case 8:
+					return "img/workunit_warning.png";
+				case 9:
+					return "img/workunit_submitted.png";
+				case 10:
+					return "img/workunit_warning.png";
+				case 11:
+					return "img/workunit_running.png";
+				case 12:
+					return "img/workunit_warning.png";
+				case 13:
+					return "img/workunit_warning.png";
+				case 14:
+					return "img/workunit_warning.png";
+				case 15:
+					return "img/workunit_running.png";
+				case 999:
+					return "img/workunit_deleted.png";
+			}
+			return "img/workunit.png";
+		},
+		getProtectedImage: function () {
+			if (this.protected) {
+				return "img/locked.png"
+			}
+			return "img/unlocked.png"
+		},
 		fetchText: function (onFetchText) {
 			if (this.text) {
 				onFetchText(this.text);
@@ -307,6 +464,30 @@ define([
 				onGetText: onFetchText
 			});
 		},
+		fetchXML: function (onFetchXML) {
+			if (this.xml) {
+				onFetchXML(this.xml);
+				return;
+			}
+
+			var request = {
+				Wuid: this.wuid,
+				Type: "XML"
+			};
+
+			var context = this;
+			xhr.post({
+				url: this.getBaseURL() + "/WUFile.json",
+				handleAs: "text",
+				content: request,
+				load: function (response) {
+					context.xml = response;
+					onFetchXML(response);
+				},
+				error: function (e) {
+				}
+			});
+		},
 		fetchResults: function (onFetchResults) {
 			if (this.results && this.results.length) {
 				onFetchResults(this.results);
@@ -366,7 +547,9 @@ define([
 			var idx = this.getGraphIndex(graphName);
 			if (idx >= 0) {
 				this.graphs[idx].svg = svg;
-				this.update(null, graphName, svg);
+				var appData = [];
+				appData[graphName + "_SVG"] = svg;
+				this.update({ }, appData);
 			}
 		}
 	});

+ 24 - 38
esp/files/scripts/GraphPageWidget.js

@@ -36,6 +36,7 @@ define([
 
     "hpcc/GraphWidget",
     "hpcc/ESPWorkunit",
+    "hpcc/TimingGridWidget",
     "hpcc/TimingTreeMapWidget",
 
     "dojo/text!../templates/GraphPageWidget.html",
@@ -44,7 +45,7 @@ define([
     "dijit/form/TextBox"
 ], function (declare, sniff, array, dom, domConstruct, on, Memory, ObjectStore,
             _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, TabContainer, ContentPane, registry, Dialog,
-            DataGrid, GraphWidget, ESPWorkunit, TimingTreeMapWidget,
+            DataGrid, GraphWidget, ESPWorkunit, TimingGridWidget, TimingTreeMapWidget,
             template) {
     return declare("GraphPageWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
         templateString: template,
@@ -153,10 +154,8 @@ define([
             this.graphSelect.onChange = function () {
                 context.graphName = this.getValue();
                 context.loadGraph(context.wu, context.graphName);
-                context.timingGrid.setQuery({
-                    GraphName: context.graphName
-                });
-                context.timingTreeMap.loadTimers(context.wu.timers, context.graphName);
+                context.timingGrid.setQuery(context.graphName);
+                context.timingTreeMap.setQuery(context.graphName);
             }
         },
 
@@ -164,19 +163,15 @@ define([
             this.timingGrid = registry.byId(this.id + "TimingsGrid");
 
             var context = this;
-            this.timingGrid.on("RowClick", function (evt) {
+            this.timingGrid.onClick = function(items) {
                 context.syncSelectionFrom(context.timingGrid);
-            }, true);
+            };
 
-            this.timingGrid.on("RowDblClick", function (evt) {
-                var idx = evt.rowIndex;
-                var item = this.getItem(idx);
-                if (this.store.getValue(item, "SubGraphId")) {
-                    var subgraphID = this.store.getValue(item, "SubGraphId");
-                    var mainItem = context.main.getItem(subgraphID);
-                    context.main.centerOnItem(mainItem, true);
-                }
-            }, true);
+            this.timingGrid.onDblClick = function(item) {
+                var subgraphID = item.SubGraphId;
+                var mainItem = context.main.getItem(subgraphID);
+                context.main.centerOnItem(mainItem, true);
+            };
 
             this.timingTreeMap = registry.byId(this.id + "TimingsTreeMap");
             this.timingTreeMap.onClick = function (value) {
@@ -290,12 +285,14 @@ define([
                         } else {
                             context.refreshGraph(context.wu, context.graphName);
                         }
-                    },
-                    onGetTimers: function (timers) {
-                        context.loadTimings(timers);
                     }
                 });
             });
+
+            this.timingGrid.init(params);
+            this.timingGrid.setQuery(this.graphName);
+
+            this.timingTreeMap.init(params);
         },
 
         loadGraphSelect: function (graphs) {
@@ -342,15 +339,6 @@ define([
             });
         },
 
-        loadTimings: function (timers) {
-            var store = new Memory({ data: timers });
-            var dataStore = new ObjectStore({ objectStore: store });
-            this.timingGrid.setStore(dataStore);
-            this.timingGrid.setQuery({
-                GraphName: this.graphName
-            });
-        },
-
         loadVertices: function () {
             var vertices = this.main.plugin.getVerticesWithProperties();
 
@@ -413,15 +401,15 @@ define([
 
         syncSelectionFrom: function (sourceControl) {
             var selItems = [];
-            if (sourceControl == this.timingGrid) {
-                var items = sourceControl.selection.getSelected();
+
+            //  Get Selected Items  ---
+            if (sourceControl == this.timingGrid || sourceControl == this.timingTreeMap) {
+                var items = sourceControl.getSelected();
                 for (var i = 0; i < items.length; ++i) {
                     if (items[i].SubGraphId) {
                         selItems.push(items[i].SubGraphId);
                     }
                 }
-            } else if (sourceControl == this.timingTreeMap) {
-                selItems.push(sourceControl.lastSelection.subGraphId);
             } else if (sourceControl == this.verticesGrid || sourceControl == this.edgesGrid) {
                 var items = sourceControl.selection.getSelected();
                 for (var i = 0; i < items.length; ++i) {
@@ -433,14 +421,12 @@ define([
                 selItems = sourceControl.getSelectionAsGlobalID();
             }
 
-            if (sourceControl != this.timingGrid && this.timingGrid.store) {
-                for (var i = 0; i < this.timingGrid.rowCount; ++i) {
-                    var row = this.timingGrid.getItem(i);
-                    this.timingGrid.selection.setSelected(i, (row.SubGraphId && array.indexOf(selItems, row.SubGraphId) != -1));
-                }
+            //  Set Selected Items  ---
+            if (sourceControl != this.timingGrid) {
+                this.timingGrid.setSelected(selItems);
             }
             if (sourceControl != this.timingTreeMap) {
-                //TODO - Not sure if this is currently possible.
+                this.timingTreeMap.setSelected(selItems);
             }
             if (sourceControl != this.verticesGrid && this.verticesGrid.store) {
                 for (var i = 0; i < this.verticesGrid.rowCount; ++i) {

+ 164 - 0
esp/files/scripts/InfoGridWidget.js

@@ -0,0 +1,164 @@
+/*##############################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/array",
+    "dojo/store/Memory",
+    "dojo/data/ObjectStore",
+
+    "dijit/registry",
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+
+    "dojox/grid/DataGrid",
+
+    "hpcc/ESPWorkunit",
+
+    "dojo/text!../templates/InfoGridWidget.html"
+],
+    function (declare, array, Memory, ObjectStore, 
+            registry, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, 
+            DataGrid,
+            ESPWorkunit,
+            template) {
+        return declare("InfoGridWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+            templateString: template,
+            baseClass: "InfoGridWidget",
+            infoGrid: null,
+
+            dataStore: null,
+
+            lastSelection: null,
+
+            buildRendering: function (args) {
+                this.inherited(arguments);
+            },
+
+            test: function (value, rowIdx, cell) {
+                switch (value) {
+                    case "Error":
+                        cell.customClasses.push("ErrorCell");
+                        break;
+
+                    case "Warning":
+                        cell.customClasses.push("WarningCell");
+                        break;
+                }
+                return value;
+            },
+
+            postCreate: function (args) {
+                this.inherited(arguments);
+                this.infoGrid = registry.byId(this.id + "InfoGrid");
+
+                var context = this;
+                this.infoGrid.setStructure([
+                    { name: "Severity", field: "Severity", width: 8, formatter: context.test },
+                    { name: "Source", field: "Source", width: 10 },
+                    { name: "Code", field: "Code", width: 4 },
+                    { name: "Message", field: "Message", width: "100%" }
+                ]);
+
+                this.infoGrid.on("RowClick", function (evt) {
+                });
+
+                this.infoGrid.on("RowDblClick", function (evt) {
+                });
+            },
+
+            startup: function (args) {
+                this.inherited(arguments);
+            },
+
+            resize: function (args) {
+                this.inherited(arguments);
+                this.infoGrid.resize();
+            },
+
+            layout: function (args) {
+                this.inherited(arguments);
+            },
+
+            //  Plugin wrapper  ---
+            _onStyleRow: function (row) {
+                var item = this.infoGrid.getItem(row.index);
+                if (item) {
+                    var severity = this.store.getValue(item, "Severity", null);
+                    if (severity == "Error") {
+                        row.customStyles += "background-color: red;";
+                    } else if (severity == "Warning") {
+                        row.customStyles += "background-color: yellow;";
+                    }
+                }
+                this.infoGrid.focus.styleRow(row);
+                this.infoGrid.edit.styleRow(row);
+            },
+
+            onClick: function (items) {
+            },
+
+            onDblClick: function (item) {
+            },
+
+            init: function (params) {
+                this.wu = new ESPWorkunit({
+                    wuid: params.Wuid
+                });
+
+                var context = this;
+                this.wu.monitor(function () {
+                    context.wu.getInfo({
+                        onGetExceptions: function (exceptions) {
+                            context.loadExceptions(exceptions);
+                        }
+                    });
+                });
+            },
+
+            setQuery: function (graphName) {
+                if (!graphName || graphName == "*") {
+                    this.infoGrid.setQuery({
+                        GraphName: "*"
+                    });
+                } else {
+                    this.infoGrid.setQuery({
+                        GraphName: graphName,
+                        HasSubGraphId: true
+                    });
+                }
+            },
+
+            getSelected: function () {
+                return this.infoGrid.selection.getSelected();
+            },
+
+            setSelected: function (selItems) {
+                for (var i = 0; i < this.infoGrid.rowCount; ++i) {
+                    var row = this.infoGrid.getItem(i);
+                    this.infoGrid.selection.setSelected(i, (row.SubGraphId && array.indexOf(selItems, row.SubGraphId) != -1));
+                }
+            },
+
+            loadExceptions: function (exceptions) {
+                var memory = new Memory({ data: exceptions });
+                this.store = new ObjectStore({ objectStore: memory });
+                this.infoGrid.setStore(this.store);
+                this.setQuery("*");
+            }
+        });
+    });

+ 189 - 0
esp/files/scripts/LogsWidget.js

@@ -0,0 +1,189 @@
+/*##############################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/array",
+    "dojo/_base/lang",
+    "dojo/store/Memory",
+    "dojo/data/ObjectStore",
+    "dojo/request/iframe",
+
+    "dijit/registry",
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+
+    "dojox/grid/EnhancedGrid",
+    "dojox/grid/enhanced/plugins/IndirectSelection",
+
+    "hpcc/ESPWorkunit",
+
+    "dojo/text!../templates/LogsWidget.html"
+],
+    function (declare, array, lang, Memory, ObjectStore, iframe,
+            registry, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, 
+            EnhancedGrid, IndirectSelection,
+            ESPWorkunit,
+            template) {
+        return declare("LogsWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+            templateString: template,
+            baseClass: "LogsWidget",
+            borderContainer: null,
+            logsGrid: null,
+
+            dataStore: null,
+
+            lastSelection: null,
+
+            buildRendering: function (args) {
+                this.inherited(arguments);
+            },
+
+            postCreate: function (args) {
+                this.inherited(arguments);
+                this.borderContainer = registry.byId(this.id + "BorderContainer");
+                this.logsGrid = new dojox.grid.EnhancedGrid({
+                    id: this.id + "LogsGrid",
+                    structure: [
+                        { name: "Type", field: "Type", width: 8 },
+                        { name: "Description", field: "Description", width: "100%" }
+                    ],
+                    plugins: {
+                        indirectSelection: {
+                            headerSelector:true, width:"40px", styles:"text-align: center;"
+                        }
+                    }
+                });
+                this.logsGrid.placeAt(this.id + 'CenterPane');
+                this.logsGrid.startup();
+
+                var context = this;
+                this.logsGrid.on("RowClick", function (evt) {
+                });
+
+                this.logsGrid.on("RowDblClick", function (evt) {
+                });
+            },
+
+            startup: function (args) {
+                this.inherited(arguments);
+            },
+
+            resize: function (args) {
+                this.inherited(arguments);
+                this.borderContainer.resize();
+            },
+
+            layout: function (args) {
+                this.inherited(arguments);
+            },
+
+            _doDownload: function (option) {
+                var selection = this.logsGrid.selection.getSelected();
+
+                for (var i = 0; i < selection.length; ++i) {
+                    var downloadPdfIframeName = "downloadIframe_" + i;
+                    var frame = iframe.create(downloadPdfIframeName);
+                    var params = "";
+
+                    switch (selection[i].Type) {
+                        case "dll":
+                            var parts = selection[i].Orig.Name.split("/");
+                            if (parts.length) {
+                                var leaf = parts[parts.length - 1];
+                                params = "/WUFile/" + leaf + "?Wuid=" + this.wu.wuid + "&Name=" + selection[i].Orig.Name + "&Type=" + selection[i].Orig.Type;
+                            }
+                            break;
+                        case "res":
+                            params = "/WUFile/res.txt?Wuid=" + this.wu.wuid + "&Type=" + selection[i].Orig.Type;
+                            break;
+                        case "ThorLog":
+                        case "EclAgentLog":
+                            params = "/WUFile/" + selection[i].Type + "?Wuid=" + this.wu.wuid + "&Process=" + selection[i].Orig.Description + "&Type=" + selection[i].Orig.Type;
+                            break;
+                        case "ThorSlaveLog":
+                            params = "/WUFile?Wuid=" + this.wu.wuid + "&Process=" + selection[i].Orig.ProcessName + "&ClusterGroup=" + selection[i].Orig.ProcessName + "&LogDate=" + selection[i].Orig.LogDate + "&SlaveNumber=" + selection[i].Orig.SlaveNumber + "&Type=" + selection[i].Type;
+                            break;
+                    }
+
+                    var url = this.wu.getBaseURL() + params + (option ? "&Option=" + option : "&Option=1");
+                    iframe.setSrc(frame, url, true);
+                }
+
+            },
+            _onDownload: function (args) {
+                this._doDownload(1);
+            },
+            _onDownloadZip: function (args) {
+                this._doDownload(2);
+            },
+            _onDownloadGZip: function (args) {
+                this._doDownload(3);
+            },
+            //  Plugin wrapper  ---
+            init: function (params) {
+                this.wu = new ESPWorkunit({
+                    wuid: params.Wuid
+                });
+
+                var context = this;
+                this.wu.monitor(function () {
+                    context.wu.getInfo({
+                        onGetAll: function (response) {
+                            context.logData = [];
+                            if (response.Helpers && response.Helpers.ECLHelpFile) {
+                                context.loadHelpers(response.Helpers.ECLHelpFile);
+                            }
+                            if (response.ThorLogList && response.ThorLogList.ThorLogInfo) {
+                                context.loadThorLogInfo(response.ThorLogList.ThorLogInfo);
+                            }
+                            var memory = new Memory({ data: context.logData });
+                            var store = new ObjectStore({ objectStore: memory });
+                            context.logsGrid.setStore(store);
+                            context.logsGrid.setQuery({
+                                Type: "*"
+                            });
+                        }
+                    });
+                });
+            },
+
+            loadHelpers: function (helpers) {
+                for (var i = 0; i < helpers.length; ++i) {
+                    this.logData.push({
+                        Type: helpers[i].Type,
+                        Description: helpers[i].IPAddress ? "//" + helpers[i].IPAddress + helpers[i].Name : helpers[i].Name,
+                        Orig: helpers[i]
+                    });
+                }
+            },
+
+            loadThorLogInfo: function (thorLogInfo) {
+                for (var i = 0; i < thorLogInfo.length; ++i) {
+                    for (var j = 0; j < thorLogInfo[i].NumberSlaves; ++j) {
+                        this.logData.push({
+                            Type: "ThorSlaveLog",
+                            Description: thorLogInfo[i].ClusterGroup + "." + thorLogInfo[i].LogDate + ".log (slave " + (j + 1) + " of " + thorLogInfo[i].NumberSlaves + ")",
+                            Orig: lang.mixin({
+                                SlaveNumber: j + 1
+                            }, thorLogInfo[i])
+                        });
+                    }
+                }
+            }
+        });
+    });

+ 22 - 12
esp/files/scripts/ResultsControl.js

@@ -18,25 +18,27 @@ define([
 	"dojo/store/Memory",
 	"dojo/data/ObjectStore",
 
+	"dijit/registry",
+	"dijit/layout/ContentPane",
+
 	"dojox/grid/DataGrid",
 	"dojox/grid/EnhancedGrid",
 	"dojox/grid/enhanced/plugins/Pagination",
 	"dojox/grid/enhanced/plugins/Filter",
-	"dojox/grid/enhanced/plugins/NestedSorting",
+	"dojox/grid/enhanced/plugins/NestedSorting"
 
-	"dijit/registry",
-	"dijit/layout/ContentPane"
 ], function (declare, Memory, ObjectStore,
-			DataGrid, EnhancedGrid, Pagination, Filter, NestedSorting,
-			registry, ContentPane) {
+    registry, ContentPane,
+    DataGrid, EnhancedGrid, Pagination, Filter, NestedSorting) {
 	return declare(null, {
 		workunit: null,
 		paneNum: 0,
-		resultsSheetID: "",
+		id: "",
 		dataGridSheet: {},
 		resultIdStoreMap: [],
 		resultIdGridMap: [],
 		delayLoad: [],
+		selectedResult: null,
 
 		//  Callbacks
 		onErrorClick: function (line, col) {
@@ -46,12 +48,12 @@ define([
 		constructor: function (args) {
 			declare.safeMixin(this, args);
 
-			this.dataGridSheet = registry.byId(this.resultsSheetID);
+			this.dataGridSheet = registry.byId(this.id);
 			var context = this;
 			this.dataGridSheet.watch("selectedChildWidget", function (name, oval, nval) {
 				if (nval.id in context.delayLoad) {
-					var result = context.delayLoad[nval.id].result;
-					if (!result.isComplete()) {
+					context.selectedResult = context.delayLoad[nval.id].result;
+					if (!context.selectedResult.isComplete()) {
 						context.delayLoad[nval.id].loadingMessage = context.getLoadingMessage();
 					}
 					context.delayLoad[nval.id].placeAt(nval.containerNode, "last");
@@ -73,7 +75,7 @@ define([
 		},
 
 		getNextPaneID: function () {
-			return "Pane_" + ++this.paneNum;
+			return this.id + "Pane_" + ++this.paneNum;
 		},
 
 		addTab: function (label, paneID) {
@@ -83,7 +85,6 @@ define([
 			var pane = new ContentPane({
 				title: label,
 				id: paneID,
-				closable: true,
 				style: {
 					overflow: "hidden",
 					padding: 0
@@ -95,6 +96,7 @@ define([
 
 		addResultTab: function (result) {
 			var paneID = this.getNextPaneID();
+
 			var grid = EnhancedGrid({
 				result: result,
 				store: result.getObjectStore(),
@@ -163,7 +165,15 @@ define([
 		},
 
 		addExceptionTab: function (exceptions) {
-			if (exceptions.length) {
+			var hasErrorWarning = false;
+			for (var i = 0; i < exceptions.length; ++i) {
+				if (exceptions[i].Severity == "Error" || exceptions[i].Severity == "Warning") {
+					hasErrorWarning = true;
+					break;
+				}
+			}
+
+			if (hasErrorWarning) {
 				var resultNode = this.addTab("Error/Warning(s)");
 				store = new Memory({ data: exceptions });
 				dataStore = new ObjectStore({ objectStore: store });

+ 39 - 4
esp/files/scripts/ResultsWidget.js

@@ -15,8 +15,10 @@
 ############################################################################## */
 define([
 	"dojo/_base/declare",
+	"dojo/_base/lang",
 	"dojo/_base/xhr",
 	"dojo/dom",
+	"dojo/request/iframe",
 
 	"dijit/layout/_LayoutWidget",
 	"dijit/_TemplatedMixin",
@@ -28,7 +30,7 @@ define([
 	"hpcc/ESPWorkunit",
 	"hpcc/ResultsControl",
 	"dojo/text!../templates/ResultsWidget.html"
-], function (declare, xhr, dom,
+], function (declare, lang, xhr, dom, iframe,
 				_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, TabContainer, registry,
 				ESPBase, ESPWorkunit, ResultsControl,
 				template) {
@@ -36,6 +38,7 @@ define([
         templateString: template,
         baseClass: "ResultsWidget",
 
+        borderContainer: null,
         resultsPane: null,
         resultsControl: null,
 
@@ -46,7 +49,6 @@ define([
         postCreate: function (args) {
             this.inherited(arguments);
             this._initControls();
-            this.resultsPane.resize();
         },
 
         startup: function (args) {
@@ -55,24 +57,57 @@ define([
 
         resize: function (args) {
             this.inherited(arguments);
-            this.resultsPane.resize();
+            this.borderContainer.resize();
         },
 
         layout: function (args) {
             this.inherited(arguments);
         },
 
+        _doDownload: function (type) {
+            if (lang.exists("resultsControl.selectedResult.Sequence", this)) {
+                var sequence = this.resultsControl.selectedResult.Sequence;
+                var downloadPdfIframeName = "downloadIframe_" + sequence;
+                var frame = iframe.create(downloadPdfIframeName);
+                var url = this.wu.getBaseURL() + "/WUResultBin?Format=" + type + "&Wuid=" + this.wu.wuid + "&Sequence=" + sequence;
+                iframe.setSrc(frame, url, true);
+            } else if (lang.exists("resultsControl.selectedResult.Name", this)) {
+                var logicalName = this.resultsControl.selectedResult.Name;
+                var downloadPdfIframeName = "downloadIframe_" + logicalName;
+                var frame = iframe.create(downloadPdfIframeName);
+                var url = this.wu.getBaseURL() + "/WUResultBin?Format=" + type + "&Wuid=" + this.wu.wuid + "&LogicalName=" + logicalName;
+                iframe.setSrc(frame, url, true);
+            }
+        },
+
+        _onDownloadZip: function (args) {
+            this._doDownload("zip");
+        },
+
+        _onDownloadGZip: function (args) {
+            this._doDownload("gzip");
+        },
+
+        _onDownloadXLS: function (args) {
+            this._doDownload("xls");
+        },
+
+        _onFileDetails: function (args) {
+            alert("todo");
+        },
+
         //  Implementation  ---
         onErrorClick: function (line, col) {
         },
 
         _initControls: function () {
             var context = this;
+            this.borderContainer = registry.byId(this.id + "BorderContainer");
             this.resultsPane = registry.byId(this.id + "ResultsPane");
 
             var context = this;
             this.resultsControl = new ResultsControl({
-                resultsSheetID: this.id + "ResultsPane",
+                id: this.id + "ResultsPane",
                 sequence: 0,
                 onErrorClick: function (line, col) {
                     context.onErrorClick(line, col);

+ 0 - 0
esp/files/scripts/SampleSelectControl.js


+ 134 - 0
esp/files/scripts/TimingGridWidget.js

@@ -0,0 +1,134 @@
+/*##############################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/array",
+    "dojo/store/Memory",
+    "dojo/data/ObjectStore",
+
+    "dijit/registry",
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+
+    "dojox/grid/DataGrid",
+
+    "hpcc/ESPWorkunit",
+
+    "dojo/text!../templates/TimingGridWidget.html"
+],
+    function (declare, array, Memory, ObjectStore, 
+            registry, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, 
+            DataGrid,
+            ESPWorkunit,
+            template) {
+        return declare("TimingGridWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+            templateString: template,
+            baseClass: "TimingGridWidget",
+            timingGrid: null,
+
+            dataStore: null,
+
+            lastSelection: null,
+
+            buildRendering: function (args) {
+                this.inherited(arguments);
+            },
+
+            postCreate: function (args) {
+                this.inherited(arguments);
+                this.timingGrid = registry.byId(this.id + "TimingGrid");
+
+                var context = this;
+                this.timingGrid.on("RowClick", function (evt) {
+                    var items = context.timingGrid.selection.getSelected();
+                    context.onClick(items);
+                });
+
+                this.timingGrid.on("RowDblClick", function (evt) {
+                    var item = context.timingGrid.getItem(evt.rowIndex);
+                    context.onDblClick(item);
+                });
+            },
+
+            startup: function (args) {
+                this.inherited(arguments);
+            },
+
+            resize: function (args) {
+                this.inherited(arguments);
+                this.timingGrid.resize();
+            },
+
+            layout: function (args) {
+                this.inherited(arguments);
+            },
+
+            //  Plugin wrapper  ---
+            onClick: function (items) {
+            },
+
+            onDblClick: function (item) {
+            },
+
+            init: function (params) {
+                this.wu = new ESPWorkunit({
+                    wuid: params.Wuid
+                });
+
+                var context = this;
+                this.wu.monitor(function () {
+                    context.wu.getInfo({
+                        onGetTimers: function (timers) {
+                            context.loadTimings(timers);
+                        }
+                    });
+                });
+            },
+
+            setQuery: function (graphName) {
+                if (!graphName || graphName == "*") {
+                    this.timingGrid.setQuery({
+                        GraphName: "*"
+                    });
+                } else {
+                    this.timingGrid.setQuery({
+                        GraphName: graphName,
+                        HasSubGraphId: true
+                    });
+                }
+            },
+
+            getSelected: function () {
+                return this.timingGrid.selection.getSelected();
+            },
+
+            setSelected: function (selItems) {
+                for (var i = 0; i < this.timingGrid.rowCount; ++i) {
+                    var row = this.timingGrid.getItem(i);
+                    this.timingGrid.selection.setSelected(i, (row.SubGraphId && array.indexOf(selItems, row.SubGraphId) != -1));
+                }
+            },
+
+            loadTimings: function (timers) {
+                var store = new Memory({ data: timers });
+                var dataStore = new ObjectStore({ objectStore: store });
+                this.timingGrid.setStore(dataStore);
+                this.setQuery("*");
+            }
+        });
+    });

+ 73 - 0
esp/files/scripts/TimingPageWidget.js

@@ -0,0 +1,73 @@
+/*##############################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+
+    "dijit/registry",
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/layout/BorderContainer",
+
+    "hpcc/TimingGridWidget",
+    "hpcc/TimingTreeMapWidget",
+
+    "dojo/text!../templates/TimingPageWidget.html"
+],
+    function (declare, 
+            registry, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, 
+            TimingGridWidget, TimingTreeMapWidget,
+            template) {
+        return declare("TimingPageWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+            templateString: template,
+            baseClass: "TimingPageWidget",
+            borderContainer: null,
+            timingGrid: null,
+            timingTreeMap: null,
+
+            buildRendering: function (args) {
+                this.inherited(arguments);
+            },
+
+            postCreate: function (args) {
+                this.inherited(arguments);
+                this.borderContainer = registry.byId(this.id + "BorderContainer");
+                this.timingGrid = registry.byId(this.id + "Grid");
+                this.timingTreeMap = registry.byId(this.id + "TreeMap");
+            },
+
+            startup: function (args) {
+                this.inherited(arguments);
+            },
+
+            resize: function (args) {
+                this.inherited(arguments);
+                this.borderContainer.resize();
+            },
+
+            layout: function (args) {
+                this.inherited(arguments);
+            },
+
+            //  Plugin wrapper  ---
+            init: function (params) {
+                this.timingGrid.init(params);
+                this.timingTreeMap.init(params);
+            }
+
+        });
+    });

+ 46 - 46
esp/files/scripts/TimingTreeMapWidget.js

@@ -16,38 +16,30 @@
 ############################################################################## */
 define([
     "dojo/_base/declare",
-    "dojo/aspect",
-    "dojo/has",
-    "dojo/dom",
-    "dojo/dom-construct",
-    "dojo/dom-class",
     "dojo/store/Memory",
-    "dojo/_base/Color",
 
     "dijit/registry",
     "dijit/layout/_LayoutWidget",
     "dijit/_TemplatedMixin",
     "dijit/_WidgetsInTemplateMixin",
-    "dijit/layout/BorderContainer",
     "dijit/layout/ContentPane",
 
     "dojox/treemap/TreeMap",
-    "dojox/color/MeanColorModel",
 
     "hpcc/ESPWorkunit",
 
     "dojo/text!../templates/TimingTreeMapWidget.html"
 ],
-    function (declare, aspect, has, dom, domConstruct, domClass, Memory, Color,
-            registry, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, ContentPane,
-            TreeMap, MeanColorModel,
+    function (declare, Memory,
+            registry, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, ContentPane,
+            TreeMap, 
             ESPWorkunit,
             template) {
         return declare("TimingTreeMapWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
             templateString: template,
             baseClass: "TimingTreeMapWidget",
-            borderContainer: null,
-            timingContentPane: null,
+            contentPane: null,
+            treeMap: null,
 
             dataStore: null,
 
@@ -59,17 +51,17 @@ define([
 
             postCreate: function (args) {
                 this.inherited(arguments);
-                this.borderContainer = registry.byId(this.id + "BorderContainer");
-                this.timingContentPane = registry.byId(this.id + "TimingContentPane");
+                this.contentPane = registry.byId(this.id + "ContentPane");
+                this.treeMap = registry.byId(this.id + "TreeMap");
 
                 var context = this;
-                this.timingContentPane.on("change", function (e) {
+                this.treeMap.on("change", function (e) {
                     context.lastSelection = e.newValue;
                 });
-                this.timingContentPane.on("click", function (evt) {
+                this.treeMap.on("click", function (evt) {
                     context.onClick(context.lastSelection);
                 });
-                this.timingContentPane.on("dblclick", function (evt) {
+                this.treeMap.on("dblclick", function (evt) {
                     context.onDblClick(context.lastSelection);
                 });
             },
@@ -80,7 +72,7 @@ define([
 
             resize: function (args) {
                 this.inherited(arguments);
-                this.borderContainer.resize();
+                this.contentPane.resize();
             },
 
             layout: function (args) {
@@ -94,9 +86,6 @@ define([
             onDblClick: function (value) {
             },
 
-            initTreeMap: function () {
-            },
-
             init: function (params) {
                 var context = this;
                 if (params.Wuid) {
@@ -104,35 +93,52 @@ define([
                         wuid: params.Wuid
                     });
                     this.wu.fetchTimers(function (timers) {
+                        context.timers = timers;
                         context.loadTimers(timers, "*");
                     });
                 }
             },
 
+            setQuery: function(query) {
+                this.loadTimers(this.timers, query);
+            },
+
+            getSelected: function () {
+                return [{
+                    SubGraphId: this.lastSelection.subGraphId
+                }];
+            },
+
+            setSelected: function (selItems) {
+                //  TODO:  Not sure this possible...
+            },
+
             loadTimers: function (timers, query) {
                 this.largestValue = 0;
                 var timerData = [];
-                for (var i = 0; i < timers.length; ++i) {
-                    if (timers[i].GraphName && (query == "*" || query == timers[i].GraphName)) {
-                        var value = timers[i].Seconds;
-                        timerData.push({
-                            graphName: timers[i].GraphName,
-                            subGraphId: timers[i].SubGraphId,
-                            name: timers[i].Name,
-                            value: value
-                        });
-                        if (this.largestValue < value * 1000) {
-                            this.largestValue = value * 1000;
+                if (timers) {
+                    for (var i = 0; i < timers.length; ++i) {
+                        if (timers[i].GraphName && timers[i].SubGraphId && (query == "*" || query == timers[i].GraphName)) {
+                            var value = timers[i].Seconds;
+                            timerData.push({
+                                graphName: timers[i].GraphName,
+                                subGraphId: timers[i].SubGraphId,
+                                label: timers[i].Name,
+                                value: value
+                            });
+                            if (this.largestValue < value * 1000) {
+                                this.largestValue = value * 1000;
+                            }
                         }
                     }
                 }
                 var dataStore = new Memory({
-                    idProperty: "name",
+                    idProperty: "sequenceNumber",
                     data: timerData
                 });
 
                 var context = this;
-                this.timingContentPane.set("colorFunc", function (item) {
+                this.treeMap.set("colorFunc", function (item) {
                     var redness = 255 * (item.value * 1000 / context.largestValue);
                     return {
                         r: 255,
@@ -140,19 +146,13 @@ define([
                         b: 255 - redness
                     };
                 });
-                this.timingContentPane.set("areaAttr", "value");
-                this.timingContentPane.set("tooltipFunc", function (item) {
-                    return item.name + " " + item.value;
+                this.treeMap.set("areaAttr", "value");
+                this.treeMap.set("tooltipFunc", function (item) {
+                    return item.label + " " + item.value;
                 });
-                this.timingContentPane.set("groupAttrs", ["graphName"]);
-
-                this.timingContentPane.set("store", dataStore);
-            },
-
-            select: function (graphID, subGraphID) {
-                //this.timingContentPane.setItemSelected(this.timingContentPane.store.data[2]);
+                this.treeMap.set("groupAttrs", ["graphName"]);
 
+                this.treeMap.set("store", dataStore);
             }
-
         });
     });

+ 373 - 0
esp/files/scripts/WUDetailsWidget.js

@@ -0,0 +1,373 @@
+/*##############################################################################
+#	HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+#
+#	Licensed under the Apache License, Version 2.0 (the "License");
+#	you may not use this file except in compliance with the License.
+#	You may obtain a copy of the License at
+#
+#	   http://www.apache.org/licenses/LICENSE-2.0
+#
+#	Unless required by applicable law or agreed to in writing, software
+#	distributed under the License is distributed on an "AS IS" BASIS,
+#	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#	See the License for the specific language governing permissions and
+#	limitations under the License.
+############################################################################## */
+define([
+	"dojo/_base/declare",
+	"dojo/_base/xhr",
+	"dojo/dom",
+	"dojo/store/Memory",
+	"dojo/data/ObjectStore",
+
+	"dijit/layout/_LayoutWidget",
+	"dijit/_TemplatedMixin",
+	"dijit/_WidgetsInTemplateMixin",
+	"dijit/layout/BorderContainer",
+	"dijit/layout/TabContainer",
+	"dijit/layout/ContentPane",
+	"dijit/Toolbar",
+	"dijit/TooltipDialog",
+	"dijit/form/Textarea",
+	"dijit/form/Button",
+	"dijit/TitlePane",
+	"dijit/registry",
+
+	"hpcc/ECLSourceWidget",
+	"hpcc/TargetSelectWidget",
+	"hpcc/SampleSelectWidget",
+	"hpcc/GraphWidget",
+	"hpcc/ResultsWidget",
+	"hpcc/InfoGridWidget",
+	"hpcc/LogsWidget",
+	"hpcc/ESPWorkunit",
+
+	"dojo/text!../templates/WUDetailsWidget.html"
+], function (declare, xhr, dom, Memory, ObjectStore,
+				_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, TabContainer, ContentPane, Toolbar, TooltipDialog, Textarea, Button, TitlePane, registry,
+				EclSourceWidget, TargetSelectWidget, SampleSelectWidget, GraphWidget, ResultsWidget, InfoGridWidget, LogsWidget, Workunit,
+				template) {
+    return declare("WUDetailsWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "WUDetailsWidget",
+        borderContainer: null,
+        tabContainer: null,
+        resultsWidget: null,
+        resultsWidgetLoaded: false,
+        filesWidget: null,
+        filesWidgetLoaded: false,
+        timersWidget: null,
+        timersWidgetLoaded: false,
+        graphsWidget: null,
+        graphsWidgetLoaded: false,
+        sourceWidget: null,
+        sourceWidgetLoaded: false,
+        logsWidget: null,
+        logsWidgetLoaded: false,
+        playgroundWidget: null,
+        playgroundWidgetLoaded: false,
+        xmlWidget: null,
+        xmlWidgetLoaded: false,
+
+        wu: null,
+        prevState: "",
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this.borderContainer = registry.byId(this.id + "BorderContainer");
+            this.tabContainer = registry.byId(this.id + "TabContainer");
+            this.resultsWidget = registry.byId(this.id + "Results");
+            this.filesWidget = registry.byId(this.id + "Files");
+            this.timersWidget = registry.byId(this.id + "Timers");
+            this.graphsWidget = registry.byId(this.id + "Graphs");
+            this.sourceWidget = registry.byId(this.id + "Source");
+            this.logsWidget = registry.byId(this.id + "Logs");
+            this.playgroundWidget = registry.byId(this.id + "Playground");
+            this.xmlWidget = registry.byId(this.id + "XML");
+            this.infoGridWidget = registry.byId(this.id + "InfoContainer");
+            var context = this;
+            this.tabContainer.watch("selectedChildWidget", function (name, oval, nval) {
+                if (nval.id == context.id + "Results" && !context.resultsWidgetLoaded) {
+                    context.resultsWidgetLoaded = true;
+                    context.resultsWidget.init({
+                        Wuid: context.wu.wuid
+                    });
+                } else if (nval.id == context.id + "Files" && !context.filesWidgetLoaded) {
+                    context.filesWidgetLoaded = true;
+                    context.filesWidget.init({
+                        Wuid: context.wu.wuid,
+                        SourceFiles: true
+                    });
+                } else if (nval.id == context.id + "Timers" && !context.timersWidgetLoaded) {
+                    context.timersWidgetLoaded = true;
+                    context.timersWidget.init({
+                        Wuid: context.wu.wuid
+                    });
+                } else if (nval.id == context.id + "Graphs" && !context.graphsWidgetLoaded) {
+                    context.graphsWidgetLoaded = true;
+                    context.graphsWidget.init({
+                        Wuid: context.wu.wuid
+                    });
+                } else if (nval.id == context.id + "Source" && !context.sourceWidgetLoaded) {
+                    context.sourceWidgetLoaded = true;
+                    context.sourceWidget.init({
+                        Wuid: context.wu.wuid
+                    });
+                } else if (nval.id == context.id + "Logs" && !context.logsWidgetLoaded) {
+                    context.logsWidgetLoaded = true;
+                    context.logsWidget.init({
+                        Wuid: context.wu.wuid
+                    });
+                } else if (nval.id == context.id + "Playground" && !context.playgroundWidgetLoaded) {
+                    context.playgroundWidgetLoaded = true;
+                    context.playgroundWidget.init({
+                        Wuid: context.wu.wuid
+                    });
+                } else if (nval.id == context.id + "XML" && !context.xmlWidgetLoaded) {
+                    context.xmlWidgetLoaded = true;
+                    context.xmlWidget.init({
+                        Wuid: context.wu.wuid
+                    });
+                }
+            });
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+            this.borderContainer.resize();
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        //  Hitched actions  ---
+        _onSave: function (event) {
+            var protectedCheckbox = registry.byId("showProtected");
+            var context = this;
+            this.wu.update({
+                Description: dom.byId("showDescription").value,
+                Jobname: dom.byId("showJobName").value,
+                Protected: protectedCheckbox.get("value")
+            }, null, {
+                load: function (response) {
+                    context.monitor();
+                }
+            });
+        },
+        _onReload: function (event) {
+            this.monitor();
+        },
+        _onClone: function (event) {
+            this.wu.clone({
+                load: function (response) {
+                    //TODO
+                }
+            });
+        },
+        _onDelete: function (event) {
+            this.wu.doDelete({
+                load: function (response) {
+                    //TODO
+                }
+            });
+        },
+        _onResubmit: function (event) {
+            var context = this;
+            this.wu.resubmit({
+                load: function (response) {
+                    context.monitor();
+                }
+            });
+        },
+        _onAbort: function (event) {
+            var context = this;
+            this.wu.abort({
+                load: function (response) {
+                    context.monitor();
+                }
+            });
+        },
+        _onRestart: function (event) {
+            var context = this;
+            this.wu.restart({
+                load: function (response) {
+                    context.monitor();
+                }
+            });
+        },
+        _onPublish: function (event) {
+            this.wu.publish(dom.byId("showJobName2").value);
+        },
+
+        //  Implementation  ---
+        init: function (params) {
+            if (params.Wuid) {
+                dom.byId(this.id + "Wuid").innerHTML = params.Wuid;
+                dom.byId(this.id + "Wuid2").innerHTML = params.Wuid;
+                this.wu = new Workunit({
+                    wuid: params.Wuid
+                });
+                this.monitor();
+            }
+            this.infoGridWidget.init(params);
+        },
+
+        monitor: function () {
+            var prevState = "";
+            var context = this;
+            this.wu.monitor(function (workunit) {
+                context.monitorEclPlayground(workunit);
+            });
+        },
+
+        resetPage: function () {
+        },
+
+        objectToText: function (obj) {
+            var text = ""
+            for (var key in obj) {
+                text += "<tr><td>" + key + ":</td>";
+                if (typeof obj[key] == "object") {
+                    text += "[<br>";
+                    for (var i = 0; i < obj[key].length; ++i) {
+                        text += this.objectToText(obj[key][i]);
+                    }
+                    text += "<br>]<br>";
+                } else {
+                    text += "<td>" + obj[key] + "</td></tr>";
+
+                }
+            }
+            return text;
+        },
+
+        monitorEclPlayground: function (response) {
+            registry.byId(this.id + "Save").set("disabled", !this.wu.isComplete());
+            //registry.byId(this.id + "Reload").set("disabled", this.wu.isComplete());
+            registry.byId(this.id + "Clone").set("disabled", !this.wu.isComplete());
+            registry.byId(this.id + "Delete").set("disabled", !this.wu.isComplete());
+            registry.byId(this.id + "Abort").set("disabled", this.wu.isComplete());
+            registry.byId(this.id + "Resubmit").set("disabled", !this.wu.isComplete());
+            registry.byId(this.id + "Restart").set("disabled", !this.wu.isComplete());
+            registry.byId(this.id + "Publish").set("disabled", !this.wu.isComplete());
+
+            registry.byId("showJobName").set("readOnly", !this.wu.isComplete());
+            registry.byId("showDescription").set("readOnly", !this.wu.isComplete());
+            registry.byId("showProtected").set("readOnly", !this.wu.isComplete());
+
+            dom.byId("showStateIdImage").src = this.wu.getStateImage();
+            dom.byId("showStateIdImage").title = response.State;
+            dom.byId("showStateIdImage2").src = this.wu.getStateImage();
+            dom.byId("showStateIdImage2").title = response.State;
+            dom.byId("showProtectedImage").src = this.wu.getProtectedImage();
+            dom.byId("showProtectedImage2").src = this.wu.getProtectedImage();
+            dom.byId("showState").innerHTML = response.State;
+            dom.byId("showOwner").innerHTML = response.Owner;
+            dom.byId("showJobName").value = response.Jobname;
+            dom.byId("showJobName2").value = response.Jobname;
+            dom.byId("showCluster").innerHTML = response.Cluster;
+
+            var context = this;
+            if (this.wu.isComplete() || this.prevState != response.State) {
+                this.prevState = response.State;
+                this.wu.getInfo({
+                    onGetVariables: function (response) {
+                        registry.byId(context.id + "Variables").set("title", "Variables " + "(" + response.length + ")");
+                        context.variablesGrid = registry.byId(context.id + "VariablesGrid");
+                        context.variablesGrid.setStructure([
+                            { name: "Name", field: "Name", width: 16 },
+                            { name: "Type", field: "ColumnType", width: 10 },
+                            { name: "Default Value", field: "Value", width: 32 }
+                        ]);
+                        var memory = new Memory({ data: response });
+                        var store = new ObjectStore({ objectStore: memory });
+                        context.variablesGrid.setStore(store);
+                        context.variablesGrid.setQuery({
+                            Name: "*"
+                        });
+                    },
+
+                    onGetResults: function (response) {
+                        context.resultsWidget.set("title", "Outputs " + "(" + response.length + ")");
+                        var tooltip = "";
+                        for (var i = 0; i < response.length; ++i) {
+                            if (tooltip != "")
+                                tooltip += "\n";
+                            tooltip += response[i].Name;
+                            if (response[i].Value)
+                                tooltip += " " + response[i].Value;
+                        }
+                        context.resultsWidget.set("tooltip", tooltip);
+                    },
+
+                    onGetSourceFiles: function (response) {
+                        context.filesWidget.set("title", "Inputs " + "(" + response.length + ")");
+                        var tooltip = "";
+                        for (var i = 0; i < response.length; ++i) {
+                            if (tooltip != "")
+                                tooltip += "\n";
+                            tooltip += response[i].Name;
+                        }
+                        context.filesWidget.set("tooltip", tooltip);
+                    },
+
+                    onGetTimers: function (response) {
+                        context.timersWidget.set("title", "Timers " + "(" + response.length + ")");
+                        var tooltip = "";
+                        for (var i = 0; i < response.length; ++i) {
+                            if (response[i].GraphName)
+                                continue;
+                            if (response[i].Name == "Process")
+                                dom.byId("showTime").innerHTML = response[i].Value;
+                            if (tooltip != "")
+                                tooltip += "\n";
+                            tooltip += response[i].Name;
+                            if (response[i].Value)
+                                tooltip += " " + response[i].Value;
+                        }
+                        context.timersWidget.set("tooltip", tooltip);
+                    },
+
+                    onGetGraphs: function (response) {
+                        context.graphsWidget.set("title", "Graphs " + "(" + response.length + ")");
+                        var tooltip = "";
+                        for (var i = 0; i < response.length; ++i) {
+                            if (tooltip != "")
+                                tooltip += "\n";
+                            tooltip += response[i].Name;
+                            if (response[i].Time)
+                                tooltip += " " + response[i].Time;
+                        }
+                        context.graphsWidget.set("tooltip", tooltip);
+                    },
+
+                    onGetAll: function (response) {
+                        var helpersCount = 0;
+                        if (response.Helpers && response.Helpers.ECLHelpFile) {
+                            helpersCount += response.Helpers.ECLHelpFile.length;
+                        }
+                        if (response.ThorLogList && response.ThorLogList.ThorLogInfo) {
+                            helpersCount += response.ThorLogList.ThorLogInfo.length;
+                        }
+                        context.logsWidget.set("title", "Helpers " + "(" + helpersCount + ")");
+                        //dom.byId(context.id + "WUInfoResponse").innerHTML = context.objectToText(response);
+                        dom.byId("showDescription").value = response.Description;                        
+                        dom.byId("showAction").innerHTML = response.ActionEx;
+                        dom.byId("showScope").innerHTML = response.Scope;
+                        var protectedCheckbox = registry.byId("showProtected");
+                        protectedCheckbox.set("value", response.Protected);
+                    }
+                });
+            }
+        }
+    });
+});

+ 1 - 0
esp/files/stub.htm

@@ -23,6 +23,7 @@
     <link href="CodeMirror2/lib/codemirror.css" rel="stylesheet">
     <script src="CodeMirror2/lib/codemirror.js"></script>
     <script src="CodeMirror2/mode/ecl/ecl.js"></script>
+    <script src="CodeMirror2/mode/xml/xml.js"></script>
     <link href="css/ecl.css" rel="stylesheet">
     <link href="css/hpcc.css" rel="stylesheet">
     <link href="dijit/themes/claro/claro.css" media="screen" rel="stylesheet">

+ 7 - 4
esp/files/stub.js

@@ -25,10 +25,13 @@ define([
     "hpcc/ECLPlaygroundWidget",
     "hpcc/GraphPageWidget",
     "hpcc/ResultsWidget",
+    "hpcc/TimingPageWidget",
     "hpcc/TimingTreeMapWidget",
-    "hpcc/ECLSourceWidget"
+    "hpcc/ECLSourceWidget",
+    "hpcc/InfoGridWidget",
+    "hpcc/WUDetailsWidget"
 ], function (fx, baseWindow, dom, domStyle, domGeometry, ioQuery, ready,
-        ECLPlaygroundWidget, GraphPageWidget, ResultsWidget, TimingTreeMapWidget, ECLSourceWidget) {
+        ECLPlaygroundWidget, GraphPageWidget, ResultsWidget, TimingPageWidget, TimingTreeMapWidget, ECLSourceWidget, InfoGridWidget, WUDetailsWidget) {
 
     var initUi = function () {
         var params = ioQuery.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)));
@@ -36,7 +39,7 @@ define([
         //TODO:  Can we get rid of the required dependency above?
         var widget = new (eval(params.Widget))({
             id: "appLayout",
-            class: "hpccApp"
+            "class": "hpccApp"
         });
 
         if (widget) {
@@ -66,7 +69,7 @@ define([
                 domStyle.set(node, "display", "none");
             }
         }).play();
-    }
+    };
 
     return {
         init: function () {

+ 5 - 0
esp/files/templates/CMakeLists.txt

@@ -18,10 +18,15 @@ set ( TEMPLATES_FILES
     ${CMAKE_CURRENT_SOURCE_DIR}/ECLSourceWidget.html
     ${CMAKE_CURRENT_SOURCE_DIR}/GraphPageWidget.html
     ${CMAKE_CURRENT_SOURCE_DIR}/GraphWidget.html
+    ${CMAKE_CURRENT_SOURCE_DIR}/InfoGridWidget.html
+    ${CMAKE_CURRENT_SOURCE_DIR}/LogsWidget.html
     ${CMAKE_CURRENT_SOURCE_DIR}/ResultsWidget.html
     ${CMAKE_CURRENT_SOURCE_DIR}/SampleSelectWidget.html
     ${CMAKE_CURRENT_SOURCE_DIR}/TargetSelectWidget.html
+    ${CMAKE_CURRENT_SOURCE_DIR}/TimingGridWidget.html
+    ${CMAKE_CURRENT_SOURCE_DIR}/TimingPageWidget.html
     ${CMAKE_CURRENT_SOURCE_DIR}/TimingTreeMapWidget.html
+    ${CMAKE_CURRENT_SOURCE_DIR}/WUDetailsWidget.html
 )
 set ( TEMPLATES_FILES ${TEMPLATES_FILES} PARENT_SCOPE)
 

+ 1 - 0
esp/files/templates/ECLPlaygroundWidget.html

@@ -18,6 +18,7 @@
         <div id="${id}SubmitPane" class="edgePanel" data-dojo-props="region: 'bottom'" data-dojo-type="dijit.layout.ContentPane">
             <div style="display: inline-block; vertical-align: middle">
                 <button id="${id}SubmitBtn" data-dojo-attach-event="onClick:_onSubmit" data-dojo-type="dijit.form.Button">Submit</button>
+                <label for="Target">Target:</label>
                 <div id="${id}TargetSelect" style="display: inline-block; vertical-align: middle" data-dojo-type="TargetSelectWidget">
                 </div>
             </div>

+ 1 - 1
esp/files/templates/ECLSourceWidget.html

@@ -1,7 +1,7 @@
 <div class="${baseClass}">
     <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%; padding: 0px; overflow: hidden" data-dojo-props="splitter: false, gutters:false" data-dojo-type="dijit.layout.BorderContainer">
         <div id="${id}EclContent" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
-            <textarea id="${id}EclCode"/>
+            <textarea id="${id}EclCode">.</textarea>
         </div>
     </div>
 </div>

+ 3 - 9
esp/files/templates/GraphPageWidget.html

@@ -15,14 +15,8 @@
             <div id="${id}OverviewTabContainer" data-dojo-props="region: 'center', tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
                 <div id="${id}MiniGraphWidget" title="Overview" data-dojo-type="GraphWidget">
                 </div>
-                <table id="${id}TimingsGrid" title="Timings" style="padding: 0px; overflow: hidden" data-dojo-type="dojox.grid.DataGrid">
-                    <thead>
-                        <tr>
-                            <th field="Name" width="auto">Component</th>
-                            <th field="Seconds" width="auto">Time (Seconds)</th>
-                        </tr>
-                    </thead>
-                </table>
+                <div id="${id}TimingsGrid" title="Timings" data-dojo-type="TimingGridWidget">
+                </div>
                 <div id="${id}TimingsTreeMap" title="Timings Map" data-dojo-type="TimingTreeMapWidget">
                 </div>
                 <div id="${id}VerticesGrid" title="Activities" style="padding: 0px; overflow: hidden" data-dojo-type="dojox.grid.DataGrid">
@@ -30,7 +24,7 @@
                 <div id="${id}EdgesGrid" title="Edges" style="padding: 0px; overflow: hidden" data-dojo-type="dojox.grid.DataGrid">
                 </div>
             </div>
-            <div id="${id}LocalTabContainer" class="edgePanel" style="height: 66%" data-dojo-props="region: 'bottom', splitter:true, minSize: 120, tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
+            <div id="${id}LocalTabContainer" style="height: 66%" data-dojo-props="region: 'bottom', splitter:true, minSize: 120, tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
                 <div id="${id}LocalGraphWidget" title="Local" data-dojo-type="GraphWidget">
                     <div id="${id}LocalSync" data-dojo-attach-event="onClick:_onLocalSync" data-dojo-type="dijit.form.Button">Recalculate</div>
                 </div>

+ 4 - 0
esp/files/templates/InfoGridWidget.html

@@ -0,0 +1,4 @@
+<div class="${baseClass}">
+    <div id="${id}InfoGrid" style="width: 100%; height: 100%" data-dojo-type="dojox.grid.DataGrid">
+    </div>
+</div>

+ 13 - 0
esp/files/templates/LogsWidget.html

@@ -0,0 +1,13 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%" data-dojo-props="splitter: false"data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}Toolbar" class="topPanel" style="padding: 0px; overflow: hidden" data-dojo-props="region: 'top'" data-dojo-type="dijit.Toolbar">
+            <span class="bold">Download:</span>
+            <div data-dojo-attach-event="onClick:_onDownload" data-dojo-type="dijit.form.Button">File</div>
+            <div data-dojo-attach-event="onClick:_onDownloadZip" data-dojo-type="dijit.form.Button">Zip</div>
+            <div data-dojo-attach-event="onClick:_onDownloadGZip" data-dojo-type="dijit.form.Button">GZip</div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+        </div>
+        <div id="${id}CenterPane" style="padding: 0px; overflow: hidden" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
+        </div>
+    </div>
+</div>

+ 11 - 1
esp/files/templates/ResultsWidget.html

@@ -1,4 +1,14 @@
 <div class="${baseClass}">
-    <div id="${id}ResultsPane" style="width: 100%; height: 100%" data-dojo-props="tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%" data-dojo-props="splitter: false"data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}Toolbar" class="topPanel" style="padding: 0px; overflow: hidden" data-dojo-props="region: 'top'" data-dojo-type="dijit.Toolbar">
+            <span class="bold">Download:</span>
+            <div data-dojo-attach-event="onClick:_onDownloadZip" data-dojo-type="dijit.form.Button">Zip</div>
+            <div data-dojo-attach-event="onClick:_onDownloadGZip" data-dojo-type="dijit.form.Button">GZip</div>
+            <div data-dojo-attach-event="onClick:_onDownloadXLS" data-dojo-type="dijit.form.Button">XLS</div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div data-dojo-attach-event="onClick:_onFileDetails" data-dojo-type="dijit.form.Button">File Details</div>
+        </div>
+        <div id="${id}ResultsPane" style="padding: 0px; overflow: hidden" data-dojo-props="region: 'center', tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
+        </div>
     </div>
 </div>

+ 1 - 1
esp/files/templates/TargetSelectWidget.html

@@ -1,5 +1,5 @@
 <div class="${baseClass}">
-    <label id="${id}TargetSelectLabel" for="${id}TargetSelect">Target:</label>
+    <!--<label id="${id}TargetSelectLabel" for="${id}TargetSelect">Target:</label>-->
     <div id="${id}TargetSelect" data-dojo-type="dijit.form.Select">
     </div>
 </div>

+ 10 - 0
esp/files/templates/TimingGridWidget.html

@@ -0,0 +1,10 @@
+<div class="${baseClass}">
+    <table id="${id}TimingGrid" title="Timings" style="width: 100%; height: 100%" data-dojo-type="dojox.grid.DataGrid">
+        <thead>
+            <tr>
+                <th field="Name" width="auto">Component</th>
+                <th field="Seconds" width="auto">Time (Seconds)</th>
+            </tr>
+        </thead>
+    </table>
+</div>

+ 8 - 0
esp/files/templates/TimingPageWidget.html

@@ -0,0 +1,8 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}Grid" data-dojo-props="region: 'center'" data-dojo-type="TimingGridWidget">
+        </div>
+        <div id="${id}TreeMap" style="width: 33%" data-dojo-props="region: 'right', splitter:true, minSize: 120" data-dojo-type="TimingTreeMapWidget">
+        </div>
+    </div>
+</div>

+ 3 - 4
esp/files/templates/TimingTreeMapWidget.html

@@ -1,7 +1,6 @@
 <div class="${baseClass}">
-    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%; padding: 0px; overflow: hidden" data-dojo-props="splitter: false, gutters:false" data-dojo-type="dijit.layout.BorderContainer">
-		<link rel="stylesheet" href="dojox/treemap/themes/TreeMap.css">
-        <div id="${id}TimingContentPane" class="${baseClass}TimingContentPane"  data-dojo-props="region: 'center'" data-dojo-type="dojox.treemap.TreeMap">
-        </div>
+    <div id="${id}ContentPane" style="width: 100%; height: 100%; padding: 0px; overflow: hidden" data-dojo-type="dijit.layout.ContentPane">
+	    <link rel="stylesheet" href="dojox/treemap/themes/TreeMap.css">
+        <div id="${id}TreeMap" data-dojo-type="dojox.treemap.TreeMap"></div>
     </div>
 </div>

+ 97 - 0
esp/files/templates/WUDetailsWidget.html

@@ -0,0 +1,97 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}Toolbar" class="topPanel" data-dojo-props="region: 'top'" data-dojo-type="dijit.Toolbar">
+            <img id="showProtectedImage" class="iconAlign" src="img/unlocked.png"/>&nbsp<img id="showStateIdImage" class="iconAlign" src="img/workunit.png"/>&nbsp<span id="${id}Wuid" class="bold">WUID</span>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div id="${id}Save" data-dojo-attach-event="onClick:_onSave" data-dojo-type="dijit.form.Button">Save</div>
+            <div id="${id}Delete" data-dojo-attach-event="onClick:_onDelete" data-dojo-type="dijit.form.Button">Delete</div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div id="${id}Abort" data-dojo-attach-event="onClick:_onAbort" data-dojo-type="dijit.form.Button">Abort</div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div id="${id}Clone" data-dojo-attach-event="onClick:_onClone" data-dojo-type="dijit.form.Button">Clone</div>
+            <div id="${id}Resubmit" data-dojo-attach-event="onClick:_onResubmit" data-dojo-type="dijit.form.Button">Resubmit</div>
+            <div id="${id}Restart" data-dojo-attach-event="onClick:_onRestart" data-dojo-type="dijit.form.Button">Restart</div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div id="${id}Publish" data-dojo-type="dijit.form.DropDownButton">
+                <span>Publish</span>
+                <div data-dojo-type="dijit.TooltipDialog">
+                    <label class="Prompt" for="showJobName2">Job Name:</label>
+                    <input id="showJobName2" data-dojo-props="trim: true" data-dojo-type="dijit.form.TextBox"/>
+                    <button data-dojo-attach-event="onClick:_onPublish" data-dojo-type="dijit.form.Button">Submit</button>
+                </div>
+            </div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div id="${id}Reload" data-dojo-attach-event="onClick:_onReload" data-dojo-props="iconClass:'iconRefresh'" data-dojo-type="dijit.form.Button">Refresh</div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+        </div>
+        <div id="${id}TabContainer" data-dojo-props="region: 'center', tabPosition: 'top'" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.TabContainer">
+            <div title="Summary" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.BorderContainer">
+                <div id="${id}Summary" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
+                    <h2>
+                        <img id="showProtectedImage2" class="iconAlign" src="img/locked.png"/>&nbsp<img id="showStateIdImage2" class="iconAlign" src="img/workunit.png"/>&nbsp<span id="${id}Wuid2" class="bold">WUID</span>
+                    </h2>
+                    <table>
+                        <tr>
+                            <td><span class="Prompt">Action: </span></td>
+                            <td id="showAction"></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">State:</span></td>
+                            <td id="showState"></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">Owner:</span></td>
+                            <td id="showOwner"></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">Scope: </span></td>
+                            <td id="showScope"></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">Job Name:</span></td>
+                            <td><input id="showJobName" style="width: 220px" data-dojo-props="trim:true" data-dojo-type="dijit.form.TextBox"/></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">Description:</span></td>
+                            <td><input id="showDescription" style="width: 220px" data-dojo-props="trim:true" data-dojo-type="dijit.form.TextBox"/></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">Protected:</span></td>
+                            <td><input id="showProtected" data-dojo-type="dijit.form.CheckBox"/></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">Cluster:</span></td>
+                            <td id="showCluster"></td>
+                        </tr>
+                        <tr>
+                            <td><span class="Prompt">Duration:</span></td>
+                            <td id="showTime"></td>
+                        </tr>
+                    </table>
+                </div>
+                <div id="${id}InfoContainer" style="height: 33%" data-dojo-props="region: 'bottom', splitter: true, minSize: 120" data-dojo-type="InfoGridWidget">
+                </div>
+            </div>
+            <div id="${id}Variables" title="Variables" data-dojo-type="dijit.layout.ContentPane">
+                <div id="${id}VariablesGrid" data-dojo-type="dojox.grid.DataGrid">
+                </div>
+            </div>
+            <div id="${id}Results" title="Outputs" data-dojo-type="ResultsWidget">
+            </div>
+            <div id="${id}Files" title="Inputs" data-dojo-type="ResultsWidget">
+            </div>
+            <div id="${id}Timers" title="Timings" data-dojo-type="TimingPageWidget">
+            </div>
+            <div id="${id}Graphs" title="Graphs" data-dojo-type="GraphPageWidget">
+            </div>
+            <div id="${id}Source" title="ECL" data-dojo-type="ECLSourceWidget">
+            </div>
+            <div id="${id}Logs" title="Helpers" data-dojo-type="LogsWidget">
+            </div>
+            <div id="${id}Playground" title="Playground" data-dojo-type="ECLPlaygroundWidget">
+            </div>
+            <div id="${id}XML" title="XML" data-dojo-props="WUXml: true" data-dojo-type="ECLSourceWidget">
+            </div>
+        </div>
+    </div>
+</div>