Jelajahi Sumber

Merge branch 'closedown-5.0.x'

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 10 tahun lalu
induk
melakukan
e20e915fe9
45 mengubah file dengan 488 tambahan dan 241 penghapusan
  1. 9 1
      common/workunit/workunit.cpp
  2. 2 1
      dali/base/dautils.cpp
  3. 10 2
      ecl/eclagent/eclagent.cpp
  4. 56 19
      esp/eclwatch/ws_XSLT/dropzonefile.xslt
  5. 5 5
      esp/eclwatch/ws_XSLT/index.xslt
  6. 3 5
      esp/services/ws_fs/ws_fsService.cpp
  7. 5 5
      esp/src/eclwatch/ActivityWidget.js
  8. 2 2
      esp/src/eclwatch/DFUQueryWidget.js
  9. 6 6
      esp/src/eclwatch/DFUWUDetailsWidget.js
  10. 4 1
      esp/src/eclwatch/ESPDFUWorkunit.js
  11. 3 0
      esp/src/eclwatch/ESPLogicalFile.js
  12. 31 0
      esp/src/eclwatch/ESPWorkunit.js
  13. 2 2
      esp/src/eclwatch/EventScheduleWorkunitWidget.js
  14. 2 2
      esp/src/eclwatch/GetDFUWorkunitsWidget.js
  15. 18 3
      esp/src/eclwatch/GraphPageWidget.js
  16. 8 1
      esp/src/eclwatch/GraphTreeWidget.js
  17. 50 0
      esp/src/eclwatch/GraphWidget.js
  18. 2 2
      esp/src/eclwatch/GraphsWidget.js
  19. 19 4
      esp/src/eclwatch/LFDetailsWidget.js
  20. 3 1
      esp/src/eclwatch/LZBrowseWidget.js
  21. 7 7
      esp/src/eclwatch/LogsWidget.js
  22. 0 7
      esp/src/eclwatch/QuerySetErrorsWidget.js
  23. 4 4
      esp/src/eclwatch/QuerySetQueryWidget.js
  24. 2 2
      esp/src/eclwatch/ResourcesWidget.js
  25. 6 6
      esp/src/eclwatch/ResultsWidget.js
  26. 2 2
      esp/src/eclwatch/SearchResultsWidget.js
  27. 2 2
      esp/src/eclwatch/SourceFilesWidget.js
  28. 3 2
      esp/src/eclwatch/TimingPageWidget.js
  29. 16 11
      esp/src/eclwatch/TimingTreeMapWidget.js
  30. 0 18
      esp/src/eclwatch/UserQueryWidget.js
  31. 16 28
      esp/src/eclwatch/WUDetailsWidget.js
  32. 2 2
      esp/src/eclwatch/WUQueryWidget.js
  33. 6 0
      esp/src/eclwatch/nls/hpcc.js
  34. 11 11
      esp/src/eclwatch/templates/DFUQueryWidget.html
  35. 5 5
      esp/src/eclwatch/templates/GraphWidget.html
  36. 44 19
      esp/src/eclwatch/templates/LFDetailsWidget.html
  37. 10 10
      esp/src/eclwatch/templates/LZBrowseWidget.html
  38. 18 17
      initfiles/examples/embed/mysql-simple.ecl
  39. 1 1
      roxie/ccd/ccdfile.cpp
  40. 17 5
      system/jlib/jmutex.cpp
  41. 1 1
      system/jlib/jsocket.cpp
  42. 24 10
      testing/regress/README.rst
  43. 29 9
      testing/regress/ecl-test
  44. 6 0
      testing/regress/ecl-test.json
  45. 16 0
      testing/regress/hpcc/util/util.py

+ 9 - 1
common/workunit/workunit.cpp

@@ -2197,8 +2197,16 @@ public:
     {
         StringBuffer wuidStr(wuid);
         wuidStr.trim();
-        if (!wuidStr.length())
+        if (wuidStr.length() && ('w' == wuidStr.charAt(0)))
+            wuidStr.setCharAt(0, 'W');
+
+        if (!wuidStr.length() || ('W' != wuidStr.charAt(0)))
+        {
+            if (workUnitTraceLevel > 1)
+                PrintLog("openWorkUnit %s invalid WUID", nullText(wuidStr.str()));
+
             return NULL;
+        }
 
         if (workUnitTraceLevel > 1)
             PrintLog("openWorkUnit %s", wuidStr.str());

+ 2 - 1
dali/base/dautils.cpp

@@ -1992,7 +1992,8 @@ IRemoteConnection *getElementsPaged( IElementsPager *elementsPager,
     if (hint&&*hint)
     {
         elem.setown(QUERYINTERFACE(pagedElementsCache->get(owner,*hint),CPECacheElem)); // NB: removes from cache in process, added back at end
-        postfilter = elem->postFilter; // reuse cached postfilter
+        if (elem)
+            postfilter = elem->postFilter; // reuse cached postfilter
     }
     else
     {

+ 10 - 2
ecl/eclagent/eclagent.cpp

@@ -731,10 +731,18 @@ IConstWUResult *EclAgent::getResultForGet(const char *name, unsigned sequence)
 
 IConstWUResult *EclAgent::getExternalResult(const char * wuid, const char *name, unsigned sequence)
 {
+    LOG(MCsetresult, unknownJob, "EclAgent::getExternalResult(wuid:'%s',name='%s',sequence:%d)", nullText(wuid), nullText(name), sequence);
     Owned<IWorkUnitFactory> factory = getWorkUnitFactory();
     Owned<IConstWorkUnit> externalWU = factory->openWorkUnit(wuid, false);
-    externalWU->remoteCheckAccess(queryUserDescriptor(), false);
-    return getWorkUnitResult(externalWU, name, sequence);
+    if (externalWU)
+    {
+        externalWU->remoteCheckAccess(queryUserDescriptor(), false);
+        return getWorkUnitResult(externalWU, name, sequence);
+    }
+    else
+    {
+        fail(0, "Missing or invalid workunit name in getExternalResult()");
+    }
 }
 
 void EclAgent::outputFormattedResult(const char * name, unsigned sequence, bool close)

+ 56 - 19
esp/eclwatch/ws_XSLT/dropzonefile.xslt

@@ -38,6 +38,7 @@
         <title>EclWatch</title>
         <script language="JavaScript1.2">
           var initialPath = '<xsl:value-of select="/DropZoneFilesResponse/Path"/>';
+          var currentNetAddress = '<xsl:value-of select="/DropZoneFilesResponse/NetAddress"/>';
           <xsl:text disable-output-escaping="yes"><![CDATA[
             var intervalId = 0;
             var hideLoading = 1;
@@ -45,7 +46,7 @@
             function onChangeMachine(resetPath)
             {
               machineDropDown = document.forms['DropZoneForm'].machine;
-              if (machineDropDown.selectedIndex >=0)
+              if (machineDropDown.selectedIndex > 0)
               {
                 selected=machineDropDown.options[machineDropDown.selectedIndex];               
                 document.forms['DropZoneForm'].NetAddress.value=selected.value;
@@ -65,6 +66,18 @@
 
                 document.location.href = "/FileSpray/DropZoneFiles?NetAddress=" + selected.value + "&OS=" + sourceOS+ "&Path=" + directory;
               }
+              else
+              {
+                document.forms['DropZoneForm'].NetAddress.value = "";
+                document.forms['DropZoneForm'].Directory.value = "";
+                var chooseFileButton = document.getElementById('FilesToUpload');
+                var uploadBtn = document.getElementById('UploadBtn');
+                if (chooseFileButton)
+                  chooseFileButton.disabled =  true;
+                if (uploadBtn)
+                  uploadBtn.disabled =  true;
+                document.forms['DropZoneFileForm'].style.display="none";
+              }
             }         
 
             function doBlink() 
@@ -183,12 +196,26 @@
             function getSelectedDropZone()
             {
               machineDropDown = document.forms['DropZoneForm'].machine;
-              if (machineDropDown.selectedIndex >=0)
+              if ((machineDropDown.selectedIndex < 1) && (machineDropDown.length > 1) && (currentNetAddress != ""))
+              {
+                for (i=1; i<machineDropDown.length; i++)
+                {
+                  if (machineDropDown.options[i].value == currentNetAddress)
+                  {
+                    machineDropDown.selectedIndex = i;
+                    break;
+                  }
+                }
+              }
+              if (machineDropDown.selectedIndex > 0)
               {
                 selected=machineDropDown.options[machineDropDown.selectedIndex];               
                 document.forms['DropZoneForm'].NetAddress.value=selected.value;
                 pos = selected.title.indexOf(';');
-                directory = selected.title.substring(0, pos);
+                if (initialPath == '')
+                  directory = selected.title.substring(0, pos);
+                else
+                  directory = initialPath;
                 linux = selected.title.substring(pos+1);
                 sourceOS = linux == 'true' ? 1: 0;
 
@@ -200,6 +227,16 @@
 
                 url0 = "/FileSpray/UploadFile?upload_&NetAddress=" + selected.value + "&OS=" + sourceOS + "&Path=" + directory;
               }
+              else
+              {
+                document.forms['DropZoneForm'].Directory.value = "";
+                var chooseFileButton = document.getElementById('FilesToUpload');
+                var uploadBtn = document.getElementById('UploadBtn');
+                if (chooseFileButton)
+                  chooseFileButton.disabled =  true;
+                if (uploadBtn)
+                  uploadBtn.disabled =  true;
+              }
             }
 
             function onLoad()
@@ -207,9 +244,6 @@
               initSelection('resultsTable');
 
               getSelectedDropZone();
-              document.forms['DropZoneForm'].Directory.value = initialPath;
-              document.forms['DropZoneFileForm'].Path.value = initialPath;
-              url0 = "/FileSpray/UploadFile?upload_&NetAddress=" + selected.value + "&OS=" + sourceOS + "&Path=" + initialPath;
             }   
           ]]></xsl:text>
         </script>
@@ -232,6 +266,7 @@
                         <td>Machine/dropzone:</td>
                         <td>
                           <select name="machine" id="machine" onchange="onChangeMachine(true)" onblur="onChangeMachine(true)">
+                            <option>(Select a dropzone.)</option>
                             <xsl:for-each select="DropZones/DropZone">
                               <option>
                                 <xsl:variable name="curip" select="NetAddress"/>
@@ -284,19 +319,21 @@
               </tr>
               <tr>
                 <td>
-                  <form id="DropZoneFileForm" action="/FileSpray/DeleteDropZoneFiles" method="post">
-                    <input type="hidden" name="NetAddress" value="{$netAddress0}"/>
-                    <input type="hidden" name="Path" value="{$path0}"/>
-                    <input type="hidden" name="OS" value="{$os0}"/>
-                    <xsl:choose>
-                      <xsl:when test="not(Files/PhysicalFileStruct[1])">
-                        <br/><br/>No file found in this dropzone.<br/><br/>
-                      </xsl:when>
-                      <xsl:otherwise>
-                        <xsl:apply-templates select="Files"/>
-                      </xsl:otherwise>
-                    </xsl:choose>
-                  </form>
+                  <xsl:if test="string-length($netAddress0)">
+                    <form id="DropZoneFileForm" action="/FileSpray/DeleteDropZoneFiles" method="post">
+                      <input type="hidden" name="NetAddress" value="{$netAddress0}"/>
+                      <input type="hidden" name="Path" value="{$path0}"/>
+                      <input type="hidden" name="OS" value="{$os0}"/>
+                      <xsl:choose>
+                        <xsl:when test="not(Files/PhysicalFileStruct[1])">
+                          <br/><br/>No file found in this dropzone.<br/><br/>
+                        </xsl:when>
+                        <xsl:otherwise>
+                          <xsl:apply-templates select="Files"/>
+                        </xsl:otherwise>
+                      </xsl:choose>
+                    </form>
+                  </xsl:if>
                 </td>
               </tr>
             </table>

+ 5 - 5
esp/eclwatch/ws_XSLT/index.xslt

@@ -889,8 +889,8 @@
         <xsl:param name="queue"/>
         <xsl:param name="thorlcr" select="'0'"/>
 
-        <xsl:variable name="active1" select="$workunits[State='running' and $queue = QueueName]"/>
-        <xsl:variable name="active2" select="$workunits[State='running' and $queue != QueueName]"/>
+        <xsl:variable name="active1" select="$workunits[starts-with(State,'running') and $queue = QueueName]"/>
+        <xsl:variable name="active2" select="$workunits[starts-with(State,'running') and $queue != QueueName]"/>
         <tbody>
             <xsl:choose>
                 <xsl:when test="$workunits[1]">
@@ -938,7 +938,7 @@
                         </tr>
                     </xsl:if>
 
-                    <xsl:for-each select="$workunits[State!='running']">
+                    <xsl:for-each select="$workunits[not(starts-with(State,'running'))]">
                         <tr>
                             <xsl:apply-templates select=".">
                                 <xsl:with-param name="cluster" select="$cluster"/>
@@ -1017,7 +1017,7 @@
         <xsl:variable name="active">
             <xsl:choose>
                 <xsl:when test="Priority='high'">highpriority</xsl:when>
-                <xsl:when test="State='running'">active</xsl:when>
+                <xsl:when test="starts-with(State,'running')">active</xsl:when>
                 <xsl:when test="Priority='normal'">normalpriority</xsl:when>
                 <xsl:when test="Priority='low'">lowpriority</xsl:when>
                 <xsl:otherwise></xsl:otherwise>
@@ -1044,7 +1044,7 @@
             </xsl:variable>
             <a href="javascript:go('{$href-method}?{$href-wuidparam}={Wuid}')">
                 <xsl:choose>
-                    <xsl:when test="State='running'">
+                    <xsl:when test="starts-with(State,'running')">
                         <b>
                             <xsl:value-of select="Wuid"/>
                         </b>

+ 3 - 5
esp/services/ws_fs/ws_fsService.cpp

@@ -3038,11 +3038,6 @@ bool CFileSprayEx::onDropZoneFiles(IEspContext &context, IEspDropZoneFilesReques
                 aDropZone->setPath(dir.str());
                 aDropZone->setName(pszName);
                 aDropZone->setNetAddress(sNetAddr.str());
-                if (netAddressStr.length() < 1)
-                {
-                    netAddressStr = sNetAddr;
-                    directoryStr = dir;
-                }
 
                 dropZoneList.append(*aDropZone.getClear());
             }
@@ -3051,6 +3046,9 @@ bool CFileSprayEx::onDropZoneFiles(IEspContext &context, IEspDropZoneFilesReques
         if (dropZoneList.ordinality())
             resp.setDropZones(dropZoneList);
 
+        if (netAddressStr.length() < 1)
+            return true;
+
         char pathSep = '/';
         if (osStr && *osStr)
         {

+ 5 - 5
esp/src/eclwatch/ActivityWidget.js

@@ -290,12 +290,12 @@ define([
                             var img = row.getStateImage();
                             if (context.activity.isInstanceOfQueue(row)) {
                                 if (row.ClusterType === 3) {
-                                    return "<img src='" + img + "'/>&nbsp;<a href='#' class='" + context.id + "RowClick'>" + _name + "</a>";
+                                    return "<img src='" + img + "'/>&nbsp;<a href='#' class='dgrid-row-url'>" + _name + "</a>";
                                 } else {
                                     return "<img src='" + img + "'/>&nbsp;" + _name;
                                 }
                             }
-                            return "<img src='" + img + "'/>&nbsp;<a href='#' class='" + context.id + "RowClick'>" + row.Wuid + "</a>";
+                            return "<img src='" + img + "'/>&nbsp;<a href='#' class='dgrid-row-url'>" + row.Wuid + "</a>";
                         }
                     }),
                     GID: {
@@ -303,7 +303,7 @@ define([
                         formatter: function (_gid, row) {
                             if (context.activity.isInstanceOfWorkunit(row)) {
                                 if (row.GraphName) {
-                                    return "<a href='#' class='" + context.id + "GraphClick'>" + row.GraphName + "-" + row.GID + "</a>";
+                                    return "<a href='#' class='dgrid-row-url2'>" + row.GraphName + "-" + row.GID + "</a>";
                                 }
                             }
                             return "";
@@ -339,7 +339,7 @@ define([
                 }
             }, domID);
 
-            on(document, "." + this.id + "RowClick:click", function (evt) {
+            retVal.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = retVal.row(evt).data;
                     context._onRowDblClick(row, {
@@ -348,7 +348,7 @@ define([
                 }
             });
 
-            on(document, "." + this.id + "GraphClick:click", function (evt) {
+            retVal.on(".dgrid-row-url2:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = retVal.row(evt).data;
                     context._onRowDblClick(row, {

+ 2 - 2
esp/src/eclwatch/DFUQueryWidget.js

@@ -438,7 +438,7 @@ define([
                             if (row.__hpcc_isDir) {
                                 return name;
                             }
-                            return (row.getStateImageHTML ? row.getStateImageHTML() + "&nbsp;" : "") + "<a href='#' rowIndex=" + row + " class='" + context.id + "LogicalNameClick'>" + name + "</a>";
+                            return (row.getStateImageHTML ? row.getStateImageHTML() + "&nbsp;" : "") + "<a href='#' class='dgrid-row-url'>" + name + "</a>";
                         },
                         renderExpando: function (level, hasChildren, expanded, object) {
                             var dir = this.grid.isRTL ? "right" : "left";
@@ -462,7 +462,7 @@ define([
             }, this.id + "WorkunitsGrid");
 
             var context = this;
-            on(document, "." + context.id + "LogicalNameClick:click", function (evt) {
+            this.workunitsGrid.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var item = context.workunitsGrid.row(evt).data;
                     context._onRowDblClick(item);

+ 6 - 6
esp/src/eclwatch/DFUWUDetailsWidget.js

@@ -283,13 +283,13 @@ define([
 
         refreshActionState: function () {
             this.setDisabled(this.id + "AutoRefresh", this.wu.isComplete(), "iconAutoRefresh", "iconAutoRefreshDisabled");
-            registry.byId(this.id + "Save").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 + "Save").set("disabled", !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Delete").set("disabled", !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Abort").set("disabled", this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Resubmit").set("disabled", !this.wu.isComplete() || this.wu.isDeleted());
             registry.byId(this.id + "Modify").set("disabled", true);  //TODO
-            registry.byId(this.id + "JobName").set("readOnly", !this.wu.isComplete());
-            registry.byId(this.id + "isProtected").set("readOnly", !this.wu.isComplete());
+            registry.byId(this.id + "JobName").set("readOnly", !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "isProtected").set("readOnly", !this.wu.isComplete() || this.wu.isDeleted());
 
             this.summaryWidget.set("iconClass", this.wu.getStateIconClass());
             dom.byId(this.id + "StateIdImage").src = this.wu.getStateImage();

+ 4 - 1
esp/src/eclwatch/ESPDFUWorkunit.js

@@ -154,6 +154,9 @@ define([
         isComplete: function () {
             return this.hasCompleted;
         },
+        isDeleted: function () {
+            return this.State === 999;
+        },
         monitor: function (callback) {
             if (callback) {
                 callback(this);
@@ -257,7 +260,7 @@ define([
             return dojoConfig.getImageURL("unlocked.png");
         },
         getStateIconClass: function () {
-            switch (this.StateID) {
+            switch (this.State) {
                 case 1:
                     return "iconWarning";
                 case 2:

+ 3 - 0
esp/src/eclwatch/ESPLogicalFile.js

@@ -380,6 +380,9 @@ define([
         },
         getStateImageHTML: function () {
             return dojoConfig.getImageHTML(this.getStateImageName());
+        },
+        isDeleted: function () {
+            return this.StateID === 999;
         }
     });
 

+ 31 - 0
esp/src/eclwatch/ESPWorkunit.js

@@ -171,6 +171,34 @@ define([
         _GraphsSetter: function (Graphs) {
             this.set("graphs", Graphs.ECLGraph);
         },
+
+        //  Calculated "Helpers"  ---
+        _HelpersSetter: function (Helpers) {
+            this.set("helpers", Helpers.ECLHelpFile);
+            this.refreshHelpersCount();
+        },
+        _ThorLogListSetter: function (ThorLogList) {
+            this.set("thorLogInfo", ThorLogList.ThorLogInfo);
+            this.refreshHelpersCount();
+        },
+        _HasArchiveQuerySetter: function (HasArchiveQuery) {
+            this.set("hasArchiveQuery", HasArchiveQuery);
+            this.refreshHelpersCount();
+        },
+        refreshHelpersCount: function () {
+            var helpersCount = 2;   //  ECL + Workunit XML are also helpers...
+            if (this.helpers) {
+                helpersCount += this.helpers.length;
+            }
+            if (this.thorLogList) {
+                helpersCount += this.thorLogList.length;
+            }
+            if (this.hasArchiveQuery) {
+                helpersCount += 1;
+            }
+            this.set("helpersCount", helpersCount);
+        },
+
         //  ---  ---  ---
         onCreate: function () {
         },
@@ -188,6 +216,9 @@ define([
         isComplete: function () {
             return this.hasCompleted;
         },
+        isDeleted: function () {
+            return this.StateID === 999;
+        },
         monitor: function (callback) {
             if (callback) {
                 callback(this);

+ 2 - 2
esp/src/eclwatch/EventScheduleWorkunitWidget.js

@@ -140,7 +140,7 @@ define([
                     Wuid: {
                         label: this.i18n.Workunit, width: 180, sortable: true,
                         formatter: function (Wuid) {
-                            return "<a href='#' class='" + context.id + "WuidClick'>" + Wuid + "</a>";
+                            return "<a href='#' class='dgrid-row-url'>" + Wuid + "</a>";
                         }
                     },
                     Cluster: { label: this.i18n.Cluster, width: 100, sortable: true },
@@ -152,7 +152,7 @@ define([
                 }
             }, this.id + "EventGrid");
 
-            on(document, "." + context.id + "WuidClick:click", function (evt) {
+            this.eventGrid.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var item = context.eventGrid.row(evt).data;
                     context._onRowDblClick(item);

+ 2 - 2
esp/src/eclwatch/GetDFUWorkunitsWidget.js

@@ -333,7 +333,7 @@ define([
                         width: 180,
                         formatter: function (ID, idx) {
                             var wu = ESPDFUWorkunit.Get(ID);
-                            return "<img src='" + wu.getStateImage() + "'>&nbsp;<a href='#' rowIndex=" + idx + " class='" + context.id + "IDClick'>" + ID + "</a>";
+                            return "<img src='" + wu.getStateImage() + "'>&nbsp;<a href='#' class='dgrid-row-url'>" + ID + "</a>";
                         }
                     },
                     Command: {
@@ -355,7 +355,7 @@ define([
             }, this.id + "WorkunitsGrid");
 
             var context = this;
-            on(document, "." + context.id + "IDClick:click", function (evt) {
+            this.workunitsGrid.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var item = context.workunitsGrid.row(evt).data;
                     context._onRowDblClick(item.ID);

+ 18 - 3
esp/src/eclwatch/GraphPageWidget.js

@@ -150,7 +150,9 @@ define([
             };
             this.overview.onDoubleClick = function (globalID, keyState) {
                 var mainItem = context.main.getItem(globalID);
-                context.main.centerOnItem(mainItem, true);
+                if (mainItem) {
+                    context.main.centerOnItem(mainItem, true);
+                }
             };
 
             this.main = registry.byId(this.id + "MainGraphWidget");
@@ -436,8 +438,15 @@ define([
                         initialSelection = [this.params.SubGraphId];
                     }
                 }
+                var mainRoot = [0];
                 this.setOverviewRootItems([0], initialSelection);
-                this.setMainRootItems(initialSelection);
+                var complexityInfo = this.global.getComplexityInfo();
+                if (complexityInfo.isComplex()) {
+                    if (confirm(lang.replace(this.i18n.ComplexityWarning, complexityInfo) + "\n" + this.i18n.ManualOverviewSelection)) {
+                        mainRoot = [];
+                    }
+                }
+                this.setMainRootItems(mainRoot, initialSelection);
                 this.setLocalRootItems([]);
                 this.loadSubgraphs();
                 this.loadVertices();
@@ -635,7 +644,13 @@ define([
                 }
             }
             if (sourceControl != this.local) {
-                this.setLocalRootItems(selectedGlobalIDs);
+                switch (sourceControl) {
+                    case this.overview:
+                        this.setLocalRootItems([]);
+                        break;
+                    default:
+                        this.setLocalRootItems(selectedGlobalIDs);
+                }
             }
 
             var propertiesDom = dom.byId(this.id + "Properties");

+ 8 - 1
esp/src/eclwatch/GraphTreeWidget.js

@@ -431,7 +431,14 @@ define([
             if (this.global.loadXGMML(xgmml, false, this.graphTimers)) {
                 this.global.setMessage("...");  //  Just in case it decides to render  ---
                 var initialSelection = [];
-                this.setMainRootItems(initialSelection);
+                var mainRoot = [0];
+                var complexityInfo = this.global.getComplexityInfo();
+                if (complexityInfo.isComplex()) {
+                    if (confirm(lang.replace(this.i18n.ComplexityWarning, complexityInfo) + "\n" + this.i18n.ManualTreeSelection)) {
+                        mainRoot = [];
+                    }
+                }
+                this.setMainRootItems(mainRoot, initialSelection);
                 this.loadTree();
                 this.loadSubgraphs();
                 this.loadVertices();

+ 50 - 0
esp/src/eclwatch/GraphWidget.js

@@ -263,6 +263,7 @@ define([
                                         this.graphViewHistory.getLatest().navigateTo(this);
                                     }
                                     deferred.resolve("Layout Complete.");
+                                    this.refreshRootState(context.selectedGlobalIDs);
                                 };
                                 if (this.svg) {
                                     targetGraphWidget.startCachedLayout(this.svg);
@@ -277,6 +278,7 @@ define([
                             targetGraphWidget.setSelectedAsGlobalID(context.selectedGlobalIDs);
                             targetGraphWidget.setMessage("");
                             deferred.resolve("XGMML Did Not Change.");
+                            targetGraphWidget.refreshRootState(context.selectedGlobalIDs);
                         }
                     }));
                 }));
@@ -596,6 +598,35 @@ define([
                 return this.loadXGMML(xgmml, true);
             },
 
+            getTypeSummary: function (gloablIDs) {
+                var retVal = {
+                    Graph: 0,
+                    Cluster: 0,
+                    Vertex: 0,
+                    Edge: 0,
+                    Unknown: 0
+                };
+                arrayUtil.forEach(gloablIDs, function (item) {
+                    retVal[this.getGlobalType(item)]++;
+                }, this);
+                return retVal;
+            },
+
+            getGlobalType: function(globalID) {
+                if (this.hasPlugin()) {
+                    return this._plugin.getGlobalType(this._plugin.getItem(globalID));
+                }
+                return "Unknown";
+            },
+
+            getComplexityInfo: function () {
+                return {
+                    threshold: 200,
+                    activityCount: this.getVertices().length,
+                    isComplex: function () { return this.activityCount > this.threshold; }
+                };
+            },
+
             centerOn: function (globalID) {
                 if (this.hasPlugin()) {
                     var item = this.getItem(globalID);
@@ -1103,6 +1134,13 @@ define([
                 return [];
             },
 
+            getVertices: function () {
+                if (this.hasPlugin()) {
+                    return this._plugin.getVertices();
+                }
+                return [];
+            },
+
             getVerticesWithProperties: function () {
                 if (this.hasPlugin()) {
                     return this._plugin.getVerticesWithProperties();
@@ -1152,6 +1190,18 @@ define([
                 this.setDisabled(this.id + "Next", !this.graphViewHistory.hasNext(), "iconRight", "iconRightDisabled");
                 var selection = this.getSelection();
                 this.setDisabled(this.id + "SyncSelection", !this.getSelection().length, "iconSync", "iconSyncDisabled");
+            },
+
+            refreshRootState: function (selectedGlobalIDs) {
+                var depthDisabled = false;
+                var distanceDisabled = false;
+                if (selectedGlobalIDs) {
+                    var typeSummary = this.getTypeSummary(selectedGlobalIDs);
+                    depthDisabled = !selectedGlobalIDs.length || !(typeSummary.Graph || typeSummary.Cluster);
+                    distanceDisabled = !(typeSummary.Vertex || typeSummary.Edge);
+                }
+                this.setDisabled(this.id + "Depth", depthDisabled);
+                this.setDisabled(this.id + "Distance", distanceDisabled);
             }
         });
     });

+ 2 - 2
esp/src/eclwatch/GraphsWidget.js

@@ -123,7 +123,7 @@ define([
                     Name: {
                         label: this.i18n.Name, width: 72, sortable: true,
                         formatter: function (Name, row) {
-                            return "<a href='#' class='" + context.id + "GraphClick'>" + Name + "</a>";
+                            return "<a href='#' class='dgrid-row-url'>" + Name + "</a>";
                         }
                     },
                     Label: { label: this.i18n.Label, sortable: true },
@@ -146,7 +146,7 @@ define([
                 context.syncSelectionFrom(context.grid);
             });
 
-            on(document, "." + this.id + "GraphClick:click", function (evt) {
+            retVal.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = retVal.row(evt).data;
                     context._onRowDblClick(row);

+ 19 - 4
esp/src/eclwatch/LFDetailsWidget.js

@@ -51,7 +51,8 @@ define([
     "dijit/TooltipDialog",
     "dijit/form/ValidationTextBox",
     "dijit/form/CheckBox",
-    
+    "dijit/Fieldset",
+
     "hpcc/TableContainer"
 
 ], function (exports, declare, lang, i18n, nlsHPCC, arrayUtil, dom, domAttr, domClass, domForm, query,
@@ -153,8 +154,13 @@ define([
         _onDesprayOk: function (event) {
             if (this.desprayForm.validate()) {
                 var context = this;
+                var request = domForm.toObject(this.id + "DesprayForm");
+                if (!context.endsWith(request.destPath, "/")) {
+                    request.destPath += "/";
+                }
+                request.destPath += registry.byId(this.id + "DesprayTargetName").get("value");
                 this.logicalFile.despray({
-                    request: domForm.toObject(this.id + "DesprayForm")
+                    request: request
                 }).then(function (response) {
                     context._handleResponse("DesprayResponse.wuid", response);
                 });
@@ -186,7 +192,8 @@ define([
                 DropZones: true,
                 callback: function (value, item) {
                     context.updateInput("DesprayTargetIPAddress", null, item.machine.Netaddress);
-                    context.updateInput("DesprayTargetPath", null, item.machine.Directory + "/" + context.logicalFile.getLeaf());
+                    context.updateInput("DesprayTargetPath", null, item.machine.Directory);
+                    context.updateInput("DesprayTargetName", null, context.logicalFile.getLeaf());
                 }
             });
         },
@@ -291,6 +298,7 @@ define([
                 this.summaryWidget.set("iconClass", this.logicalFile.getStateIconClass());
                 domClass.remove(this.id + "StateIdImage");
                 domClass.add(this.id + "StateIdImage", this.logicalFile.getStateIconClass());
+                this.refreshActionState();
             }
         },
 
@@ -309,7 +317,14 @@ define([
                 this.addChild(retVal);
             }
             return retVal;
-        }
+        },
 
+        refreshActionState: function () {
+            registry.byId(this.id + "Save").set("disabled", this.logicalFile.isDeleted());
+            registry.byId(this.id + "Delete").set("disabled", this.logicalFile.isDeleted());
+            registry.byId(this.id + "CopyDropDown").set("disabled", this.logicalFile.isDeleted());
+            registry.byId(this.id + "RenameDropDown").set("disabled", this.logicalFile.isDeleted());
+            registry.byId(this.id + "DesprayDropDown").set("disabled", this.logicalFile.isDeleted());
+        }
     });
 });

+ 3 - 1
esp/src/eclwatch/LZBrowseWidget.js

@@ -475,10 +475,12 @@ define([
                         label: this.i18n.Name,
                         collapseOnRefresh: true,
                         sortable: false,
-                        formatter: function (name, row) {
+                        formatter: function (_name, row) {
                             var img = "";
+                            var name = _name;
                             if (row.isDir === undefined) {
                                 img = dojoConfig.getImageHTML("server.png");
+                                name += " [//" + row.NetAddress + row.Path + "]";
                             } else if (row.isDir) {
                                 img = dojoConfig.getImageHTML("folder.png");
                             } else {

+ 7 - 7
esp/src/eclwatch/LogsWidget.js

@@ -152,7 +152,7 @@ define([
                             width: 117,
                             formatter: function (Type, row) {
                                 if (context.canShowContent(Type)) {
-                                    return "<a href='#' rowIndex=" + row.id + " class='" + context.id + "HelperClick'>" + Type + "</a>";
+                                    return "<a href='#' class='dgrid-row-url'>" + Type + "</a>";
                                 }
                                 return Type;
                             }
@@ -166,7 +166,7 @@ define([
                         }
                     }
                 }, domID);
-                on(document, "." + this.id + "HelperClick:click", function (evt) {
+                retVal.on(".dgrid-row-url:click", function (evt) {
                     if (context._onRowDblClick) {
                         var row = context.grid.row(evt).data;
                         context._onRowDblClick(row);
@@ -226,17 +226,17 @@ define([
                             Type: "Workunit XML",
                             FileSize: response.WUXMLSize
                         });
-                        if (response.HasArchiveQuery) {
+                        if (response.hasArchiveQuery) {
                             context.logData.push({
                                 id: "A:0",
                                 Type: "Archive Query"
                             });
                         }
-                        if (response.Helpers && response.Helpers.ECLHelpFile) {
-                            context.loadHelpers(response.Helpers.ECLHelpFile);
+                        if (response.helpers) {
+                            context.loadHelpers(response.helpers);
                         }
-                        if (response.ThorLogList && response.ThorLogList.ThorLogInfo) {
-                            context.loadThorLogInfo(response.ThorLogList.ThorLogInfo);
+                        if (response.thorLogList) {
+                            context.loadThorLogInfo(response.thorLogList);
                         }
                         context.store.setData(context.logData);
                         context.grid.refresh();

+ 0 - 7
esp/src/eclwatch/QuerySetErrorsWidget.js

@@ -58,13 +58,6 @@ define([
                     State: { label: this.i18n.State, width: 108, sortable: false }
                 }
             }, domID);
-
-            on(document, "." + this.id + "WuidClick:click", function (evt) {
-                if (context._onRowDblClick) {
-                    var row = retVal.row(evt).data;
-                    context._onRowDblClick(row);
-                }
-            });
             return retVal;
         },
 

+ 4 - 4
esp/src/eclwatch/QuerySetQueryWidget.js

@@ -345,7 +345,7 @@ define([
                     Id: {
                         label: this.i18n.ID,
                         formatter: function (Id, idx) {
-                            return "<a href='#' rowIndex=" + idx + " class='" + context.id + "IdClick'>" + Id + "</a>";
+                            return "<a href='#' class='dgrid-row-url'>" + Id + "</a>";
                         }
                     },
                     Name: {
@@ -361,7 +361,7 @@ define([
                         width: 180,
                         label: this.i18n.WUID,
                         formatter: function (Wuid, idx) {
-                            return "<a href='#' rowIndex=" + idx + " class='" + context.id + "WuidClick'>" + Wuid + "</a>";
+                            return "<a href='#' class='dgrid-row-url2'>" + Wuid + "</a>";
                         }
                     },
                     Dll: {
@@ -380,13 +380,13 @@ define([
                     }
                 }
             }, this.id + "QuerySetGrid");
-            on(document, "." + context.id + "IdClick:click", function (evt) {
+            this.querySetGrid.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var item = context.querySetGrid.row(evt).data;
                     context._onRowDblClick(item);
                 }
             });
-            on(document, "." + context.id + "WuidClick:click", function (evt) {
+            this.querySetGrid.on(".dgrid-row-url2:click", function (evt) {
                 if (context._onRowDblClick) {
                     var item = context.querySetGrid.row(evt).data;
                     context._onRowDblClick(item, true);

+ 2 - 2
esp/src/eclwatch/ResourcesWidget.js

@@ -87,13 +87,13 @@ define([
                     DisplayPath: {
                         label: this.i18n.Name, sortable: true,
                         formatter: function (url, row) {
-                            return "<a href='#' class='" + context.id + "URLClick'>" + url + "</a>";
+                            return "<a href='#' class='dgrid-row-url'>" + url + "</a>";
                         }
                     }
                 }
             }, domID);
 
-            on(document, "." + this.id + "URLClick:click", function (evt) {
+            retVal.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = retVal.row(evt).data;
                     context._onRowDblClick(row);

+ 6 - 6
esp/src/eclwatch/ResultsWidget.js

@@ -97,13 +97,13 @@ define([
                     Name: {
                         label: this.i18n.Name, width: 180, sortable: true,
                         formatter: function (Name, idx) {
-                            return "<a href='#' rowIndex=" + idx + " class='" + context.id + "ResultClick'>" + Name + "</a>";
+                            return "<a href='#' class='dgrid-row-url'>" + Name + "</a>";
                         }
                     },
                     FileName: {
                         label: this.i18n.FileName, sortable: true,
                         formatter: function (FileName, idx) {
-                            return "<a href='#' rowIndex=" + idx + " class='" + context.id + "FileClick'>" + FileName + "</a>";
+                            return "<a href='#' class='dgrid-row-url2'>" + FileName + "</a>";
                         }
                     },
                     Value: {
@@ -116,7 +116,7 @@ define([
                         formatter: function (ResultViews, idx) {
                             var retVal = "";
                             arrayUtil.forEach(ResultViews, function (item, idx) {
-                                retVal += "<a href='#' viewName=" + encodeURIComponent(item) + " class='" + context.id + "ViewClick'>" + item + "</a>&nbsp;";
+                                retVal += "<a href='#' viewName=" + encodeURIComponent(item) + " class='dgrid-row-url3'>" + item + "</a>&nbsp;";
                             });
                             return retVal;
                         }
@@ -125,19 +125,19 @@ define([
             }, domID);
 
             var context = this;
-            on(document, "." + this.id + "ResultClick:click", function (evt) {
+            retVal.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = context.grid.row(evt).data;
                     context._onRowDblClick(row);
                 }
             });
-            on(document, "." + this.id + "FileClick:click", function (evt) {
+            retVal.on(".dgrid-row-url2:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = context.grid.row(evt).data;
                     context._onRowDblClickFile(row);
                 }
             });
-            on(document, "." + this.id + "ViewClick:click", function (evt) {
+            retVal.on(".dgrid-row-url3:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = context.grid.row(evt).data;
                     context._onRowDblClickView(row, evt.srcElement.getAttribute("viewName"));

+ 2 - 2
esp/src/eclwatch/SearchResultsWidget.js

@@ -132,14 +132,14 @@ define([
                     Summary: {
                         label: this.i18n.Who, sortable: true,
                         formatter: function (summary, idx) {
-                            return "<a href='#' rowIndex=" + idx + " class='" + context.id + "SearchResultClick'>" + summary + "</a>";
+                            return "<a href='#' class='dgrid-row-url'>" + summary + "</a>";
                         }
                     }
                 }
             }, domID);
 
             var context = this;
-            on(document, "." + this.id + "SearchResultClick:click", function (evt) {
+            retVal.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = retVal.row(evt).data;
                     context._onRowDblClick(row);

+ 2 - 2
esp/src/eclwatch/SourceFilesWidget.js

@@ -67,7 +67,7 @@ define([
                     Name: {
                         label: "Name", sortable: true,
                         formatter: function (Name, row) {
-                            return dojoConfig.getImageHTML(row.IsSuperFile ? "folder_table.png" : "file.png") + "&nbsp;<a href='#' rowIndex=" + row + " class='" + context.id + "SourceFileClick'>" + Name + "</a>";
+                            return dojoConfig.getImageHTML(row.IsSuperFile ? "folder_table.png" : "file.png") + "&nbsp;<a href='#' class='dgrid-row-url'>" + Name + "</a>";
                         }
                     },
                     Count: { label: "Usage", width: 72, sortable: true }
@@ -75,7 +75,7 @@ define([
             }, domID);
 
             var context = this;
-            on(document, "." + this.id + "SourceFileClick:click", function (evt) {
+            retVal.on("." + this.id + "dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var row = context.grid.row(evt).data;
                     context._onRowDblClick(row);

+ 3 - 2
esp/src/eclwatch/TimingPageWidget.js

@@ -104,7 +104,7 @@ define([
                             sortable: true,
                             formatter: function (Name, row) {
                                 if (row.GraphName) {
-                                    return "<a href='#' class='" + context.id + "GraphClick'>" + Name + "</a>";
+                                    return "<a href='#' class='dgrid-row-url'>" + Name + "</a>";
                                 }
                                 return Name;
                             }
@@ -117,7 +117,7 @@ define([
                     context.syncSelectionFrom(context.grid);
                 });
 
-                on(document, "." + this.id + "GraphClick:click", function (evt) {
+                retVal.on(".dgrid-row-url:click", function (evt) {
                     if (context._onRowDblClick) {
                         var row = retVal.row(evt).data;
                         context._onRowDblClick(row);
@@ -155,6 +155,7 @@ define([
                         onGetTimers: function (timers) {
                             context.store.setData(timers);
                             context.grid.refresh();
+                            context.timingTreeMap.loadTimers(timers);
                         }
                     });
                 }

+ 16 - 11
esp/src/eclwatch/TimingTreeMapWidget.js

@@ -94,18 +94,15 @@ define([
                 var context = this;
                 if (params.Wuid) {
                     this.wu = ESPWorkunit.Get(params.Wuid);
-
-                    this.wu.fetchTimers(function (timers) {
-                        context.timers = timers;
-                        context.loadTimers(timers, params.query);
+                    var monitorCount = 4;
+                    this.wu.monitor(function () {
+                        if (context.wu.isComplete() || ++monitorCount % 5 == 0) {
+                            context.refreshTreeMap();
+                        }
                     });
                 }
             },
 
-            setQuery: function (query) {
-                this.loadTimers(this.timers, query);
-            },
-
             getSelected: function () {
                 return this.treeMap.selectedItems;
             },
@@ -158,13 +155,21 @@ define([
                 }
             },
 
-            loadTimers: function (timers, query) {
+            refreshTreeMap: function () {
+                var context = this;
+                this.wu.fetchTimers(function (timers) {
+                    context.timers = timers;
+                    context.loadTimers(timers);
+                });
+            },
+
+            loadTimers: function (timers) {
                 this.largestValue = 0;
                 var timerData = [];
                 if (timers) {
                     for (var i = 0; i < timers.length; ++i) {
-                        if (query.graphsOnly) {
-                            if (timers[i].SubGraphId && (query.graphName === "*" || query.graphName === timers[i].GraphName) && (query.subGraphId === "*" || query.subGraphId === timers[i].SubGraphId)) {
+                        if (this.params.query.graphsOnly) {
+                            if (timers[i].SubGraphId && (this.params.query.graphName === "*" || this.params.query.graphName === timers[i].GraphName) && (this.params.query.subGraphId === "*" || this.params.query.subGraphId === timers[i].SubGraphId)) {
                                 timerData.push(lang.mixin({
                                     __hpcc_prefix: timers[i].GraphName
                                 }, timers[i]));

+ 0 - 18
esp/src/eclwatch/UserQueryWidget.js

@@ -394,12 +394,6 @@ define([
                 }
             }, this.id + "GroupsGrid");
             var context = this;
-            on(document, ".WuidClick:click", function (evt) {
-                if (context._onGroupsRowDblClick) {
-                    var item = context.groupsGrid.row(evt).data;
-                    context._onGroupsRowDblClick(item.name);
-                }
-            });
             this.groupsGrid.on(".dgrid-row:dblclick", function (evt) {
                 if (context._onGroupsRowDblClick) {
                     var item = context.groupsGrid.row(evt).data;
@@ -492,12 +486,6 @@ define([
                 }
             }, this.id + "UsersGrid");
             var context = this;
-            on(document, ".WuidClick:click", function (evt) {
-                if (context._onUsersRowDblClick) {
-                    var item = context.usersGrid.row(evt).data;
-                    context._onUsersRowDblClick(item.username,item.fullname,item.passwordexpiration);
-                }
-            });
             this.usersGrid.on(".dgrid-row:dblclick", function (evt) {
                 if (context._onUsersRowDblClick) {
                     var item = context.usersGrid.row(evt).data;
@@ -606,12 +594,6 @@ define([
                 }
             }, this.id + "PermissionsGrid");
             var context = this;
-            on(document, ".WuidClick:click", function (evt) {
-                if (context._onPermissionsRowDblClick) {
-                    var item = context.permissionsGrid.row(evt).data;
-                    context._onPermissionsRowDblClick(item.username, item.fullname, item.passwordexpiration);
-                }
-            });
             this.permissionsGrid.on(".dgrid-row:dblclick", function (evt) {
                 if (context._onPermissionsRowDblClick) {
                     var item = context.permissionsGrid.row(evt).data;

+ 16 - 28
esp/src/eclwatch/WUDetailsWidget.js

@@ -421,6 +421,9 @@ define([
             } else if (name === "resourceURLCount" && newValue) {
                 this.widget._Resources.set("title", this.i18n.Resources + " (" + newValue + ")");
                 this.setDisabled(this.widget._Resources.id, false);
+            } else if (name === "helpersCount" && newValue) {
+                this.logsWidget.set("title", this.i18n.Helpers + " (" + newValue + ")");
+                this.setDisabled(this.logsWidget.id, false);
             } else if (name === "Archived") {
                 this.refreshActionState();
             } else if (name === "StateID") {
@@ -444,19 +447,20 @@ define([
         refreshActionState: function () {
             var isArchived = this.wu.get("Archived");
             this.setDisabled(this.id + "AutoRefresh", isArchived || this.wu.isComplete(), "iconAutoRefresh", "iconAutoRefreshDisabled");
-            registry.byId(this.id + "Save").set("disabled", isArchived || !this.wu.isComplete());
-            registry.byId(this.id + "Delete").set("disabled", isArchived || !this.wu.isComplete());
+            registry.byId(this.id + "Save").set("disabled", isArchived || !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Delete").set("disabled", isArchived || !this.wu.isComplete() || this.wu.isDeleted());
             registry.byId(this.id + "Restore").set("disabled", !isArchived);
-            registry.byId(this.id + "SetToFailed").set("disabled", isArchived || this.wu.isComplete());
-            registry.byId(this.id + "Abort").set("disabled", isArchived || this.wu.isComplete());
-            registry.byId(this.id + "Clone").set("disabled", isArchived || !this.wu.isComplete());
-            registry.byId(this.id + "Resubmit").set("disabled", isArchived || !this.wu.isComplete());
-            registry.byId(this.id + "Recover").set("disabled", isArchived || !this.wu.isComplete());
-            registry.byId(this.id + "Publish").set("disabled", isArchived || !this.wu.isComplete());
-
-            registry.byId(this.id + "Jobname").set("readOnly", !this.wu.isComplete());
-            registry.byId(this.id + "Description").set("readOnly", !this.wu.isComplete());
-            registry.byId(this.id + "Protected").set("readOnly", !this.wu.isComplete());
+            registry.byId(this.id + "SetToFailed").set("disabled", isArchived || this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Abort").set("disabled", isArchived || this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Clone").set("disabled", isArchived || !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Resubmit").set("disabled", isArchived || !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Recover").set("disabled", isArchived || !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Publish").set("disabled", isArchived || !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "ZapReport").set("disabled", this.wu.isDeleted());
+
+            registry.byId(this.id + "Jobname").set("readOnly", !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Description").set("readOnly", !this.wu.isComplete() || this.wu.isDeleted());
+            registry.byId(this.id + "Protected").set("readOnly", !this.wu.isComplete() || this.wu.isDeleted());
 
             this.summaryWidget.set("iconClass", this.wu.getStateIconClass());
             domClass.remove(this.id + "StateIdImage");
@@ -468,22 +472,6 @@ define([
             if (this.wu.isComplete()) {
                 this.wu.getInfo({
                     onGetVariables: function (response) {
-                    },
-
-                    onAfterSend: 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;
-                        }
-                        if (response.HasArchiveQuery) {
-                            helpersCount += 1;
-                        }
-
-                        context.logsWidget.set("title", context.i18n.Helpers + " (" + helpersCount + ")");
-                        context.setDisabled(context.logsWidget.id, false);
                     }
                 });
             }

+ 2 - 2
esp/src/eclwatch/WUQueryWidget.js

@@ -387,7 +387,7 @@ define([
                         label: this.i18n.WUID, width: 180,
                         formatter: function (Wuid, idx) {
                             var wu = ESPWorkunit.Get(Wuid);
-                            return wu.getStateImageHTML() + "&nbsp;<a href='#' rowIndex=" + idx + " class='" + context.id + "WuidClick'>" + Wuid + "</a>";
+                            return wu.getStateImageHTML() + "&nbsp;<a href='#' class='dgrid-row-url'>" + Wuid + "</a>";
                         }
                     },
                     Owner: { label: this.i18n.Owner, width: 90 },
@@ -400,7 +400,7 @@ define([
             }, this.id + "WorkunitsGrid");
 
             var context = this;
-            on(document, "." + context.id + "WuidClick:click", function (evt) {
+            this.workunitsGrid.on(".dgrid-row-url:click", function (evt) {
                 if (context._onRowDblClick) {
                     var item = context.workunitsGrid.row(evt).data;
                     context._onRowDblClick(item.Wuid);

+ 6 - 0
esp/src/eclwatch/nls/hpcc.js

@@ -56,6 +56,7 @@ define({root:
     Command: "Command",
     Comment: "Comment",
     Completed: "Completed",
+    ComplexityWarning: "More than {threshold} activities ({activityCount}) - suppress initial display?",
     Component: "Component",
     Compress: "Compress",
     Compressed: "Compressed",
@@ -92,6 +93,7 @@ define({root:
     DenyRead: "<center>Deny<br>Read</center>",
     DenyWrite: "<center>Deny<br>Write</center>",
     Depth: "Depth",
+    DepthTooltip: "Maximum Subgraph Depth",
     Deschedule: "Deschedule",
     DescheduleSelectedWorkunits: "Deschedule Selected Workunits?",
     Description: "Description",
@@ -104,6 +106,7 @@ define({root:
     DisableScopeScanConfirm: "Are you sure you want to disable Scope Scans?  Changes will revert to configuration settings on DALI reboot.",
     DiskUsage: "Disk Usage",
     Distance: "Distance",
+    DistanceTooltip: "Maximum Activity Neighbourhood Distance",
     Dll: "Dll",
     DOT: "DOT",
     DOTAttributes: "DOT Attributes",
@@ -232,6 +235,8 @@ define({root:
     log_analysis_1: "log_analysis_1*",
     Low: "Low",
     ManualCopy: "Press Ctrl+C",
+    ManualOverviewSelection: "(Manual overview selection will be required)",
+    ManualTreeSelection: "(Manual tree selection will be required)",
     Mappings: "Mappings",
     Mask: "Mask",
     Max: "Max",
@@ -370,6 +375,7 @@ define({root:
     RequestSchema: "Request Schema",
     Reschedule: "Reschedule",
     Reset: "Reset",
+    ResetViewToSelection: "Reset View to Selection",
     Resource: "Resource",
     Resources: "Resources",
     ResponseSchema: "Response Schema",

+ 11 - 11
esp/src/eclwatch/templates/DFUQueryWidget.html

@@ -13,7 +13,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}ImportForm" style="width: 460px;" onsubmit="return false;" data-dojo-props="region: 'bottom'" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Source</legend>
+                                    <legend>${i18n.Source}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input name="sourceDali" title="${i18n.Dali}:" style="width:100%" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
                                         <input name="srcusername" title="${i18n.UserID}:" style="width:100%" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
@@ -22,14 +22,14 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}ImportTargetSelect" title="${i18n.Group}:" name="destGroup" style="width:100%" data-dojo-type="TargetSelectWidget" />
                                         <input name="destLogicalName" title="${i18n.LogicalName}:" style="width:100%" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <input title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
                                         <input title="${i18n.NoSplit}:" name="nosplit" data-dojo-type="dijit.form.CheckBox" />
@@ -51,7 +51,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}CopyForm" style="width: 460px;" onsubmit="return false;" data-dojo-props="region: 'bottom'" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}CopyTargetSelect" title="${i18n.Group}:" name="destGroup" style="width:100%;" data-dojo-type="TargetSelectWidget" style="display: inline-block; vertical-align: middle" />
                                     </div>
@@ -59,7 +59,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}CopyTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
                                         <input id="${id}CopyTargetNoSplit" title="${i18n.NoSplit}:" name="nosplit" data-dojo-type="dijit.form.CheckBox" />
@@ -79,12 +79,12 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}RenameForm" style="width: 460px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div id="${id}RenameGrid" data-dojo-type="SelectionGridWidget">
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:1" data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}RenameTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
                                     </div>
@@ -100,7 +100,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}AddToSuperfileForm" style="width:480px" onsubmit="return false;" data-dojo-type="dijit.form.Form" >
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div class="dijitDialogPaneContentArea" data-dojo-props="cols:1" data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}AddToSuperfileTargetName" name="Superfile" title="${i18n.SuperFile}:" style="width:100%;" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
                                     </div>
@@ -108,7 +108,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div class="dijitDialogPaneContentArea" data-dojo-props="cols:1" data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}AddToSuperfileTargetAppend" name="ExistingFile" title="${i18n.Append}:" data-dojo-type="dijit.form.CheckBox" />
                                     </div>
@@ -124,7 +124,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}DesprayForm" style="width: 460px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}DesprayTargetSelect" title="${i18n.DropZone}:" name="destGroup" style="width: 100%;" data-dojo-type="TargetSelectWidget" />
                                         <input id="${id}DesprayTargetIPAddress" title="${i18n.IPAddress}:" name="destIP" style="width: 100%;" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
@@ -135,7 +135,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}DesprayTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
                                         <input id="${id}DesprayTargetUseSingleConnection" title="${i18n.UseSingleConnection}:" name="SingleConnection" data-dojo-type="dijit.form.CheckBox" />

+ 5 - 5
esp/src/eclwatch/templates/GraphWidget.html

@@ -11,12 +11,12 @@
                 <div data-dojo-attach-event="onClick:_onClickZoomWidth" data-dojo-props="iconClass:'iconZoomWidth', showLabel:false" data-dojo-type="dijit.form.Button">${i18n.ZoomWidth}</div>
                 <span data-dojo-type="dijit.ToolbarSeparator"></span>
                 <span data-dojo-attach-point="containerNode"></span>
-                <div title="${i18n.Depth}" data-dojo-props="iconClass:'iconDepth', showLabel:false, disabled: true" data-dojo-type="dijit.form.Button"></div>
-                <input id="${id}Depth" style="width: 60px" value="2" title="${i18n.Depth}" data-dojo-attach-event="onChange:_onDepthChange" data-dojo-props="placeHolder:'${i18n.Depth}', intermediateChanges:true, constraints:{min:0,max:1000}" data-dojo-type="dijit.form.NumberSpinner" />
-                <div id="${id}DistanceLabel" title="${i18n.Distance}" data-dojo-props="iconClass:'iconDistance', showLabel:false, disabled: true" data-dojo-type="dijit.form.Button"></div>
-                <input id="${id}Distance" style="width: 60px" value="2" title="${i18n.Distance}" data-dojo-attach-event="onChange:_onDistanceChange" data-dojo-props="placeHolder:'${i18n.Depth}', intermediateChanges:true, constraints:{min:0,max:1000}" data-dojo-type="dijit.form.NumberSpinner" />
+                <div title="${i18n.DepthTooltip}" data-dojo-props="iconClass:'iconDepth', showLabel:false, disabled: true" data-dojo-type="dijit.form.Button"></div>
+                <input id="${id}Depth" style="width: 60px" value="2" title="${i18n.DepthTooltip}" data-dojo-attach-event="onChange:_onDepthChange" data-dojo-props="intermediateChanges:true, constraints:{min:0,max:1000}" data-dojo-type="dijit.form.NumberSpinner" />
+                <div id="${id}DistanceLabel" title="${i18n.DistanceTooltip}" data-dojo-props="iconClass:'iconDistance', showLabel:false, disabled: true" data-dojo-type="dijit.form.Button"></div>
+                <input id="${id}Distance" style="width: 60px" value="2" title="${i18n.DistanceTooltip}" data-dojo-attach-event="onChange:_onDistanceChange" data-dojo-props="intermediateChanges:true, constraints:{min:0,max:1000}" data-dojo-type="dijit.form.NumberSpinner" />
                 <span id="${id}SyncSelectionSplitter" data-dojo-type="dijit.ToolbarSeparator"></span>
-                <div id="${id}SyncSelection" data-dojo-attach-event="onClick:_onSyncSelection" data-dojo-props="iconClass:'iconSync', showLabel:false" data-dojo-type="dijit.form.Button">${i18n.SyncSelection}</div>
+                <div id="${id}SyncSelection" data-dojo-attach-event="onClick:_onSyncSelection" data-dojo-props="iconClass:'iconSync', showLabel:false" data-dojo-type="dijit.form.Button">${i18n.ResetViewToSelection}</div>
             </div>
         </div>
         <div id="${id}GraphContentPane" class="${baseClass}GraphContentPane" style="padding: 0px; overflow: hidden; background-color: #707070" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">

+ 44 - 19
esp/src/eclwatch/templates/LFDetailsWidget.html

@@ -12,15 +12,23 @@
                         <span>${i18n.Copy}</span>
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}CopyForm" style="width: 460px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
-                                <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
-                                    <input id="${id}CopyTargetSelect" title="${i18n.Group}:" name="destGroup" colspan="2" style="width:100%;" data-dojo-type="TargetSelectWidget" style="display: inline-block; vertical-align: middle" />
-                                    <input id="${id}CopyTargetName"  title="${i18n.TargetName}:" name="destLogicalName" colspan="2" style="width:100%;" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
-                                    <input id="${id}CopyTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
-                                    <input id="${id}CopyTargetReplicate" title="${i18n.Replicate}:" name="replicate" data-dojo-type="dijit.form.CheckBox" />
-                                    <input id="${id}CopyTargetNoSplit" title="${i18n.NoSplit}:" name="nosplit" data-dojo-type="dijit.form.CheckBox" />
-                                    <input id="${id}CopyTargetCompress" title="${i18n.Compress}:" name="compress" data-dojo-type="dijit.form.CheckBox" />
-                                    <input id="${id}CopyTargetWrap" title="${i18n.Wrap}:" name="Wrap" data-dojo-type="dijit.form.CheckBox" />
-                                    <input id="${id}CopyTargetRetainSuperfileStructure" title="${i18n.RetainSuperfileStructure}:" name="superCopy" data-dojo-type="dijit.form.CheckBox" />
+                                <div data-dojo-type="dijit.Fieldset">
+                                    <legend>${i18n.Target}</legend>
+                                    <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
+                                        <input id="${id}CopyTargetSelect" title="${i18n.Group}:" name="destGroup" colspan="2" style="width:100%;" data-dojo-type="TargetSelectWidget" style="display: inline-block; vertical-align: middle" />
+                                        <input id="${id}CopyTargetName" title="${i18n.TargetName}:" name="destLogicalName" colspan="2" style="width:100%;" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
+                                    </div>
+                                </div>
+                                <div data-dojo-type="dijit.Fieldset">
+                                    <legend>${i18n.Options}</legend>
+                                    <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
+                                        <input id="${id}CopyTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
+                                        <input id="${id}CopyTargetReplicate" title="${i18n.Replicate}:" name="replicate" data-dojo-type="dijit.form.CheckBox" />
+                                        <input id="${id}CopyTargetNoSplit" title="${i18n.NoSplit}:" name="nosplit" data-dojo-type="dijit.form.CheckBox" />
+                                        <input id="${id}CopyTargetCompress" title="${i18n.Compress}:" name="compress" data-dojo-type="dijit.form.CheckBox" />
+                                        <input id="${id}CopyTargetWrap" title="${i18n.Wrap}:" name="Wrap" data-dojo-type="dijit.form.CheckBox" />
+                                        <input id="${id}CopyTargetRetainSuperfileStructure" title="${i18n.RetainSuperfileStructure}:" name="superCopy" data-dojo-type="dijit.form.CheckBox" />
+                                    </div>
                                 </div>
                                 <div class="dijitDialogPaneActionBar">
                                     <button type="submit" data-dojo-attach-event="onClick:_onCopyOk" data-dojo-type="dijit.form.Button">${i18n.Copy}</button>
@@ -32,9 +40,17 @@
                         <span>${i18n.Rename}</span>
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}RenameForm" style="width: 460px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
-                                <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
-                                    <input id="${id}RenameTargetName" title="${i18n.TargetName}:" style="width: 100%;" name="dstname" colspan="2" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
-                                    <input id="${id}RenameTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
+                                <div data-dojo-type="dijit.Fieldset">
+                                    <legend>${i18n.Target}</legend>
+                                    <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
+                                        <input id="${id}RenameTargetName" title="${i18n.TargetName}:" style="width: 100%;" name="dstname" colspan="2" required="true" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
+                                    </div>
+                                </div>
+                                <div data-dojo-type="dijit.Fieldset">
+                                    <legend>${i18n.Options}</legend>
+                                    <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
+                                        <input id="${id}RenameTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
+                                    </div>
                                 </div>
                                 <div class="dijitDialogPaneActionBar">
                                     <button type="submit" data-dojo-attach-event="onClick:_onRenameOk" data-dojo-type="dijit.form.Button">${i18n.Rename}</button>
@@ -46,13 +62,22 @@
                         <span>${i18n.Despray}</span>
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}DesprayForm" style="width: 460px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
-                                <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
-                                    <input id="${id}DesprayTargetSelect" title="${i18n.DropZone}:" name="destGroup" colspan="2" style="width: 100%;" data-dojo-type="TargetSelectWidget" />
-                                    <input id="${id}DesprayTargetIPAddress" title="${i18n.IPAddress}:" name="destIP" colspan="2" required="true" style="width: 100%;" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
-                                    <input id="${id}DesprayTargetPath" title="${i18n.Path}:" name="destPath" colspan="2" required="true" style="width: 100%;" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
-                                    <input id="${id}DesprayTargetSplitPrefix" title="${i18n.SplitPrefix}:" name="splitprefix" colspan="2" style="width: 100%;" data-dojo-props="trim: true" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}DesprayTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
-                                    <input id="${id}DesprayTargetUseSingleConnection" title="${i18n.UseSingleConnection}:" name="SingleConnection" data-dojo-type="dijit.form.CheckBox" />
+                                <div data-dojo-type="dijit.Fieldset">
+                                    <legend>${i18n.Target}</legend>
+                                    <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
+                                        <input id="${id}DesprayTargetSelect" title="${i18n.DropZone}:" name="destGroup" colspan="2" style="width: 100%;" data-dojo-type="TargetSelectWidget" />
+                                        <input id="${id}DesprayTargetIPAddress" title="${i18n.IPAddress}:" name="destIP" colspan="2" required="true" style="width: 100%;" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
+                                        <input id="${id}DesprayTargetPath" title="${i18n.Path}:" name="destPath" colspan="2" required="true" style="width: 100%;" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
+                                        <input id="${id}DesprayTargetName" title="${i18n.TargetName}:" colspan="2" required="true" style="width: 100%;" data-dojo-props="trim: true" data-dojo-type="dijit.form.ValidationTextBox" />
+                                        <input id="${id}DesprayTargetSplitPrefix" title="${i18n.SplitPrefix}:" name="splitprefix" colspan="2" style="width: 100%;" data-dojo-props="trim: true" data-dojo-type="dijit.form.TextBox" />
+                                    </div>
+                                </div>
+                                <div data-dojo-type="dijit.Fieldset">
+                                    <legend>${i18n.Options}</legend>
+                                    <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
+                                        <input id="${id}DesprayTargetOverwrite" title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
+                                        <input id="${id}DesprayTargetUseSingleConnection" title="${i18n.UseSingleConnection}:" name="SingleConnection" data-dojo-type="dijit.form.CheckBox" />
+                                    </div>
                                 </div>
                                 <div class="dijitDialogPaneActionBar">
                                     <button type="submit" data-dojo-attach-event="onClick:_onDesprayOk" data-dojo-type="dijit.form.Button">${i18n.Despray}</button>

+ 10 - 10
esp/src/eclwatch/templates/LZBrowseWidget.html

@@ -33,7 +33,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}SprayFixedForm" style="width: 530px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}SprayFixedDestination" title="${i18n.Group}:" style="width: 95%;" name="destGroup" data-dojo-type="TargetSelectWidget" />
                                         <input id="${id}SprayFixedDestinationNamePrefix" title="${i18n.NamePrefix}:" style="width: 95%;" name="namePrefix" data-dojo-props="trim: true, placeHolder:'${i18n.NamePrefixPlaceholder}'" data-dojo-type="dijit.form.TextBox" />
@@ -42,7 +42,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <input title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />
                                         <input title="${i18n.Replicate}:" name="replicate" data-dojo-type="dijit.form.CheckBox" />
@@ -62,7 +62,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}SprayDelimitedForm" style="width: 530px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}SprayDelimitedDestination" title="${i18n.Group}:" style="width: 95%;" name="destGroup" data-dojo-type="TargetSelectWidget" />
                                         <input title="${i18n.NamePrefix}:" style="width: 95%;" name="namePrefix" data-dojo-props="trim: true, placeHolder:'${i18n.NamePrefixPlaceholder}'" data-dojo-type="dijit.form.TextBox" />
@@ -71,7 +71,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <select id="${id}sourceFormat" title="${i18n.Format}:" name="sourceFormat" colspan="2" data-dojo-type="dijit.form.Select">
                                             <option value="1">ASCII</option>
@@ -110,7 +110,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}SprayXmlForm" style="width: 530px;" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}SprayXmlDestinationSelect" title="${i18n.Group}:" style="width: 95%;" name="destGroup" data-dojo-type="TargetSelectWidget" />
                                         <input title="${i18n.NamePrefix}:" style="width: 95%;" name="namePrefix" data-dojo-props="trim: true, placeHolder:'${i18n.NamePrefixPlaceholder}'" data-dojo-type="dijit.form.TextBox" />
@@ -119,7 +119,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <select id="${id}xmlsourceFormat" title="${i18n.Format}:" name="sourceFormat" colspan="2" data-dojo-type="dijit.form.Select">
                                             <option value="1">ASCII</option>
@@ -151,7 +151,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}SprayVariableForm" style="width: 530px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}SprayVariableDestination" title="${i18n.Group}:" style="width: 95%;" name="destGroup" data-dojo-type="TargetSelectWidget" />
                                         <input title="${i18n.NamePrefix}:" style="width: 95%;" name="namePrefix" data-dojo-props="trim: true, placeHolder:'${i18n.NamePrefixPlaceholder}'" data-dojo-type="dijit.form.TextBox" />
@@ -160,7 +160,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <select title="${i18n.VariableSourceType}:" name="sourceFormat" colspan="2" data-dojo-type="dijit.form.Select">
                                             <option value="recfmv" selected="true">recfmv</option>
@@ -186,7 +186,7 @@
                         <div data-dojo-type="dijit.TooltipDialog">
                             <div id="${id}SprayBlobForm" style="width: 530px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Target</legend>
+                                    <legend>${i18n.Target}</legend>
                                     <div data-dojo-type="hpcc.TableContainer">
                                         <input id="${id}SprayBlobDestination" title="${i18n.Group}:" style="width: 95%;" name="destGroup" data-dojo-type="TargetSelectWidget" />
                                         <input title="${i18n.TargetName}:" style="width: 95%;" name="destLogicalName" data-dojo-props="trim: true, placeHolder:'${i18n.TargetNamePlaceholder}'" required="true" data-dojo-type="dijit.form.ValidationTextBox" />
@@ -195,7 +195,7 @@
                                     </div>
                                 </div>
                                 <div data-dojo-type="dijit.Fieldset">
-                                    <legend>Options</legend>
+                                    <legend>${i18n.Options}</legend>
                                     <div data-dojo-props="cols:2" data-dojo-type="hpcc.TableContainer">
                                         <input title="${i18n.BlobPrefix}:" style="width: 95%;" name="prefix" data-dojo-props="trim: true, placeHolder:'${i18n.PrefixPlaceholder}'" colspan="2" data-dojo-type="dijit.form.TextBox" />
                                         <input title="${i18n.Overwrite}:" name="overwrite" data-dojo-type="dijit.form.CheckBox" />

+ 18 - 17
initfiles/examples/embed/mysql-simple.ecl

@@ -26,24 +26,25 @@ init := DATASET([{'name1', 1, true, 1.2, 3.4, D'aa55aa55', 1234567.89, U'Straße
                  {'name2', 2, false, 5.6, 7.8, D'00', -1234567.89, U'là', U'là'}], childrec);
 
 // Set up the MySQL database
+// Enable remote access to your MySQL DB Server to use its DNS name or IP, else use localhost or 127.0.0.1
 
-drop() := EMBED(mysql : user('rchapman'),database('test'))
+drop() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   DROP TABLE IF EXISTS tbl1;
 ENDEMBED;
 
-create() := EMBED(mysql : user('rchapman'),database('test'))
+create() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   CREATE TABLE tbl1 ( name VARCHAR(20), value INT, boolval TINYINT, r8 DOUBLE, r4 FLOAT, d BLOB, ddd DECIMAL(10,2), u1 VARCHAR(10), u2 VARCHAR(10) );
 ENDEMBED;
 
 // Initialize the MySQL table, passing in the ECL dataset to provide the rows
 
-initialize(dataset(childrec) values) := EMBED(mysql : user('rchapman'),database('test'))
+initialize(dataset(childrec) values) := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   INSERT INTO tbl1 values (?, ?, ?, ?, ?, ?, ?, ?, ?);
 ENDEMBED;
 
 // Add an additional row containing NULL values, to test that they are handled properly when read in ECL
 
-initializeNulls() := EMBED(mysql : user('rchapman'),database('test'))
+initializeNulls() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   INSERT INTO tbl1 (name) values ('nulls');
 ENDEMBED;
 
@@ -55,13 +56,13 @@ ENDEMBED;
 
 // Returning a dataset
 
-dataset(childrec) testMySQLDS() := EMBED(mysql : user('rchapman'),database('test'))
+dataset(childrec) testMySQLDS() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT * from tbl1;
 ENDEMBED;
 
 // Returning a single row
 
-childrec testMySQLRow() := EMBED(mysql : user('rchapman'),database('test'))
+childrec testMySQLRow() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT * from tbl1 LIMIT 1;
 ENDEMBED;
 
@@ -76,45 +77,45 @@ childrec testMySQLParms(
    real4 r4,
    DATA d,
    UTF8 u1,
-   UNICODE8 u2) := EMBED(mysql : user('rchapman'),database('test'))
+   UNICODE8 u2) := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT * from tbl1 WHERE name=? AND value=? AND boolval=? AND r8=? AND r4=? AND d=? AND u1=? AND u2=?;
 ENDEMBED;
 
 // Returning scalars
 
-string testMySQLString() := EMBED(mysql : user('rchapman'),database('test'))
+string testMySQLString() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(name) from tbl1;
 ENDEMBED;
 
-dataset(childrec) testMySQLStringParam(string filter) := EMBED(mysql : user('rchapman'),database('test'))
+dataset(childrec) testMySQLStringParam(string filter) := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT * from tbl1 where name = ?;
 ENDEMBED;
 
-integer testMySQLInt() := EMBED(mysql : user('rchapman'),database('test'))
+integer testMySQLInt() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(value) from tbl1;
 ENDEMBED;
 
-boolean testMySQLBool() := EMBED(mysql : user('rchapman'),database('test'))
+boolean testMySQLBool() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(boolval) from tbl1;
 ENDEMBED;
 
-real8 testMySQLReal8() := EMBED(mysql : user('rchapman'),database('test'))
+real8 testMySQLReal8() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(r8) from tbl1;
 ENDEMBED;
 
-real4 testMySQLReal4() := EMBED(mysql : user('rchapman'),database('test'))
+real4 testMySQLReal4() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(r4) from tbl1;
 ENDEMBED;
 
-data testMySQLData() := EMBED(mysql : user('rchapman'),database('test'))
+data testMySQLData() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(d) from tbl1;
 ENDEMBED;
 
-UTF8 testMySQLUtf8() := EMBED(mysql : user('rchapman'),database('test'))
+UTF8 testMySQLUtf8() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(u1) from tbl1;
 ENDEMBED;
 
-UNICODE testMySQLUnicode() := EMBED(mysql : user('rchapman'),database('test'))
+UNICODE testMySQLUnicode() := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT max(u2) from tbl1;
 ENDEMBED;
 
@@ -128,7 +129,7 @@ stringrec extractName(childrec l) := TRANSFORM
   SELF := l;
 END;
 
-dataset(childrec) testMySQLDSParam(dataset(stringrec) inrecs) := EMBED(mysql : user('rchapman'),database('test'))
+dataset(childrec) testMySQLDSParam(dataset(stringrec) inrecs) := EMBED(mysql : user('rchapman'),database('test'),server('127.0.0.1'))
   SELECT * from tbl1 where name = ?;
 ENDEMBED;
 

+ 1 - 1
roxie/ccd/ccdfile.cpp

@@ -691,7 +691,7 @@ class CRoxieFileCache : public CInterface, implements ICopyFileProgress, impleme
                     throw MakeStringException(ROXIE_FILE_ERROR, "Local file %s does not match DFS information", localLocation);
                 else
                 {
-                    if (traceLevel > 2)
+                    if (traceLevel >= 2)
                     {
                         DBGLOG("Failed to open file at any of the following %d local locations:", localLocations.length());
                         ForEachItemIn(local_idx, localLocations)

+ 17 - 5
system/jlib/jmutex.cpp

@@ -20,6 +20,7 @@
 #include "jmutex.hpp"
 #include "jsuperhash.hpp"
 #include "jmisc.hpp"
+#include "jfile.hpp"
 
 #include <stdio.h>
 #include <assert.h>
@@ -210,14 +211,25 @@ static void unlock_file(const char *lfpath)
     ERRLOG("NamedMutex cannot unlock file (%d)",errno);
 }
 
+static CriticalSection lockPrefixCS;
+static StringBuffer lockPrefix;
 NamedMutex::NamedMutex(const char *name)
 {
-    const char * pfx = "/var/lock/JLIBMUTEX_";
-    mutexfname = (char *)malloc(strlen(name)+strlen(pfx)+1);
-    strcpy(mutexfname,pfx);
-    strcat(mutexfname,name);
+    {
+        CriticalBlock b(lockPrefixCS);
+        if (0 == lockPrefix.length())
+        {
+            if (!getConfigurationDirectory(NULL, "lock", NULL, NULL, lockPrefix))
+                throw MakeStringException(0, "Failed to get lock directory from environment");
+        }
+        addPathSepChar(lockPrefix);
+        lockPrefix.append("JLIBMUTEX_");
+    }
+    StringBuffer tmp(lockPrefix);
+    tmp.append("JLIBMUTEX_").append(name);
+    mutexfname = tmp.detach();
 }
-    
+
 NamedMutex::~NamedMutex()
 {
     free(mutexfname);

+ 1 - 1
system/jlib/jsocket.cpp

@@ -2664,7 +2664,7 @@ IpAddress & queryHostIP()
     if (cachehostip.isNull()) {
         if (!cachehostip.ipset(GetCachedHostName())) {
             cachehostip.ipset(queryLocalIP());          
-            printf("hostname %s not resolved, using localhost\n",GetCachedHostName()); // don't use jlog in case recursive
+            // printf("hostname %s not resolved, using localhost\n",GetCachedHostName()); // don't use jlog in case recursive
         }
     }
     return cachehostip;

+ 24 - 10
testing/regress/README.rst

@@ -92,13 +92,13 @@ Command:
 Result:
 
 |
-|       usage: ecl-test setup [-h] [--target [target_cluster | all]]
+|       usage: ecl-test setup [-h] [--target [target_cluster_list | all]]
 |                             [--pq threadNumber]
 |
 |       optional arguments:
 |        -h, --help               show this help message and exit
-|        --target [target_cluster | all], -t [target_cluster | all]
-|                                 target cluster for single query run. If target = 'all' then run query on all clusters. Default value is thor.
+|        --target [target_cluster_list | all], -t [target_cluster_list | all]
+|                                 run the setup on target cluster(s). If target = 'all' then run setup on all clusters. If undefined the config 'defaultSetupClusters' value will be used.
 |        --pq threadNumber        parallel query execution with threadNumber threads. (If threadNumber is '-1' on a single node system then threadNumber = numberOfLocalCore * 2)
 |
 
@@ -112,14 +112,14 @@ Command:
 Result:
 
 |
-|       usage: ecl-test run [-h] [--target [target_cluster | all]] [--publish] 
+|       usage: ecl-test run [-h] [--target [target_cluster_list | all]] [--publish]
 |                           [--pq threadNumber]
 |                           [--runclass [class]] [--excludeclass [class]]
 |
 |       optional arguments:
 |        -h, --help               show this help message and exit
-|        --target [target_cluster | all], -t [target_cluster | all]
-|                                 target cluster for single query run. If target = 'all' then run query on all clusters. Default value is thor.
+|        --target [target_cluster_list | all], -t [target_cluster_list | all]
+|                                 run the suite on target cluster(s). If target = 'all' then run suite on all clusters. If undefined the config 'defaultTargetClusters' value will be used.
 |        --publish, -p            publish compiled query instead of run.
 |        --pq threadNumber        parallel query execution with threadNumber threads. (If threadNumber is '-1' on a single node system then threadNumber = numberOfLocalCore * 2)
 |        --runclass class[,class,...], -r class[,class,...]
@@ -138,7 +138,7 @@ Command:
 Result:
 
 |
-|       usage: ecl-test query [-h] [--target [target_cluster | all]] [--publish]
+|       usage: ecl-test query [-h] [--target [target_cluster_list | all]] [--publish]
 |                             [--pq threadNumber]
 |                             ECL_query [ECL_query ...]
 |
@@ -147,8 +147,8 @@ Result:
 |
 |       optional arguments:
 |        -h, --help               show this help message and exit
-|        --target [target_cluster | all], -t [target_cluster | all]
-|                                 target cluster for single query run. If target = 'all' then run query on all clusters. Default value is thor.
+|        --target [target_cluster_list | all], -t [target_cluster_list | all]
+|                                 run the query on target cluster(s). If target = 'all' then run query on all clusters. If undefined the config 'defaultTargetClusters' value will be used.
 |        --publish, -p            publish compiled query instead of run.
 |        --pq threadNumber        parallel query execution for multiple test cases specified in CLI with threadNumber threads. (If threadNumber is '-1' on a single node system then threadNumber = numberOfLocalCore * 2 )
 |
@@ -596,7 +596,21 @@ So if you have a new test case and it works well on all clusters (or some of the
         "timeout":"720",                                - Default test case timeout in sec. Can be override by command line parameter or //timeout tag in ECL file
         "maxAttemptCount":"3"                           - Max retry count to reset timeout if a testcase in any early stage (compiled, blocked) of execution pipeline.
 
-Optionally the config file can contain a section of default values for stored parameters like this:
+Optionally the config file can contain some sections of default values:
+
+If the -t | --target command line parameter is omitted then the regression test engine uses the default target(s) from one of these default definitions. If undefined, then the engine uses the first cluster from the Cluster array.
+
+        "defaultSetupClusters": [
+            "hthor",
+            "thor3"
+        ]
+
+        "defaultTargetClusters": [
+            "thor",
+            "thor3"
+        ]
+
+For stored parameters:
 
     "Params":[
                 "querya.ecl:param1=value1,param2=value2",

+ 29 - 9
testing/regress/ecl-test

@@ -28,7 +28,7 @@ import glob
 from hpcc.util import argparse
 from hpcc.regression.regress import Regression
 from hpcc.util.ecl.file import ECLFile
-from hpcc.util.util import checkPqParam, getVersionNumbers, checkXParam, convertPath, getRealIPAddress, parentPath
+from hpcc.util.util import checkPqParam, getVersionNumbers, checkXParam, convertPath, getRealIPAddress, parentPath, checkClusters
 from hpcc.common.error import Error
 
 # For coverage
@@ -170,15 +170,15 @@ class RegressMain:
 
         parser_setup = subparsers.add_parser('setup', help='setup help')
         parser_setup.set_defaults(func='setup')
-        parser_setup.add_argument('--target', '-t', help="Run the setup on target cluster. If target = 'all' then run setup on all clusters. Default value is thor.",
-                                nargs='?', type=str,  default='thor', metavar="target_cluster | all")
+        parser_setup.add_argument('--target', '-t', help="Run the setup on target cluster(s). If target = 'all' then run setup on all clusters. If not defined then default value(s) come from config (ecl-test.json by default).",
+                                nargs='?', type=str,  default='', metavar="target_cluster_list | all")
         parser_setup.add_argument('--pq', help="Parallel query execution with threadNumber threads. (If threadNumber is '-1' on a single node system then threadNumber = numberOfLocalCore * 2 )",
                                 type=checkPqParam,  default = 0,   metavar="threadNumber")
 
         parser_run = subparsers.add_parser('run', help='run help')
         parser_run.set_defaults(func='run')
-        parser_run.add_argument('--target', '-t', help="Run the cluster suite. If target = 'all' then run suite on all clusters. Default value is thor.",
-                                nargs='?', type=str,  default='thor', metavar="target_cluster | all")
+        parser_run.add_argument('--target', '-t', help="Run the cluster(s) suite. If target = 'all' then run suite on all clusters. If not defined then default value(s) come from config (ecl-test.json by default).",
+                                nargs='?', type=str,  default='', metavar="target_cluster_list | all")
         parser_run.add_argument('--publish', '-p', help="Publish compiled query instead of run.",
                                 action='store_true')
         parser_run.add_argument('--pq', help="Parallel query execution with threadNumber threads. (If threadNumber is '-1' on a single node system then threadNumber = numberOfLocalCore * 2 )",
@@ -192,8 +192,8 @@ class RegressMain:
         parser_query.set_defaults(func='query')
         parser_query.add_argument('query', help="One or more ECL file(s). It can contain wildcards. (mandatory).",
                                   nargs='+', metavar="ECL_query")
-        parser_query.add_argument('--target', '-t', help="Target cluster for query to run. If target = 'all' then run query on all clusters. Default value is thor.",
-                                nargs='?', default='thor', metavar="target_cluster | all")
+        parser_query.add_argument('--target', '-t', help="Target cluster(s) for query to run. If target = 'all' then run query on all clusters. If not defined then default value(s) come from config (ecl-test.json by default).",
+                                nargs='?', default='', metavar="target_cluster_list | all")
         parser_query.add_argument('--publish', '-p', help="Publish compiled query instead of run.",
                                 action='store_true')
         parser_query.add_argument('--pq', help="Parallel query execution with threadNumber threads. (If threadNumber is '-1' on a single node system then threadNumber = numberOfLocalCore * 2 )",
@@ -224,11 +224,31 @@ class RegressMain:
 
             self.targetClusters = []
             if 'target' in self.args:
-                if 'all' == self.args.target:
+                if '' == self.args.target:
+                    # Target not specified, use default from config
+                    try:
+                        if self.args.func == 'setup':
+                            defaultTargets = self.regress.config.defaultSetupClusters
+                            targetSet='defaultSetupClusters'
+                        else:
+                            defaultTargets = self.regress.config.defaultTargetClusters
+                            targetSet='defaultTargetClusters'
+
+                        self.targetClusters = checkClusters(defaultTargets, targetSet)
+
+                    except AttributeError:
+                        # It seems there is no defaultSetupClusters|defaultTargetClusters array in the config file
+                        # use the first one of cluster list in config file
+                        self.targetClusters.append(self.regress.config.Clusters[0])
+                elif 'all' == self.args.target:
                     for cluster in self.regress.config.Clusters:
                         self.targetClusters.append(str(cluster))
                 else:
-                    if self.args.target in self.regress.config.Clusters:
+                    if ',' in self.args.target:
+                        # target is a list, process it
+                        targets = self.args.target.split(',')
+                        self.targetClusters = checkClusters(targets, 'target')
+                    elif self.args.target in self.regress.config.Clusters:
                         self.targetClusters.append(self.args.target)
                     else:
                         logging.error("%s. Unknown target cluster:'%s'!" % (1,  self.args.target))

+ 6 - 0
testing/regress/ecl-test.json

@@ -24,6 +24,12 @@
         ],
         "timeout":"720",
         "maxAttemptCount":"3",
+        "defaultSetupClusters": [
+            "all"
+        ],
+        "defaultTargetClusters": [
+            "all"
+        ],
         "Params":[
             "PassTest.ecl:bla='A value'"
         ]

+ 16 - 0
testing/regress/hpcc/util/util.py

@@ -158,3 +158,19 @@ def getRealIPAddress():
         pass
 
     return ipAddress
+
+def checkClusters(clusters,  targetSet):
+    targetClusters =[]
+    if 'all' in clusters:
+        for cluster in gConfig.Clusters:
+            targetClusters.append(str(cluster))
+    else:
+        for cluster in clusters:
+            cluster = cluster.strip()
+            if cluster in gConfig.Clusters:
+                targetClusters.append(cluster)
+            else:
+                logging.error("%s. Unknown cluster:'%s' in %s:'%s'!" % (1,  cluster,  targetSet,  clusters))
+                raise Error("4000")
+
+    return  targetClusters