Selaa lähdekoodia

Merge remote-tracking branch 'origin/candidate-5.0.0' into closedown-5.0.x

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 11 vuotta sitten
vanhempi
commit
aedbfe7a2b
41 muutettua tiedostoa jossa 1050 lisäystä ja 419 poistoa
  1. 18 0
      common/deftype/defvalue.cpp
  2. 1 1
      common/deftype/defvalue.ipp
  3. 1 1
      common/workunit/package.cpp
  4. 47 5
      common/workunit/workunit.cpp
  5. 2 0
      common/workunit/workunit.hpp
  6. 2 35
      ecl/eclagent/eclagent.cpp
  7. 0 1
      ecl/eclagent/eclagent.ipp
  8. 1 0
      ecl/hql/hqlmeta.cpp
  9. 2 0
      ecl/hqlcpp/hqlhtcpp.cpp
  10. 4 0
      ecl/regress/issue11401.ecl
  11. 2 2
      esp/eclwatch/ws_XSLT/access_permissionresetinput.xslt
  12. 16 14
      esp/eclwatch/ws_XSLT/access_resources.xslt
  13. 150 150
      esp/eclwatch/ws_XSLT/nls/hu/hpcc.xml
  14. 55 14
      esp/services/ws_access/ws_accessService.cpp
  15. 2 0
      esp/services/ws_access/ws_accessService.hpp
  16. 4 1
      esp/services/ws_packageprocess/ws_packageprocessService.cpp
  17. 3 0
      esp/src/eclwatch/ECLSourceWidget.js
  18. 14 0
      esp/src/eclwatch/FileSpray.js
  19. 22 0
      esp/src/eclwatch/LZBrowseWidget.js
  20. 35 10
      esp/src/eclwatch/PackageMapQueryWidget.js
  21. 240 0
      esp/src/eclwatch/PackageMapValidateContentWidget.js
  22. 148 98
      esp/src/eclwatch/PackageMapValidateWidget.js
  23. 26 0
      esp/src/eclwatch/WsPackageMaps.js
  24. 6 0
      esp/src/eclwatch/nls/hpcc.js
  25. 15 0
      esp/src/eclwatch/templates/LZBrowseWidget.html
  26. 1 1
      esp/src/eclwatch/templates/PackageMapQueryWidget.html
  27. 21 0
      esp/src/eclwatch/templates/PackageMapValidateContentWidget.html
  28. 21 41
      esp/src/eclwatch/templates/PackageMapValidateWidget.html
  29. 4 0
      initfiles/componentfiles/configxml/buildsetCC.xml.in
  30. 13 0
      initfiles/etc/DIR_NAME/environment.xml.in
  31. 1 0
      roxie/ccd/ccdcontext.cpp
  32. 67 30
      roxie/ccd/ccdserver.cpp
  33. 1 0
      roxie/ccd/ccdserver.hpp
  34. 26 4
      roxie/ccd/ccdstate.cpp
  35. 2 0
      roxie/ccd/ccdstate.hpp
  36. 26 8
      system/jlib/jstring.cpp
  37. 1 1
      testing/regress/ecl-test
  38. 12 0
      testing/regress/ecl/key/resetsplitter.xml
  39. 36 0
      testing/regress/ecl/resetsplitter.ecl
  40. 1 1
      testing/regress/hpcc/regression/suite.py
  41. 1 1
      testing/regress/hpcc/util/ecl/command.py

+ 18 - 0
common/deftype/defvalue.cpp

@@ -544,6 +544,24 @@ const char *StringValue::getStringValue(StringBuffer &out)
     return out.toCharArray();
 }
 
+const char *StringValue::getUTF8Value(StringBuffer &out)
+{
+    ICharsetInfo * srcCharset = type->queryCharset();
+    Owned<ICharsetInfo> asciiCharset = getCharset(asciiAtom);
+    if (queryDefaultTranslation(asciiCharset, srcCharset))
+    {
+        Owned<ITypeInfo> asciiType = getAsciiType(type);
+        Owned<IValue> asciiValue = castTo(asciiType);
+        return asciiValue->getUTF8Value(out);
+    }
+
+    rtlDataAttr temp;
+    unsigned bufflen;
+    rtlStrToUtf8X(bufflen, temp.refstr(), type->getSize(), (const char *)val.get());
+    out.append(rtlUtf8Size(bufflen, temp.getstr()), temp.getstr());
+    return out.toCharArray();
+}
+
 void StringValue::pushDecimalValue()
 {
     DecPushString(type->getSize(),(char *)val.get());

+ 1 - 1
common/deftype/defvalue.ipp

@@ -121,7 +121,7 @@ public:
     virtual __int64 getIntValue();
     virtual void pushDecimalValue();
     virtual const char *getStringValue(StringBuffer &); 
-
+    virtual const char *getUTF8Value(StringBuffer & out);
 }; 
 
 class UnicodeValue : public MemoryValue

+ 1 - 1
common/workunit/package.cpp

@@ -41,7 +41,7 @@ CPackageNode::CPackageNode(IPropertyTree *p)
     else
         node.setown(createPTree("HpccPackages"));
     StringBuffer xml;
-    toXML(node, xml);
+    toXML(node, xml, 0, XML_SortTags);
     hash = rtlHash64Data(xml.length(), xml.str(), 9994410);
 }
 

+ 47 - 5
common/workunit/workunit.cpp

@@ -7800,21 +7800,31 @@ IStringVal& CLocalWUResult::getResultXml(IStringVal &str) const
     getSchema(types, names);
 
     StringBuffer xml;
-    MemoryBuffer raw;
-    p->getPropBin("Value", raw);
     const char * name = p->queryProp("@name");
     if (name)
         xml.appendf("<Dataset name=\'%s\'>\n", name);
     else
         xml.append("<Dataset>\n");
 
-    unsigned __int64 numrows = getResultRowCount();
-    while (numrows--)
+    if (p->hasProp("Value"))
+    {
+        MemoryBuffer raw;
+        p->getPropBin("Value", raw);
+        unsigned __int64 numrows = getResultRowCount();
+        while (numrows--)
+        {
+            xml.append(" <Row>");
+            readRow(xml, raw, types, names);
+            xml.append("</Row>\n");
+        }
+    }
+    else if (p->hasProp("xmlValue"))
     {
         xml.append(" <Row>");
-        readRow(xml, raw, types, names);
+        appendXMLTag(xml, name, p->queryProp("xmlValue"));
         xml.append("</Row>\n");
     }
+
     xml.append("</Dataset>\n");
     str.set(xml.str());
     return str;
@@ -9509,6 +9519,38 @@ extern WORKUNIT_API bool secDebugWorkunit(const char * wuid, ISecManager &secmgr
     return false;
 }
 
+void updateSuppliedXmlParams(IWorkUnit * w)
+{
+    Owned<const IPropertyTree> params = w->getXmlParams();
+    if (!params)
+        return;
+    Owned<IPropertyTreeIterator> elems = params->getElements("*");
+    ForEach(*elems)
+    {
+        IPropertyTree & curVal = elems->query();
+        const char *name = curVal.queryName();
+        Owned<IWUResult> r = updateWorkUnitResult(w, name, -1);
+        if (r)
+        {
+            StringBuffer s;
+            if (r->isResultScalar() && !curVal.hasChildren())
+            {
+                curVal.getProp(".", s);
+                r->setResultXML(s);
+                r->setResultStatus(ResultStatusSupplied);
+            }
+            else
+            {
+                toXML(&curVal, s);
+                bool isSet = (curVal.hasProp("Item") || curVal.hasProp("string"));
+                r->setResultRaw(s.length(), s.str(), isSet ? ResultFormatXmlSet : ResultFormatXml);
+            }
+        }
+        else
+            DBGLOG("WARNING: no matching variable in workunit for input parameter %s", name);
+    }
+}
+
 IWUResult * updateWorkUnitResult(IWorkUnit * w, const char *name, unsigned sequence)
 {
     switch ((int)sequence)

+ 2 - 0
common/workunit/workunit.hpp

@@ -1281,6 +1281,8 @@ extern WORKUNIT_API void secSubmitWorkUnit(const char *wuid, ISecManager &secmgr
 extern WORKUNIT_API void secAbortWorkUnit(const char *wuid, ISecManager &secmgr, ISecUser &secuser);
 extern WORKUNIT_API IWUResult * updateWorkUnitResult(IWorkUnit * w, const char *name, unsigned sequence);
 extern WORKUNIT_API IConstWUResult * getWorkUnitResult(IConstWorkUnit * w, const char *name, unsigned sequence);
+extern WORKUNIT_API void updateSuppliedXmlParams(IWorkUnit * w);
+
 //returns a state code.  WUStateUnknown == timeout
 extern WORKUNIT_API WUState waitForWorkUnitToComplete(const char * wuid, int timeout = -1, bool returnOnWaitState = false);
 extern WORKUNIT_API bool waitForWorkUnitToCompile(const char * wuid, int timeout = -1);

+ 2 - 35
ecl/eclagent/eclagent.cpp

@@ -558,14 +558,10 @@ EclAgent::EclAgent(IConstWorkUnit *wu, const char *_wuid, bool _checkVersion, bo
         SCMStringBuffer jobName;
         debugContext->debugInitialize(wuid, wu->getJobName(jobName).str(), true);
     }
+    Owned<IWorkUnit> w = updateWorkUnit();
     if (_queryXML)
-    {
-        Owned<IWorkUnit> w = updateWorkUnit();
         w->setXmlParams(_queryXML);
-    }
-    Owned<const IPropertyTree> xmlParams = wuRead->getXmlParams();
-    if (xmlParams)
-        processXmlParams(xmlParams);
+    updateSuppliedXmlParams(w);
 }
 
 EclAgent::~EclAgent()
@@ -594,35 +590,6 @@ void EclAgent::setStandAloneOptions(bool _isStandAloneExe, bool _isRemoteWorkuni
     standAloneUDesc.set(_standAloneUDesc);
 }
 
-void EclAgent::processXmlParams(const IPropertyTree *params)
-{
-    Owned<IPropertyTreeIterator> elems = params->getElements("*");
-    ForEach(*elems)
-    {
-        IPropertyTree & curVal = elems->query();
-        const char *name = curVal.queryName();
-        Owned<IWUResult> r = updateResult(name, -1);
-        if (r)
-        {
-            StringBuffer s;
-            if (r->isResultScalar() && !curVal.hasChildren())
-            {
-                curVal.getProp(".", s);
-                r->setResultXML(s.str());
-                r->setResultStatus(ResultStatusSupplied);
-            }
-            else
-            {
-                toXML(&curVal, s);
-                bool isSet = (curVal.hasProp("Item") || curVal.hasProp("string"));
-                r->setResultRaw(s.length(), s.str(), isSet ? ResultFormatXmlSet : ResultFormatXml);
-            }
-        }
-        else
-            DBGLOG("WARNING: no matching variable in workunit for input parameter %s", name);
-    }
-}
-
 ICodeContext *EclAgent::queryCodeContext()
 {
     return this;    //called by helper

+ 0 - 1
ecl/eclagent/eclagent.ipp

@@ -382,7 +382,6 @@ private:
     const char *queryTempfilePath();
     void deleteTempFiles();
 
-    void processXmlParams(const IPropertyTree *params);
     bool checkPersistUptoDate(const char * logicalName, unsigned eclCRC, unsigned __int64 allCRC, bool isFile, StringBuffer & errText);
     bool isPersistUptoDate(Owned<IRemoteConnection> &persistLock, const char * logicalName, unsigned eclCRC, unsigned __int64 allCRC, bool isFile);
     bool changePersistLockMode(IRemoteConnection *persistLock, unsigned mode, const char * name, bool repeat);

+ 1 - 0
ecl/hql/hqlmeta.cpp

@@ -2826,6 +2826,7 @@ void calculateDatasetMeta(CHqlMetaInfo & meta, IHqlExpression * expr)
             meta.setUnknownGrouping();
         break;
     case no_externalcall:
+    case no_external:
         if (isGrouped(expr))
             meta.setUnknownGrouping();
         //No support for grouping?

+ 2 - 0
ecl/hqlcpp/hqlhtcpp.cpp

@@ -6739,6 +6739,8 @@ ABoundActivity * HqlCppTranslator::buildActivity(BuildCtx & ctx, IHqlExpression
             case no_executewhen:
                 result = doBuildActivityExecuteWhen(ctx, expr, isRoot);
                 break;
+            case no_param:
+                throwUnexpectedX("Create Parameter as an activity");
             case no_thor:
                 UNIMPLEMENTED;
                 break;

+ 4 - 0
ecl/regress/issue11401.ecl

@@ -0,0 +1,4 @@
+ds := dataset('x', { unsigned id; }, thor);
+output(ds,XMLNS('bob1','OhlàlàStraße'));
+output(ds,XMLNS('bob2',U8'OhlàlàStraße'));
+output(ds,XMLNS('bob3',(ebcdic string)'OhlàlàStraße'));     // You really have to be daft to use an ebcdic string...

+ 2 - 2
esp/eclwatch/ws_XSLT/access_permissionresetinput.xslt

@@ -303,8 +303,8 @@
         <input type="hidden" name="rtitle" value="{rtitle}"/>
         <input type="hidden" name="prefix" value="{prefix}"/>
         <input type="hidden" name="BasednName" value="{BasednName}"/>
-        <input type="hidden" name="userarray" value=""/>
-        <input type="hidden" name="grouparray" value=""/>
+        <input type="hidden" id="userarray" name="userarray" value=""/>
+        <input type="hidden" id="grouparray" name="grouparray" value=""/>
 
         <h3>Permission Reset</h3>
         <div>

+ 16 - 14
esp/eclwatch/ws_XSLT/access_resources.xslt

@@ -186,20 +186,22 @@
                 </form>
             </td>
 
-            <xsl:if test="scopeScansStatus/retcode=0">
-              <xsl:if test="scopeScansStatus/isEnabled=0">
-                <td>
-                  <form action="/ws_access/EnableScopeScans">
-                    <input id="EnableScopeScansBtn" class="sbutton" type="submit"  name="action" value="Enable Scope Scans" onclick="return confirm('Are you sure you want to enable Scope Scans? Changes will revert to configuration settings on DALI reboot.')"/>
-                  </form>
-                </td>
-              </xsl:if>
-              <xsl:if test="scopeScansStatus/isEnabled=1">
-                <td>
-                  <form action="/ws_access/DisableScopeScans">
-                    <input id="DisableScopeScansBtn" class="sbutton" type="submit"  name="action" value="Disable Scope Scans" onclick="return confirm('Are you sure you want to disable Scope Scans?  Changes will revert to configuration settings on DALI reboot.')"/>
-                  </form>
-                </td>
+            <xsl:if test="rtype='file' and rtitle='FileScope'">
+              <xsl:if test="scopeScansStatus/retcode=0">
+                <xsl:if test="scopeScansStatus/isEnabled=0">
+                  <td>
+                    <form action="/ws_access/EnableScopeScans">
+                      <input id="EnableScopeScansBtn" class="sbutton" type="submit"  name="action" value="Enable Scope Scans" onclick="return confirm('Are you sure you want to enable Scope Scans? Changes will revert to configuration settings on DALI reboot.')"/>
+                    </form>
+                  </td>
+                </xsl:if>
+                <xsl:if test="scopeScansStatus/isEnabled=1">
+                  <td>
+                    <form action="/ws_access/DisableScopeScans">
+                      <input id="DisableScopeScansBtn" class="sbutton" type="submit"  name="action" value="Disable Scope Scans" onclick="return confirm('Are you sure you want to disable Scope Scans?  Changes will revert to configuration settings on DALI reboot.')"/>
+                    </form>
+                  </td>
+                </xsl:if>
               </xsl:if>
             </xsl:if>
 

+ 150 - 150
esp/eclwatch/ws_XSLT/nls/hu/hpcc.xml

@@ -1,162 +1,162 @@
 <hpcc>
 <strings>
-<st id='Action'>Action</st>
-<st id='Available'>Available</st>
-<st id='AdditionalProcessesToFilter'>Additional processes to filter</st>
-<st id='AutoRefreshEvery'>Auto Refresh every</st>
-<st id='AutoUpdateMetricsWhenViewColumnsChanging'>Auto update metrics when View Columns changing.</st>
-<st id='AvailableNodes'>Available Nodes</st>
-<st id='BrowserTimedOut'>Browser timed out due to a long time delay.</st>
-<st id='Busy'>Busy</st>
-<st id='Bytes'>bytes</st>
-<st id='Cancel'>Cancel</st>
-<st id='Cluster'>Cluster</st>
-<st id='ClusterProcess'>Cluster Process</st>
-<st id='Clusters'>Clusters</st>
-<st id='ClusterTopology'>Cluster Topology</st>
-<st id='Component'>Component</st>
-<st id='Computer'>Computer</st>
-<st id='ComputerUpTime'>Computer Up Time</st>
-<st id='Condition'>Condition</st>
-<st id='Configuration'>Configuration</st>
-<st id='ConfigurationFile'>Configuration file</st>
-<st id='ConfirmSwap'>Are you sure you want to swap two machines under</st>
-<st id='CPULoad'>CPU Load</st>
-<st id='Critical'>Critical</st>
-<st id='DaliServer'>Dali Server</st>
-<st id='DaliServers'>Dali Servers</st>
-<st id='Date'>Date</st>
-<st id='Description'>Description</st>
-<st id='DFUServers'>DFU Servers</st>
-<st id='Directory'>Directory</st>
+<st id='Action'>Művelet</st>
+<st id='Available'>Szabad</st>
+<st id='AdditionalProcessesToFilter'>További feldolgozások a szűréshez</st>
+<st id='AutoRefreshEvery'>Automatikus frissítés minden</st>
+<st id='AutoUpdateMetricsWhenViewColumnsChanging'>Automatikus metrika frissítés, ha az oszlopnézet változik.</st>
+<st id='AvailableNodes'>Szabad csomópontok</st>
+<st id='BrowserTimedOut'>Hosszú ideig tartó lekérdezés miatt a böngésző program túllépte a rendelkezésre alló időkeretet.</st>
+<st id='Busy'>Foglalt</st>
+<st id='Bytes'>bájt</st>
+<st id='Cancel'>Mégsem</st>
+<st id='Cluster'>Csoport</st>
+<st id='ClusterProcess'>Csoport feldolgozás</st>
+<st id='Clusters'>Csoportok</st>
+<st id='ClusterTopology'>csoport topológia</st>
+<st id='Component'>alkotóelem</st>
+<st id='Computer'>számítógép</st>
+<st id='ComputerUpTime'>Számítógép aktív</st>
+<st id='Condition'>feltétel</st>
+<st id='Configuration'>Configuráció</st>
+<st id='ConfigurationFile'>Configurációs fájl</st>
+<st id='ConfirmSwap'>Biztos abban, hogy fel akarja cserelni a két számítógépet</st>
+<st id='CPULoad'>CPU terhelés</st>
+<st id='Critical'>Kritikus</st>
+<st id='DaliServer'>Dali Szerver</st>
+<st id='DaliServers'>Dali Sezrverek</st>
+<st id='Date'>Dátum</st>
+<st id='Description'>Leírás</st>
+<st id='DFUServers'>DFU Szerverek</st>
+<st id='Directory'>Könyvtár</st>
 <st id='Domain'>Domain</st>
-<st id='Download'>Download</st>
-<st id='DropZone'>Drop Zone</st>
-<st id='DropZones'>Drop Zones</st>
-<st id='EarliestFirst'>EarliestFirst</st>
-<st id='ECLAgents'>ECL Agents</st>
-<st id='ECLAgentProcess'>ECL Agent Process</st>
-<st id='ECLCCServers'>ECL CC Servers</st>
-<st id='ECLSchedulers'>ECL Schedulers</st>
-<st id='ECLServers'>ECL Servers</st>
-<st id='ESPServers'>ESP Servers</st>
-<st id='FailedToRetrieveInformation'>Failed to retrieve information.  Please check configuration.</st>
-<st id='Fatal'>Fatal</st>
-<st id='Fetched'>Fetched</st>
-<st id='Files'>Files</st>
-<st id='FirstPage'>First page</st>
-<st id='FirstRowsNotDefined'>First Rows field not defined</st>
-<st id='Folder'>Folder</st>
+<st id='Download'>Letöltés</st>
+<st id='DropZone'>Gyűjtőhely</st>
+<st id='DropZones'>Gyűjtőhelyek</st>
+<st id='EarliestFirst'>A korábbit először</st>
+<st id='ECLAgents'>ECL Ügynökök</st>
+<st id='ECLAgentProcess'>ECL ügynök feldolgozás</st>
+<st id='ECLCCServers'>ECL CC Szerverek</st>
+<st id='ECLSchedulers'>ECL ütemezők</st>
+<st id='ECLServers'>ECL Szerverek</st>
+<st id='ESPServers'>ESP Szerverek</st>
+<st id='FailedToRetrieveInformation'>Az információ kiolvasása sikertelen volt. Kérjük, ellenőrizze a beállításokat.</st>
+<st id='Fatal'>Súlyos</st>
+<st id='Fetched'>Beolvasott</st>
+<st id='Files'>Fájlok</st>
+<st id='FirstPage'>Első lap</st>
+<st id='FirstRowsNotDefined'>Az 'első sor' mezo nem definiált</st>
+<st id='Folder'>Gyüjtő</st>
 <st id='FTSlaves'>FT Slaves</st>
-<st id='GenesisServers'>Genesis Servers</st>
-<st id='GetProcessorInformation'>Get processor information</st>
-<st id='GetRoxieMetrics'>Get Roxie Metrics</st>
-<st id='GetSoftwareInformation'>Get software information</st>
-<st id='GetStorageInformation'>Get storage information</st>
-<st id='Home'>Home</st>
-<st id='Hours'>hours</st>
-<st id='Instance'>Instance</st>
-<st id='InvalidFirstRows'>Invalid data in First Rows field</st>
-<st id='InvalidLastHours'>Invalid data in Last Hours field</st>
-<st id='InvalidLastRows'>Invalid data in a Last Rows field</st>
-<st id='InvalidPageNumber'>Invalid data in page number field</st>
-<st id='InvalidTime'>Invalid data in a Time field</st>
-<st id='IPAddress'>IP Address</st>
-<st id='LastHoursNotDefined'>Last Hours field not defined</st>
-<st id='LastRowsNotDefined'>Last Rows field not defined</st>
-<st id='LatestFirst'>LatestFirst</st>
-<st id='LDAPServers'>LDAP Servers</st>
-<st id='LoadingPleaseWait'>Loading, please wait...</st>
-<st id='LocalFileSystemsOnly'>Local File Systems Only</st>
-<st id='Location'>Location</st>
-<st id='LogFile'>Log file</st>
-<st id='LogDirectory'>Log Directory</st>
-<st id='LostReferenceToParentWindow'>Lost reference to parent window.  Please traverse the path again!</st>
-<st id='MachineInfo'>Machine Information</st>
-<st id='Major'>Major</st>
-<st id='Mean'>Mean</st>
-<st id='Metrics'>Metrics</st>
-<st id='Minor'>Minor</st>
-<st id='Mins'>mins</st>
-<st id='MySQLServers'>MySQL Servers</st>
+<st id='GenesisServers'>Genesis Szerverek</st>
+<st id='GetProcessorInformation'>Processzor információk</st>
+<st id='GetRoxieMetrics'>Roxie Metrika</st>
+<st id='GetSoftwareInformation'>Szoftver információ</st>
+<st id='GetStorageInformation'>Tárolóhely információ</st>
+<st id='Home'>Nyitó képernyő</st>
+<st id='Hours'>Órák</st>
+<st id='Instance'>Példány</st>
+<st id='InvalidFirstRows'>Helytelen adat az 'első sorok' mezőben</st>
+<st id='InvalidLastHours'>Helytelen adat az 'utlosó órák' mezőben</st>
+<st id='InvalidLastRows'>Helytelen adat az utolsó sorok' mezőben</st>
+<st id='InvalidPageNumber'>Helytelen adat a 'lapszám' mezőben</st>
+<st id='InvalidTime'>Helytelen adat az 'idő' mezőben</st>
+<st id='IPAddress'>IP cím</st>
+<st id='LastHoursNotDefined'>Az 'utolsó órák' mező definiálatlan</st>
+<st id='LastRowsNotDefined'>Az 'utolsó sorok' mező definiálatlan</st>
+<st id='LatestFirst'>A kesőbbit először</st>
+<st id='LDAPServers'>LDAP Szerverek</st>
+<st id='LoadingPleaseWait'>Betöltés, kérem várjon...</st>
+<st id='LocalFileSystemsOnly'>Csak helyi fájlrendszer</st>
+<st id='Location'>Hely</st>
+<st id='LogFile'>Log fájl</st>
+<st id='LogDirectory'>Log könyvtár</st>
+<st id='LostReferenceToParentWindow'>Elveszett a hivatkozás a szülőablakra. Kérjük próbálja meg ismét bejárni az útvonalat!</st>
+<st id='MachineInfo'>Számítógép információ</st>
+<st id='Major'>Jelentős</st>
+<st id='Mean'>Átlag</st>
+<st id='Metrics'>Metrika</st>
+<st id='Minor'>Jelentéktelen</st>
+<st id='Mins'>perc</st>
+<st id='MySQLServers'>MySQL Szerverek</st>
 <st id='NA'>N/A</st>
-<st id='Name'>Name</st>
-<st id='NetworkAddress'>Network Address</st>
-<st id='NextPage'>NextPage</st>
-<st id='NoClustersDefined'>No clusters defined.</st>
-<st id='Nodes'>Nodes</st>
-<st id='NoItems'>No items</st>
-<st id='NoMachinesSelected'>No machines selected!</st>
-<st id='Normal'>Normal</st>
-<st id='NoSystemServicesDefined'>No system services defined!</st>
-<st id='NoTargetClustersSelected'>No target clusters selected!</st>
-<st id='OrFirst'>or first</st>
-<st id='OrGoToPage'>or go to page</st>
-<st id='OrLast'>or last</st>
-<st id='OrLastPage'>or last page</st>
-<st id='OrTimeFrom'>or time from</st>
-<st id='Page'>page</st>
-<st id='PageNumberNotDefined'>page number field not defined</st>
-<st id='PhysicalMemory'>Physical Memory</st>
+<st id='Name'>Név</st>
+<st id='NetworkAddress'>Hálózati cím</st>
+<st id='NextPage'>Követkző lap</st>
+<st id='NoClustersDefined'>Nincs csoport definiálva.</st>
+<st id='Nodes'>Csomopontok</st>
+<st id='NoItems'>Nincs tétel</st>
+<st id='NoMachinesSelected'>Nincs számítógép kiválasztva!</st>
+<st id='Normal'>Normális</st>
+<st id='NoSystemServicesDefined'>Nincs rendszerszolgáltatás definiálva!</st>
+<st id='NoTargetClustersSelected'>Nincs kiválasztva célcsoport!</st>
+<st id='OrFirst'>vagy első</st>
+<st id='OrGoToPage'>vagy ugrás a lapra</st>
+<st id='OrLast'>vagy az utolsó</st>
+<st id='OrLastPage'>vagy az utolsó lap</st>
+<st id='OrTimeFrom'>vagy időponttól</st>
+<st id='Page'>lap</st>
+<st id='PageNumberNotDefined'>a 'lapszám' mező definiálatlan</st>
+<st id='PhysicalMemory'>Fizikai memória</st>
 <st id='Platform'>Platform</st>
 <st id='Port'>Port</st>
-<st id='PrevPage'>PrevPage</st>
-<st id='Processes'>Processes</st>
-<st id='ProcessesDown'>Processes Down</st>
-<st id='Protocol'>Protocol</st>
-<st id='Queue'>Queue</st>
-<st id='Ready'>Ready</st>
-<st id='Recycling'>Recycling</st>
-<st id='Refresh'>Refresh</st>
-<st id='Rows'>rows</st>
-<st id='RoxieServer'>Roxie Server</st>
-<st id='SashaServers'>Sasha Servers</st>
-<st id='SchedulerProcess'>Scheduler Process</st>
-<st id='SecurityString'>Security String</st>
-<st id='Select'>Select</st>
-<st id='SelectASpareNodeToSwap'>Select a spare node to swap</st>
-<st id='SelectAllOrNone'>Select All / None</st>
-<st id='SelectAllTargetClusters'>Select all target clusters</st>
-<st id='SelectDeselectAll'>Select or deselect all</st>
-<st id='SelectDeselectAllMachines'>Select or deselect all machines</st>
-<st id='SelectThisTargetCluster'>Select this target cluster</st>
-<st id='ServerProcess'>Server Process</st>
-<st id='ServiceName'>Service Name</st>
-<st id='ServiceType'>Service Type</st>
-<st id='ShowProcessesUsingFilter'>Show processes using filter</st>
-<st id='Size'>Size</st>
-<st id='SlaveNumber'>Slave Number</st>
-<st id='Starting'>Starting</st>
-<st id='State'>State</st>
-<st id='Stopping'>Stopping</st>
-<st id='Submit'>Submit</st>
-<st id='Suspended'>Suspended</st>
-<st id='Swap'>Swap</st>
-<st id='SwapNode'>Swap Node</st>
-<st id='SystemServers'>System Servers</st>
-<st id='SystemServiceNodes'>System Service Nodes</st>
-<st id='TargetClusters'>Target Clusters</st>
-<st id='TimeNotDefined'>Either Time From or To field not defined</st>
-<st id='ThisPageFromByte'>this page from byte</st>
+<st id='PrevPage'>Előző lap</st>
+<st id='Processes'>Feldolgozások</st>
+<st id='ProcessesDown'>Leállt feldolgozások</st>
+<st id='Protocol'>Protokoll</st>
+<st id='Queue'>Várakozási sor</st>
+<st id='Ready'>Kész</st>
+<st id='Recycling'>Újrahasznosítás</st>
+<st id='Refresh'>Frissítés</st>
+<st id='Rows'>sorok</st>
+<st id='RoxieServer'>Roxie Szerver</st>
+<st id='SashaServers'>Sasha Szerverek</st>
+<st id='SchedulerProcess'>Ütemező</st>
+<st id='SecurityString'>Biztonsági karakterlánc</st>
+<st id='Select'>Kiválaszt</st>
+<st id='SelectASpareNodeToSwap'>Válasszon egy tartalék csomópontot a váltáshoz</st>
+<st id='SelectAllOrNone'>Kiválasztja mindet/egyet sem</st>
+<st id='SelectAllTargetClusters'>Az összes célcsomópont kiválasztása</st>
+<st id='SelectDeselectAll'>Kiválasztja mindet/egyet sem</st>
+<st id='SelectDeselectAllMachines'>Kiválasztja az összes számítógépet vagy egyet sem</st>
+<st id='SelectThisTargetCluster'>Kiválasztja ezeket a cél csomópontokat</st>
+<st id='ServerProcess'>Szerver feldolgozás</st>
+<st id='ServiceName'>Szolgáltatás megnevezése</st>
+<st id='ServiceType'>Szolgáltatás típusa</st>
+<st id='ShowProcessesUsingFilter'>Szűrővel kiválasztott feldolgozások megjelenítése</st>
+<st id='Size'>Méret</st>
+<st id='SlaveNumber'>Slave száma</st>
+<st id='Starting'>Indítás</st>
+<st id='State'>Állapot</st>
+<st id='Stopping'>Leállítás</st>
+<st id='Submit'>Elküld</st>
+<st id='Suspended'>Felfüggesztett</st>
+<st id='Swap'>Csere</st>
+<st id='SwapNode'>Csere csomópont</st>
+<st id='SystemServers'>Rendszer Szerverek</st>
+<st id='SystemServiceNodes'>Rendszer Szolgáltatás Csomópont</st>
+<st id='TargetClusters'>Célcsoportok</st>
+<st id='TimeNotDefined'>Valamelyik az "Időponttól' vagy az 'Időpontig' mezők közül nem definiált</st>
+<st id='ThisPageFromByte'>ez a lap a bájttól</st>
 <st id='ThorMaster'>Thor Master</st>
 <st id='ThorSlave'>Thor Slave</st>
 <st id='ThorSlaves'>Thor Slaves</st>
-<st id='ThorSpare'>Thor Spare</st>
-<st id='To'>to</st>
-<st id='ToByte'>to byte</st>
-<st id='Total'>Total</st>
-<st id='TotalFileSize'>Total file size</st>
-<st id='UpdateMetricsNow'>Update Metrics Now</st>
-<st id='UpTime'>Up Time</st>
-<st id='Unknown'>Unknown</st>
-<st id='ViewConfigurationFile'>View Configuration File</st>
-<st id='ViewColumns'>View Columns</st>
-<st id='ViewDetails'>View Details</st>
-<st id='ViewingPage'>Viewing page</st>
-<st id='ViewLogFile'>View Log File</st>
-<st id='WarnIfAvailableDiskSpaceIsUnder'>Warn if available disk space is under</st>
-<st id='WarnIfAvailableMemoryIsUnder'>Warn if available memory is under</st>
-<st id='WarnIfCPUUsageIsOver'>Warn if CPU usage is over</st>
-<st id='Warning'>Warning</st>
+<st id='ThorSpare'>Thor Tartalék</st>
+<st id='To'>-ig</st>
+<st id='ToByte'>bájtig</st>
+<st id='Total'>Összesen</st>
+<st id='TotalFileSize'>Összes fájl méret</st>
+<st id='UpdateMetricsNow'>Metrika frissítés</st>
+<st id='UpTime'>Aktív idő</st>
+<st id='Unknown'>Ismeretlen</st>
+<st id='ViewConfigurationFile'>Configurációs fájl megjelenítése</st>
+<st id='ViewColumns'>Oszlop nézet</st>
+<st id='ViewDetails'>Részletes nézet</st>
+<st id='ViewingPage'>Lap nézet</st>
+<st id='ViewLogFile'>Log fájl megjelenítése</st>
+<st id='WarnIfAvailableDiskSpaceIsUnder'>Figyelmeztetés, ha a szabad lemezterület mérete kisebb mint</st>
+<st id='WarnIfAvailableMemoryIsUnder'>Figyelmeztetés, ha a szabad memória mérete kisebb mint</st>
+<st id='WarnIfCPUUsageIsOver'>Figyelmeztetés ha a CPU terhelés nagyobb mint</st>
+<st id='Warning'>Figyelmeztetés</st>
 </strings>
 </hpcc>

+ 55 - 14
esp/services/ws_access/ws_accessService.cpp

@@ -28,21 +28,31 @@
 #define MSG_SEC_MANAGER_IS_NULL "Security manager is not found. Please check if the system authentication is set up correctly"
 #define MSG_SEC_MANAGER_ISNT_LDAP "LDAP Security manager is required for this feature. Please enable LDAP in the system configuration"
 
+#define FILE_SCOPE_URL "FileScopeAccess"
+#define FILE_SCOPE_RTYPE "file"
+#define FILE_SCOPE_RTITLE "FileScope"
+
 #define MAX_USERS_DISPLAY 400
 #define MAX_RESOURCES_DISPLAY 3000
 static const long MAXXLSTRANSFER = 5000000;
 
-void checkUser(IEspContext& context)
+void checkUser(IEspContext& context, const char* rtype = NULL, const char* rtitle = NULL, unsigned int SecAccessFlags = SecAccess_Full)
 {
     CLdapSecManager* secmgr = dynamic_cast<CLdapSecManager*>(context.querySecManager());
     if(secmgr == NULL)
         throw MakeStringException(ECLWATCH_INVALID_SEC_MANAGER, MSG_SEC_MANAGER_IS_NULL);
 
+    if (rtype && rtitle && strieq(rtype, FILE_SCOPE_RTYPE) && strieq(rtitle, FILE_SCOPE_RTITLE))
+    {
+        if (!context.validateFeatureAccess(FILE_SCOPE_URL, SecAccessFlags, false))
+            throw MakeStringException(ECLWATCH_DFU_WU_ACCESS_DENIED, "Access to File Scope is denied.");
+        return;
+    }
+
     if(!secmgr->isSuperUser(context.queryUser()))
         throw MakeStringException(ECLWATCH_ADMIN_ACCESS_DENIED, "Access denied, administrators only.");
 }
 
-
 void Cws_accessEx::init(IPropertyTree *cfg, const char *process, const char *service)
 {
     if(cfg == NULL)
@@ -108,9 +118,9 @@ void Cws_accessEx::init(IPropertyTree *cfg, const char *process, const char *ser
         Owned<IEspDnStruct> onedn = createDnStruct();
         onedn->setBasedn(files_basedn);
         onedn->setName("File Scopes");
-        onedn->setRtype("file");
+        onedn->setRtype(FILE_SCOPE_RTYPE);
         m_rawbasedns.append(*onedn.getLink());
-        onedn->setRtitle("FileScope");
+        onedn->setRtitle(FILE_SCOPE_RTITLE);
     }
 
     StringBuffer workunits_basedn;
@@ -1310,11 +1320,27 @@ bool Cws_accessEx::onPermissions(IEspContext &context, IEspBasednsRequest &req,
     return true;
 }
 
+const char* Cws_accessEx::getBaseDN(IEspContext &context, const char* rtype, StringBuffer& baseDN)
+{
+    if(!m_basedns.length())
+        setBasedns(context);
+    ForEachItemIn(y, m_basedns)
+    {
+        IEspDnStruct* curbasedn = &(m_basedns.item(y));
+        if(strieq(curbasedn->getRtype(), rtype))
+        {
+            baseDN.set(curbasedn->getBasedn());
+            return baseDN.str();
+        }
+    }
+    return NULL;
+}
+
 bool Cws_accessEx::onResources(IEspContext &context, IEspResourcesRequest &req, IEspResourcesResponse &resp)
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Read);
 
         CLdapSecManager* secmgr = queryLDAPSecurityManager(context);
         if(secmgr == NULL)
@@ -1324,6 +1350,15 @@ bool Cws_accessEx::onResources(IEspContext &context, IEspResourcesRequest &req,
         const char* filterInput = req.getSearchinput();
         const char* basedn = req.getBasedn();
         const char* rtypestr = req.getRtype();
+        if (!rtypestr || !*rtypestr)
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Rtype not specified");
+        StringBuffer baseDN;
+        if (!basedn || !*basedn)
+        {
+            basedn = getBaseDN(context, rtypestr, baseDN);
+            if (!basedn || !*basedn)
+                throw MakeStringException(ECLWATCH_INVALID_INPUT, "BaseDN not found");
+        }
 
         const char* moduletemplate = NULL;
         ForEachItemIn(x, m_basedns)
@@ -1473,7 +1508,7 @@ bool Cws_accessEx::onResourceAddInput(IEspContext &context, IEspResourceAddInput
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Full);
 
         resp.setBasedn(req.getBasedn());
         resp.setRtype(req.getRtype());
@@ -1508,7 +1543,7 @@ bool Cws_accessEx::onResourceAdd(IEspContext &context, IEspResourceAddRequest &r
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Full);
 
         ISecManager* secmgr = context.querySecManager();
 
@@ -1615,7 +1650,7 @@ bool Cws_accessEx::onResourceDelete(IEspContext &context, IEspResourceDeleteRequ
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Full);
 
         CLdapSecManager* secmgr = (CLdapSecManager*)(context.querySecManager());
 
@@ -1709,7 +1744,7 @@ bool Cws_accessEx::onResourcePermissions(IEspContext &context, IEspResourcePermi
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Read);
 
         ISecManager* secmgr = context.querySecManager();
 
@@ -1795,7 +1830,7 @@ bool Cws_accessEx::onPermissionAddInput(IEspContext &context, IEspPermissionAddR
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Full);
 
         resp.setBasedn(req.getBasedn());
         resp.setRname(req.getRname());
@@ -1833,7 +1868,7 @@ bool Cws_accessEx::onPermissionsResetInput(IEspContext &context, IEspPermissions
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Full);
 
         resp.setBasedn(req.getBasedn());
         //resp.setRname(req.getRname());
@@ -1919,6 +1954,8 @@ bool Cws_accessEx::onPermissionsResetInput(IEspContext &context, IEspPermissions
 
 bool Cws_accessEx::onClearPermissionsCache(IEspContext &context, IEspClearPermissionsCacheRequest &req, IEspClearPermissionsCacheResponse &resp)
 {
+    checkUser(context);
+
     ISecManager* secmgr = context.querySecManager();
     if(secmgr == NULL)
         throw MakeStringException(ECLWATCH_INVALID_SEC_MANAGER, MSG_SEC_MANAGER_IS_NULL);
@@ -1965,6 +2002,8 @@ bool Cws_accessEx::onQueryScopeScansEnabled(IEspContext &context, IEspQueryScope
 
 bool Cws_accessEx::onEnableScopeScans(IEspContext &context, IEspEnableScopeScansRequest &req, IEspEnableScopeScansResponse &resp)
 {
+    checkUser(context, FILE_SCOPE_RTYPE, FILE_SCOPE_RTITLE, SecAccess_Full);
+
     StringBuffer retMsg;
     int rc = enableDisableScopeScans(context, true, retMsg);
     resp.updateScopeScansStatus().setIsEnabled(rc == 0);
@@ -1975,6 +2014,8 @@ bool Cws_accessEx::onEnableScopeScans(IEspContext &context, IEspEnableScopeScans
 
 bool Cws_accessEx::onDisableScopeScans(IEspContext &context, IEspDisableScopeScansRequest &req, IEspDisableScopeScansResponse &resp)
 {
+    checkUser(context, FILE_SCOPE_RTYPE, FILE_SCOPE_RTITLE, SecAccess_Full);
+
     StringBuffer retMsg;
     int rc = enableDisableScopeScans(context, false, retMsg);
     resp.updateScopeScansStatus().setIsEnabled(rc != 0);
@@ -2050,7 +2091,7 @@ bool Cws_accessEx::onPermissionsReset(IEspContext &context, IEspPermissionsReset
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Full);
 
         resp.setBasedn(req.getBasedn());
         resp.setRname(req.getRname());
@@ -2418,7 +2459,7 @@ bool Cws_accessEx::onPermissionAction(IEspContext &context, IEspPermissionAction
 {
     try
     {
-        checkUser(context);
+        checkUser(context, req.getRtype(), req.getRtitle(), SecAccess_Full);
 
         resp.setBasedn(req.getBasedn());
         resp.setRname(req.getRname());
@@ -3337,7 +3378,7 @@ bool Cws_accessEx::onFilePermission(IEspContext &context, IEspFilePermissionRequ
                 throw MakeStringException(ECLWATCH_INVALID_SEC_MANAGER, MSG_SEC_MANAGER_IS_NULL);
         }
 
-        checkUser(context);
+        checkUser(context, FILE_SCOPE_RTYPE, FILE_SCOPE_RTITLE, SecAccess_Read);
 
         //Get all users for input form
         int numusers = secmgr->countUsers("", MAX_USERS_DISPLAY);

+ 2 - 0
esp/services/ws_access/ws_accessService.hpp

@@ -53,6 +53,7 @@ public:
             ensureNavLink(*folder, "Users", "/ws_access/Users", "Users");
             ensureNavLink(*folder, "Groups", "/ws_access/Groups", "Groups");
             ensureNavLink(*folder, "Permissions", "/ws_access/Permissions", "Permissions");
+            ensureNavLink(*folder, "FileScopes", "/ws_access/Resources?rtype=file&rtitle=FileScope", "FileScopes");
         }
     }
 
@@ -67,6 +68,7 @@ class Cws_accessEx : public Cws_access
     SecResourceType str2type(const char* rtstr);
 
     void setBasedns(IEspContext &context);
+    const char* getBaseDN(IEspContext &context, const char* rtype, StringBuffer& baseDN);
     bool permissionAddInputOnResource(IEspContext &context, IEspPermissionAddRequest &req, IEspPermissionAddResponse &resp);
     bool permissionAddInputOnAccount(IEspContext &context, const char* accountName, IEspPermissionAddRequest &req, IEspPermissionAddResponse &resp);
     bool getNewFileScopePermissions(ISecManager* secmgr, IEspResourceAddRequest &req, StringBuffer& existingResource, StringArray& newResources);

+ 4 - 1
esp/services/ws_packageprocess/ws_packageprocessService.cpp

@@ -350,7 +350,10 @@ void getPkgInfo(const char *target, const char *process, StringBuffer &info)
     Owned<IPropertyTree> tree = createPTree("PackageMaps");
     Owned<IPropertyTree> pkgSetRegistry = getPkgSetRegistry(process, true);
     if (!pkgSetRegistry)
-        throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve package information from dali for process %s", (process && *process) ? process : "*");
+    {
+        toXML(tree, info);
+        return;
+    }
     StringBuffer xpath("PackageMap[@active='1']");
     if (target && *target)
         xpath.appendf("[@querySet='%s']", target);

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

@@ -85,6 +85,9 @@ define([
                     mode = "xml";
                 }
 
+                if (params.readOnly !== undefined)
+                    this.readOnly = params.readOnly;
+
                 this.editor = CodeMirror.fromTextArea(document.getElementById(this.id + "EclCode"), {
                     tabMode: "indent",
                     matchBrackets: true,

+ 14 - 0
esp/src/eclwatch/FileSpray.js

@@ -125,6 +125,20 @@ define([
             if (options) {
                 declare.safeMixin(this, options);
             }
+            this.userAddedFiles = {};
+        },
+        addUserFile: function (_file) {
+            var fileListStore = new FileListStore({
+                parent: null
+            });
+            var file = fileListStore.get(_file.calculatedID);
+            fileListStore.update(_file.calculatedID, _file);
+            this.userAddedFiles[file.calculatedID] = file;
+        },
+        postProcessResults: function (items) {
+            for (var key in this.userAddedFiles) {
+                items.push(this.userAddedFiles[key]);
+            }
         },
         preProcessRow: function (row) {
             lang.mixin(row, {

+ 22 - 0
esp/src/eclwatch/LZBrowseWidget.js

@@ -268,6 +268,28 @@ define([
             }
         },
 
+        _onAddFile: function (event) {
+            if (registry.byId(this.id + "AddFileForm").validate()) {
+                var tmpFile = domForm.toObject(this.id + "AddFileForm");
+                var dropZone = lang.mixin(this.landingZoneStore.get(tmpFile.NetAddress), {
+                    NetAddress: tmpFile.NetAddress
+                });
+                var fullPathParts = tmpFile.fullPath.split("/");
+                if (fullPathParts.length === 1) {
+                    fullPathParts = tmpFile.fullPath.split("\\");
+                }
+                var file = lang.mixin(this.landingZoneStore.get(tmpFile.NetAddress + tmpFile.fullPath), {
+                    displayName: fullPathParts[fullPathParts.length - 1],
+                    fullPath: tmpFile.fullPath,
+                    isDir: false,
+                    DropZone: dropZone
+                });
+                this.landingZoneStore.addUserFile(file);
+                this.refreshGrid();
+                registry.byId(this.id + "AddFileDropDown").closeDropDown();
+            }
+        },
+
         _onSprayFixed: function (event) {
             var context = this;
             this._spraySelectedOneAtATime("SprayFixedDropDown", "SprayFixedForm", function (request, item) {

+ 35 - 10
esp/src/eclwatch/PackageMapQueryWidget.js

@@ -38,6 +38,7 @@ define([
     "dojo/data/ItemFileWriteStore",
 
     "hpcc/_TabContainerWidget",
+    "hpcc/DelayLoadWidget",
     "hpcc/PackageMapDetailsWidget",
     "hpcc/PackageMapValidateWidget",
     "hpcc/WsPackageMaps",
@@ -57,7 +58,7 @@ define([
 ], function (declare, lang, i18n, nlsHPCC, arrayUtil, dom, domConstruct, domForm, ObjectStore, on, topic,
     _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, registry,
     Uploader, FileUploader, EnhancedGrid, Pagination, IndirectSelection, ItemFileWriteStore,
-    _TabContainerWidget, PackageMapDetailsWidget, PackageMapValidateWidget,
+    _TabContainerWidget, DelayLoadWidget, PackageMapDetailsWidget, PackageMapValidateWidget,
     WsPackageMaps, ESPPackageProcess, SFDetailsWidget,
     template) {
     return declare("PackageMapQueryWidget", [_TabContainerWidget, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
@@ -75,6 +76,8 @@ define([
         processFilters: null,
         addPackageMapDialog: null,
         validateTab: null,
+        validateTabInitialized: false,
+        params: null,
 
         buildRendering: function (args) {
             this.inherited(arguments);
@@ -133,6 +136,11 @@ define([
 
         _onChangeTarget: function (event) {
             this.updateProcessSelections(this.processSelect, this.processesToList, this.targetSelect.getValue());
+            this.refreshGrid();
+        },
+
+        _onChangeProcess: function (event) {
+            this.refreshGrid();
         },
 
         _onChangeAddProcessMapTarget: function (event) {
@@ -330,6 +338,7 @@ define([
                         context.processFilters = response.ProcessFilters.Item;
                     }
                     context.initPackagesGrid();
+                    context.initTabs();
                 },
                 error: function (errMsg, errStack) {
                     context.showErrors(errMsg, errStack);
@@ -397,17 +406,34 @@ define([
                 return;
 
             this.initalized = true;
+            this.params = params;
+            this.getSelections();
+        },
 
-            this.validateTab = new PackageMapValidateWidget({
-                id: this.id + "_ValidatePackageMap",
-                title: this.i18n.ValidatePackageMap,
-                params: params
-            });
-            //this.tabMap[this.id + "_ValidatePackageMap"] = this.validateTab;
-            this.tabContainer.addChild(this.validateTab, 1);
+        initTabs: function() {
+            this.params.targets = this.targets;
+            if (!this.validateTabInitialized) {
+                this.validateTabInitialized = true;
+                this.validateTab = this.initValidateTab("ValidatePackageMap", {
+                    title: "Validate Package Map",
+                    params: this.params
+                });
+            }
 
             this.tabContainer.selectChild(this.packagesTab);
-            this.getSelections();
+        },
+
+        initValidateTab: function (id, params) {
+            id = this.createChildTabID(id);
+            var retVal = new DelayLoadWidget({
+                id: id,
+                title: params.title,
+                closable: false,
+                delayWidget: "PackageMapValidateWidget",
+                params: params
+            });
+            this.tabContainer.addChild(retVal, 1);
+            return retVal;
         },
 
         initPackagesGrid: function() {
@@ -430,7 +456,6 @@ define([
             ]);
             var objStore = ESPPackageProcess.CreatePackageMapQueryObjectStore();
             this.packagesGrid.setStore(objStore);
-            this.packagesGrid.setQuery(this.getFilter());
 
             var context = this;
             this.packagesGrid.on("RowDblClick", function (evt) {

+ 240 - 0
esp/src/eclwatch/PackageMapValidateContentWidget.js

@@ -0,0 +1,240 @@
+/*##############################################################################
+#    HPCC SYSTEMS software Copyright (C) 2014 HPCC Systems.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/i18n",
+    "dojo/i18n!./nls/hpcc",
+    "dojo/dom",
+    "dojo/query",
+    "dojo/topic",
+    "dijit/registry",
+
+    "hpcc/_Widget",
+    "hpcc/ECLSourceWidget",
+    "hpcc/WsPackageMaps",
+
+    "dojo/text!../templates/PackageMapValidateContentWidget.html",
+
+    "dijit/layout/BorderContainer",
+    "dijit/layout/TabContainer",
+    "dijit/layout/ContentPane",
+    "dijit/form/Button"
+], function (declare, lang, i18n, nlsHPCC, dom, query, topic, registry,
+                _Widget, EclSourceWidget, WsPackageMaps,
+                template) {
+    return declare("PackageMapValidateContentWidget", [_Widget], {
+        templateString: template,
+        baseClass: "PackageMapValidateContentWidget",
+        i18n: nlsHPCC,
+
+        initalized: false,
+        targets: null,
+
+        targetSelectControl: null,
+        processSelectControl: null,
+        selectFileControl: null,
+        validateButton: null,
+        editorControl: null,
+        resultControl: null,
+
+        constructor: function() {
+            this.processes = new Array();
+        },
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+            this.borderContainer.resize();
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        getTitle: function () {
+            return this.i18n.ValidatePackageContent;
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this.borderContainer = registry.byId(this.id + "BorderContainer");
+            this.targetSelectControl = registry.byId(this.id + "TargetSelect");
+            this.processSelectControl = registry.byId(this.id + "ProcessSelect");
+            this.validateButton = registry.byId(this.id + "ValidateBtn");
+        },
+
+        //  Init  ---
+        init: function (params) {
+            if (this.initalized)
+                return;
+
+            this.initalized = true;
+            if (params.targets !== undefined)
+                this.initSelections(params.targets);
+
+            this.editorControl = registry.byId(this.id + "Source");
+            this.editorControl.init(params);
+            this.editorControl.setText(this.i18n.LoadPackageContentHere);
+            this.initResultDisplay();
+
+            var context = this;
+            this.selectFileControl = document.getElementById(this.id + "SelectFile");
+            this.selectFileControl.addEventListener('change', function(event) {
+                var reader = new FileReader();
+                reader.onload = function(e){
+                    context.editorControl.setText(e.target.result);
+                };
+                reader.readAsText(event.target.files[0]);
+            }, false);
+        },
+
+        initSelections: function (targets) {
+            this.targets = targets;
+            if (this.targets.length > 0) {
+                var defaultTarget = 0;
+                for (var i = 0; i < this.targets.length; ++i) {
+                    if ((defaultTarget === 0) && (this.targets[i].Type === 'roxie'))
+                        defaultTarget = i; //first roxie
+                    this.targetSelectControl.options.push({label: this.targets[i].Name, value: this.targets[i].Name});
+                }
+                this.targetSelectControl.set("value", this.targets[defaultTarget].Name);
+                if (this.targets[defaultTarget].Processes !== undefined)
+                    this.updateProcessSelections(this.targets[defaultTarget], '');
+            }
+        },
+
+        updateProcessSelections: function (target, targetName) {
+            this.processSelectControl.removeOption(this.processSelectControl.getOptions());
+            if (target !== null)
+                this.addProcessSelections(target.Processes.Item);
+            else {
+                for (var i = 0; i < this.targets.length; ++i) {
+                    var target = this.targets[i];
+                    if ((target.Processes !== undefined) && (targetName === target.Name)) {
+                        this.addProcessSelections(target.Processes.Item);
+                        break;
+                    }
+                }
+            }
+            this.processSelectControl.options.push({label: this.i18n.ANY, value: 'ANY' });
+            this.processSelectControl.set("value", '');
+        },
+
+        addProcessSelections: function (processes) {
+            this.processes.length = 0;
+
+            if (processes.length < 1)
+                return;
+            for (var i = 0; i < processes.length; ++i) {
+                var process = processes[i];
+                if ((this.processes !== null) && (this.processes.indexOf(process) !== -1))
+                    continue;
+                this.processes.push(process);
+                this.processSelectControl.options.push({label: process, value: process});
+            }
+        },
+
+        initResultDisplay: function () {
+            this.resultControl = registry.byId(this.id + "Result");
+            this.resultControl.init({sourceMode: 'text/plain', readOnly: true});
+            this.resultControl.setText(this.i18n.ValidateResultHere);
+        },
+
+        //  action
+        _onChangeTarget: function (event) {
+            this.targetSelected  = this.targetSelectControl.getValue();
+            this.updateProcessSelections(null, this.targetSelected);
+        },
+
+        _onLoadBtnClicked: function (event) {
+            this.selectFileControl.click();
+        },
+
+        _onValidate: function (evt) {
+            var content = this.editorControl.getText();
+            if (content === '') {
+                alert(this.i18n.PackageContentNotSet);
+                return;
+            }
+            var request = { target: this.targetSelectControl.getValue() };
+            request['content'] = content;
+
+            var context = this;
+            this.resultControl.setText("");
+            this.validateButton.set("disabled", true);
+            WsPackageMaps.validatePackage(request, {
+                load: function (response) {
+                    var responseText = context.validateResponseToText(response);
+                    if (responseText === '')
+                        context.resultControl.setText(context.i18n.Empty);
+                    else {
+                        responseText = context.i18n.ValidateResult + responseText;
+                        context.resultControl.setText(responseText);
+                    }
+                    context.validateButton.set("disabled", false);
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
+                    context.validateButton.set("disabled", false);
+                }
+            });
+        },
+
+        validateResponseToText: function (response) {
+            var text = "";
+            if (!lang.exists("Errors", response) || (response.Errors.length < 1))
+                text += this.i18n.NoErrorFound;
+            else
+                text = this.addArrayToText(this.i18n.Errors, response.Errors, text);
+            if (!lang.exists("Warnings", response) || (response.Warnings.length < 1))
+                text += this.i18n.NoWarningFound;
+            else
+                text = this.addArrayToText(this.i18n.Warnings, response.Warnings, text);
+
+            text += "\n";
+            text = this.addArrayToText(this.i18n.QueriesNoPackage, response.queries.Unmatched, text);
+            text = this.addArrayToText(this.i18n.PackagesNoQuery, response.packages.Unmatched, text);
+            text = this.addArrayToText(this.i18n.FilesNoPackage, response.files.Unmatched, text);
+            return text;
+        },
+
+        addArrayToText: function (arrayTitle, arrayItems, text) {
+            if ((arrayItems.Item !== undefined) && (arrayItems.Item.length > 0)) {
+                text += arrayTitle + ":\n";
+                for (i=0;i<arrayItems.Item.length;i++)
+                    text += "  " + arrayItems.Item[i] + "\n";
+                text += "\n";
+            }
+            return text;
+        },
+
+        showErrors: function (errMsg, errStack) {
+            topic.publish("hpcc/brToaster", {
+                Severity: "Error",
+                Source: errMsg,
+                Exceptions: [{ Message: errStack }]
+            });
+        }
+    });
+});

+ 148 - 98
esp/src/eclwatch/PackageMapValidateWidget.js

@@ -19,72 +19,82 @@ define([
     "dojo/i18n",
     "dojo/i18n!./nls/hpcc",
     "dojo/dom",
-    "dojo/dom-attr",
-    "dojo/dom-class",
+    "dojo/query",
     "dojo/topic",
-
-    "dijit/layout/_LayoutWidget",
-    "dijit/_TemplatedMixin",
-    "dijit/_WidgetsInTemplateMixin",
     "dijit/registry",
 
+    "hpcc/_Widget",
+    "hpcc/_TabContainerWidget",
+    "hpcc/DelayLoadWidget",
+    "hpcc/ECLSourceWidget",
     "hpcc/WsPackageMaps",
 
     "dojo/text!../templates/PackageMapValidateWidget.html",
 
-    "dijit/form/Form",
-    "dijit/form/Button",
-    "dijit/form/RadioButton",
-    "dijit/form/Select",
-    "dijit/form/SimpleTextarea"
-], function (declare, lang, i18n, nlsHPCC, dom, domAttr, domClass, topic,
-    _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, registry,
-    WsPackageMaps, template) {
-    return declare("PackageMapValidateWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+    "dijit/layout/BorderContainer",
+    "dijit/layout/TabContainer",
+    "dijit/layout/ContentPane",
+    "dijit/form/Button"
+], function (declare, lang, i18n, nlsHPCC, dom, query, topic, registry,
+                _Widget, _TabContainerWidget, DelayLoadWidget, EclSourceWidget, WsPackageMaps,
+                template) {
+    return declare("PackageMapValidateWidget", [_TabContainerWidget], {
         templateString: template,
         baseClass: "PackageMapValidateWidget",
         i18n: nlsHPCC,
-        validateForm: null,
-        targetSelect: null,
-        packageContent: null,
-        validateResult: null,
-        validateButton: null,
-        targets: null,
-        processes: new Array(),
+
         initalized: false,
+        targets: null,
 
-        buildRendering: function (args) {
-            this.inherited(arguments);
+        targetSelectControl: null,
+        processSelectControl: null,
+        validateButton: null,
+        editorControl: null,
+        resultControl: null,
+
+        validatePackageMapContentWidget: null,
+        validatePackageMapContentWidgetLoaded: false,
+
+        constructor: function() {
+            this.processes = new Array();
         },
 
-        postCreate: function (args) {
+        buildRendering: function (args) {
             this.inherited(arguments);
-            this.validateForm = registry.byId(this.id + "ValidatePM");
-            this.targetSelect = registry.byId(this.id + "TargetSelect");
-            this.processSelect = registry.byId(this.id + "ProcessSelect");
-            this.packageContent = registry.byId(this.id + "Package");
-            this.validateButton = registry.byId(this.id + "Validate");
-            this.validateResult = registry.byId(this.id + "ValidateResult");
         },
 
         startup: function (args) {
             this.inherited(arguments);
         },
 
-        resize: function (args) {
+        destroy: function (args) {
             this.inherited(arguments);
         },
 
-        layout: function (args) {
+        getTitle: function () {
+            return this.i18n.ValidateActivePackageMap;
+        },
+
+        postCreate: function (args) {
             this.inherited(arguments);
+            this.targetSelectControl = registry.byId(this.id + "TargetSelect");
+            this.processSelectControl = registry.byId(this.id + "ProcessSelect");
+            this.validateButton = registry.byId(this.id + "ValidateBtn");
+            this.validatePackageMapContentWidget = registry.byId(this.id + "_ValidatePackageMapContent");
         },
 
-        init: function (params, targets) {
+        //  init this page
+        init: function (params) {
             if (this.initalized)
                 return;
+
             this.initalized = true;
-            this.validateResult.set('style', 'visibility:hidden');
-            this.initSelections(targets);
+            if (params.params.targets !== undefined)
+                this.initSelections(params.params.targets);
+
+            this.editorControl = registry.byId(this.id + "Source");
+            this.editorControl.init(params);
+            this.initResultDisplay();
         },
 
         initSelections: function (targets) {
@@ -92,98 +102,110 @@ define([
             if (this.targets.length > 0) {
                 var defaultTarget = 0;
                 for (var i = 0; i < this.targets.length; ++i) {
-                    if ((defaultTarget == 0) && (this.targets[i].Type == 'roxie'))
+                    if ((defaultTarget === 0) && (this.targets[i].Type === 'roxie'))
                         defaultTarget = i; //first roxie
-                    this.targetSelect.options.push({label: this.targets[i].Name, value: this.targets[i].Name});
+                    this.targetSelectControl.options.push({label: this.targets[i].Name, value: this.targets[i].Name});
+                }
+                this.targetSelectControl.set("value", this.targets[defaultTarget].Name);
+                if (this.targets[defaultTarget].Processes !== undefined)
+                    this.updateProcessSelections(this.targets[defaultTarget], '');
+            }
+        },
+
+        updateProcessSelections: function (target, targetName) {
+            this.processSelectControl.removeOption(this.processSelectControl.getOptions());
+            if (target !== null)
+                this.addProcessSelections(target.Processes.Item);
+            else {
+                for (var i = 0; i < this.targets.length; ++i) {
+                    var target = this.targets[i];
+                    if ((target.Processes !== undefined) && (targetName === target.Name)) {
+                        this.addProcessSelections(target.Processes.Item);
+                        break;
+                    }
                 }
-                this.targetSelect.set("value", this.targets[defaultTarget].Name);
-                if (this.targets[defaultTarget].Processes != undefined)
-                    this.updateProcessSelections(this.targets[defaultTarget].Name);
             }
+            this.processSelectControl.options.push({label: this.i18n.ANY, value: 'ANY' });
+            this.processSelectControl.set("value", '');
         },
 
         addProcessSelections: function (processes) {
+            this.processes.length = 0;
             for (var i = 0; i < processes.length; ++i) {
                 var process = processes[i];
-                if ((this.processes != null) && (this.processes.indexOf(process) != -1))
+                if ((this.processes !== null) && (this.processes.indexOf(process) !== -1))
                     continue;
                 this.processes.push(process);
-                this.processSelect.options.push({label: process, value: process});
+                this.processSelectControl.options.push({label: process, value: process});
             }
         },
 
-        updateProcessSelections: function (targetName) {
-            this.processSelect.removeOption(this.processSelect.getOptions());
-            for (var i = 0; i < this.targets.length; ++i) {
-                var target = this.targets[i];
-                if ((target.Processes != undefined) && ((targetName == '') || (targetName == target.Name)))
-                    this.addProcessSelections(target.Processes.Item);
-            }
-            this.processSelect.options.push({label: this.i18n.ANY, value: 'ANY' });
-            this.processSelect.set("value", '');
+        initResultDisplay: function () {
+            this.resultControl = registry.byId(this.id + "Result");
+            this.resultControl.init({sourceMode: 'text/plain', readOnly: true});
+            this.resultControl.setText(this.i18n.ValidateResultHere);
         },
 
-        addArrayToText: function (arrayTitle, arrayItems, text) {
-            if ((arrayItems.Item != undefined) && (arrayItems.Item.length > 0)) {
-                text += arrayTitle + ":\n";
-                for (i=0;i<arrayItems.Item.length;i++)
-                    text += "  " + arrayItems.Item[i] + "\n";
-                text += "\n";
+        //  init tab
+        initTab: function () {
+            var currSel = this.getSelectedChild();
+            if (!this.validatePackageMapContentWidgetLoaded && (currSel.id === this.validatePackageMapContentWidget.id)) {
+                this.validatePackageMapContentWidgetLoaded = true;
+                this.validatePackageMapContentWidget.init({
+                    targets: this.targets
+                });
             }
-            return text;
         },
 
-        validateResponseToText: function (response) {
-            var text = "";
-            if (!lang.exists("Errors", response) || (response.Errors.length < 1))
-                text += this.i18n.NoErrorFound;
-            else
-                text = this.addArrayToText(this.i18n.Errors, response.Errors, text);
-            if (!lang.exists("Warnings", response) || (response.Warnings.length < 1))
-                text += this.i18n.Warnings;
-            else
-                text = this.addArrayToText(this.i18n.Warnings, response.Warnings, text);
-
-            text += "\n";
-            text = this.addArrayToText(this.i18n.QueriesNoPackage, response.queries.Unmatched, text);
-            text = this.addArrayToText(this.i18n.PackagesNoQuery, response.packages.Unmatched, text);
-            text = this.addArrayToText(this.i18n.FilesNoPackage, response.files.Unmatched, text);
-            return text;
+        //  action
+        _onChangeTarget: function (event) {
+            this.targetSelected  = this.targetSelectControl.getValue();
+            this.updateProcessSelections(null, this.targetSelected);
         },
 
-        _onChangeTarget: function (event) {
-            this.processes.length = 0;
-            this.targetSelected  = this.targetSelect.getValue();
-            this.updateProcessSelections(this.targetSelected);
-        },
-
-        _onValidate: function (event) {
-            var request = { target: this.targetSelect.getValue() };
-            var type = this.validateForm.attr('value').ValidateType;
-            if (type == 'ActivePM') {
-                request['active'] = true;
-                request['process'] = this.processSelect.getValue();
-            } else {
-                var content = this.packageContent.getValue();
-                if (content == '') {
-                    alert(this.i18n.PackageContentNotSet);
-                    return;
+        _onChangeProcess: function (event) {
+            var process = this.processSelectControl.getValue();
+            if (process === 'ANY')
+                process = '*';
+
+            var context = this;
+            this.editorControl.setText('');
+            WsPackageMaps.getPackage({
+                    target: this.targetSelectControl.getValue(),
+                    process: process
+                }, {
+                load: function (content) {
+                    if (content !== '') {
+                        context.editorControl.setText(content);;
+                    }
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
                 }
-                request['content'] = content;
+            });
+        },
+
+        _onValidate: function (evt) {
+            var content = this.editorControl.getText();
+            if (content === '') {
+                alert(this.i18n.PackageContentNotSet);
+                return;
             }
+            var request = { target: this.targetSelectControl.getValue() };
+            request['content'] = content;
+
             var context = this;
-            this.validateResult.setValue("");
+            this.resultControl.setText("");
             this.validateButton.set("disabled", true);
             WsPackageMaps.validatePackage(request, {
                 load: function (response) {
                     var responseText = context.validateResponseToText(response);
-                    if (responseText == '')
-                        context.validateResult.setValue(context.i18n.Empty);
+                    if (responseText === '')
+                        context.resultControl.setText(context.i18n.Empty);
                     else {
                         responseText = context.i18n.ValidateResult + responseText;
-                        context.validateResult.setValue(responseText);
+                        context.resultControl.setText(responseText);
                     }
-                    context.validateResult.set('style', 'visibility:visible');
                     context.validateButton.set("disabled", false);
                 },
                 error: function (errMsg, errStack) {
@@ -193,6 +215,34 @@ define([
             });
         },
 
+        validateResponseToText: function (response) {
+            var text = "";
+            if (!lang.exists("Errors", response) || (response.Errors.length < 1))
+                text += this.i18n.NoErrorFound;
+            else
+                text = this.addArrayToText(this.i18n.Errors, response.Errors, text);
+            if (!lang.exists("Warnings", response) || (response.Warnings.length < 1))
+                text += this.i18n.NoWarningFound;
+            else
+                text = this.addArrayToText(this.i18n.Warnings, response.Warnings, text);
+
+            text += "\n";
+            text = this.addArrayToText(this.i18n.QueriesNoPackage, response.queries.Unmatched, text);
+            text = this.addArrayToText(this.i18n.PackagesNoQuery, response.packages.Unmatched, text);
+            text = this.addArrayToText(this.i18n.FilesNoPackage, response.files.Unmatched, text);
+            return text;
+        },
+
+        addArrayToText: function (arrayTitle, arrayItems, text) {
+            if ((arrayItems.Item !== undefined) && (arrayItems.Item.length > 0)) {
+                text += arrayTitle + ":\n";
+                for (var i=0;i<arrayItems.Item.length;i++)
+                    text += "  " + arrayItems.Item[i] + "\n";
+                text += "\n";
+            }
+            return text;
+        },
+
         showErrors: function (errMsg, errStack) {
             topic.publish("hpcc/brToaster", {
                 Severity: "Error",

+ 26 - 0
esp/src/eclwatch/WsPackageMaps.js

@@ -58,6 +58,32 @@ define([
             return false;
         },
 
+        getPackage: function (params, callback) {
+            var request = {
+                Target: params.target,
+                Process: params.process
+            };
+
+            var context = this;
+            return ESPRequest.send("WsPackageProcess", "GetPackage", {
+                request: request,
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("GetPackageResponse.status", response),
+                        response.GetPackageResponse.status))
+                    {
+                        if (!lang.exists("GetPackageResponse.Info", response))
+                            callback.load(i18n.NoContent);
+                        else
+                            callback.load(response.GetPackageResponse.Info);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        },
+
         getPackageMapById: function (params, callback) {
             var request = {
                 PackageMapId: params.packageMap

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

@@ -14,6 +14,7 @@ define({root:
     Activity: "Activity",
     ActualSize: "Actual Size",
     Add: "Add",
+    AddFile: "Add File",
     AddGroup: "Add Group",
     AdditionalResources: "Additional Resources",
     AddProcessMap: "Add Package Map",
@@ -202,6 +203,8 @@ define({root:
     Line: "Line",
     LineTerminators: "Line Terminators",
     Links: "Links",
+    LoadPackageContentHere: "(Load package content here)",
+    LoadPackageFromFile: "Load Package from a file",
     Loading: "Loading...",
     LoadingCachedLayout: "Loading Cached Layout...",
     LoadingData: "Loading Data...",
@@ -491,8 +494,11 @@ define({root:
     Users: "Users",
     UseSingleConnection: "Use Single Connection",
     Validate: "Validate",
+    ValidateActivePackageMap: "Validate Active Package Map",
+    ValidatePackageContent: "Validate Package Content",
     ValidatePackageMap: "Validate Package Map",
     ValidateResult: "=====Validate Result=====\n\n",
+    ValidateResultHere: "(Validation result)",
     Value: "Value",
     Variable: "Variable",
     Variables: "Variables",

+ 15 - 0
esp/src/eclwatch/templates/LZBrowseWidget.html

@@ -12,6 +12,21 @@
                     <div id="${id}Download" data-dojo-attach-event="onClick:_onDownload" data-dojo-type="dijit.form.Button">${i18n.Download}</div>
                     <div id="${id}Delete" data-dojo-attach-event="onClick:_onDelete" data-dojo-type="dijit.form.Button">${i18n.Delete}</div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
+                    <div id="${id}AddFileDropDown" data-dojo-type="dijit.form.DropDownButton">
+                        <span>${i18n.AddFile}</span>
+                        <div data-dojo-type="dijit.TooltipDialog">
+                            <div id="${id}AddFileForm" style="width: 530px;" onsubmit="return false;" data-dojo-type="dijit.form.Form">
+                                <div data-dojo-type="hpcc.TableContainer">
+                                    <input id="${id}AddFileIP" title="${i18n.IP}:" style="width: 95%;" name="NetAddress" data-dojo-type="dijit.form.TextBox" />
+                                    <input id="${id}AddFilePath" title="${i18n.Path}:" style="width: 95%;" name="fullPath" data-dojo-props="trim: true, placeHolder:'${i18n.NamePrefixPlaceholder}'" data-dojo-type="dijit.form.TextBox" />
+                                </div>
+                                <div class="dijitDialogPaneActionBar">
+                                    <button type="submit" data-dojo-attach-event="onClick:_onAddFile" data-dojo-type="dijit.form.Button">${i18n.Add}</button>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <span data-dojo-type="dijit.ToolbarSeparator"></span>
                     <b>${i18n.Spray}:</b>
                     <div id="${id}SprayFixedDropDown" data-dojo-type="dijit.form.DropDownButton">
                         <span>${i18n.Fixed}</span>

+ 1 - 1
esp/src/eclwatch/templates/PackageMapQueryWidget.html

@@ -8,7 +8,7 @@
                     </div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
                     <b>${i18n.Process}:</b>
-                    <div id="${id}ProcessSelect" name="ProcessSelect" style="width: 8em;" data-dojo-type="dijit.form.Select">
+                    <div id="${id}ProcessSelect" name="ProcessSelect" style="width: 8em;" data-dojo-attach-event="onChange:_onChangeProcess" data-dojo-type="dijit.form.Select">
                     </div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
                     <!--b>Process Filter:</b>

+ 21 - 0
esp/src/eclwatch/templates/PackageMapValidateContentWidget.html

@@ -0,0 +1,21 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}SubmitPane" class="topPanel" data-dojo-props="region: 'top'" data-dojo-type="dijit.layout.ContentPane">
+            <div style="display: inline-block; vertical-align: middle">
+                <label for="${id}TargetSelect">${i18n.Target}</label>
+                <div id="${id}TargetSelect" name="TargetSelect" style="width: 8em;" data-dojo-attach-event="onChange:_onChangeTarget" data-dojo-type="dijit.form.Select">
+                </div>
+                <label for="${id}ProcessSelect">${i18n.Process}</label>
+                <div id="${id}ProcessSelect" name="ProcessSelect" style="width: 8em;" data-dojo-type="dijit.form.Select">
+                </div>
+                <button id="${id}LoadBtn" data-dojo-attach-event="onClick:_onLoadBtnClicked" data-dojo-type="dijit.form.Button">${i18n.LoadPackageFromFile}</button>
+                <button id="${id}ValidateBtn" data-dojo-attach-event="onClick:_onValidate" data-dojo-type="dijit.form.Button">${i18n.Validate}</button>
+                <div style='height: 0px;width:0px; overflow:hidden;'><input id="${id}SelectFile" type="file"/></div>
+            </div>
+        </div>
+        <div id="${id}Source" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="ECLSourceWidget">
+        </div>
+        <div id="${id}Result" title="${i18n.Results}" style="width: 480px;" data-dojo-props="minSize:120, region: 'right', splitter:true" data-dojo-type="ECLSourceWidget">
+        </div>
+    </div>
+</div>

+ 21 - 41
esp/src/eclwatch/templates/PackageMapValidateWidget.html

@@ -1,45 +1,25 @@
 <div class="${baseClass}">
-    <div id="${id}ValidatePM" data-dojo-id="${id}ValidatePM" data-dojo-type="dijit.form.Form" encType="multipart/form-data" action="" method="">
-        <table id="${id}ValidatePMTable">
-            <tr>
-                <td width="140px"><b>${i18n.Target}:</b></td>
-                <td>
-                    <div id="${id}TargetSelect" name="TargetSelect" style="width: 8em;" data-dojo-attach-event="onChange:_onChangeTarget" data-dojo-type="dijit.form.Select">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%;" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}TabContainer" data-dojo-props="region: 'center', tabPosition: 'top'" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.TabContainer">
+            <div id="${id}_ValidateActivePackageMap" style="width: 100%; height: 100%" data-dojo-props="title:'${i18n.ValidateActivePackageMap}'" data-dojo-type="dijit.layout.BorderContainer">
+                <div id="${id}SubmitPane" class="topPanel" data-dojo-props="region: 'top'" data-dojo-type="dijit.layout.ContentPane">
+                    <div style="display: inline-block; vertical-align: middle">
+                        <label for="${id}TargetSelect">${i18n.Target}</label>
+                        <div id="${id}TargetSelect" name="TargetSelect" style="width: 8em;" data-dojo-attach-event="onChange:_onChangeTarget" data-dojo-type="dijit.form.Select">
+                        </div>
+                        <label for="${id}ProcessSelect">${i18n.Process}</label>
+                        <div id="${id}ProcessSelect" name="ProcessSelect" style="width: 8em;" data-dojo-attach-event="onChange:_onChangeProcess" data-dojo-type="dijit.form.Select">
+                        </div>
+                        <button id="${id}ValidateBtn" data-dojo-attach-event="onClick:_onValidate" data-dojo-type="dijit.form.Button">${i18n.Validate}</button>
                     </div>
-                </td>
-            </tr>
-            <tr>
-                <td width="140px"><b>${i18n.Process}:</b></td>
-                <td>
-                    <div id="${id}ProcessSelect" name="ProcessSelect" style="width: 8em;" data-dojo-type="dijit.form.Select">
-                    </div>
-                </td>
-            </tr>
-            <tr>
-                <td><b>${i18n.Validate}:</b></td>
-                <td>
-                    <input name="ValidateType" id="ActivePM"  value="ActivePM" type="radio" data-dojo-type="dijit.form.RadioButton" checked/>${i18n.ActivePackageMap}
-                    <input name="ValidateType" id="PMContent"  value="PMContent"type="radio" data-dojo-type="dijit.form.RadioButton"/>${i18n.PackageContent}
-                </td>
-            </tr>
-            <tr>
-                <td valign='top'><b>${i18n.PackageContent}:</b></td>
-                <td>
-                    <textarea id="${id}Package" name="${id}Package" data-dojo-type="dijit.form.SimpleTextarea" rows="15" cols="80" style="width:100%;height:80%;"></textarea>
-                </td>
-            </tr>
-            <tr>
-                <td/>
-                <td>
-                    <div id="${id}Validate" data-dojo-attach-event="onClick:_onValidate" data-dojo-type="dijit.form.Button">${i18n.Validate}</div>
-                </td>
-            </tr>
-            <tr>
-                <td/>
-                <td>
-                    <textarea id="${id}ValidateResult" data-dojo-type="dijit.form.SimpleTextarea" rows="15" cols="100"></textarea>
-                </td>
-            </tr>
-        </table>
+                </div>
+                <div id="${id}Source" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="ECLSourceWidget">
+                </div>
+                <div id="${id}Result" title="${i18n.Results}" style="width: 480px;" data-dojo-props="minSize:120, region: 'right', splitter:true" data-dojo-type="ECLSourceWidget">
+                </div>
+            </div>
+            <div id="${id}_ValidatePackageMapContent" title="${i18n.ValidatePackageContent}" data-dojo-props="delayWidget: 'PackageMapValidateContentWidget'" data-dojo-type="DelayLoadWidget">
+            </div>
+        </div>
     </div>
 </div>

+ 4 - 0
initfiles/componentfiles/configxml/buildsetCC.xml.in

@@ -165,6 +165,10 @@
                           path="FileIOAccess"
                           resource="FileIOAccess"
                           service="ws_fileio"/>
+     <AuthenticateFeature description="Access to permissions for file scopes"
+                          path="FileScopeAccess"
+                          resource="FileScopeAccess"
+                          service="ws_access"/>
      <AuthenticateFeature description="Access to WS ECL service"
                           path="WsEclAccess"
                           resource="WsEclAccess"

+ 13 - 0
initfiles/etc/DIR_NAME/environment.xml.in

@@ -167,6 +167,10 @@
                           path="FileIOAccess"
                           resource="FileIOAccess"
                           service="ws_fileio"/>
+     <AuthenticateFeature description="Access to permissions for file scopes"
+                          path="FileScopeAccess"
+                          resource="FileScopeAccess"
+                          service="ws_access"/>
      <AuthenticateFeature description="Access to WS ECL service"
                           path="WsEclAccess"
                           resource="WsEclAccess"
@@ -535,6 +539,11 @@
                          resource="FileIOAccess"
                          service="ws_fileio"/>
     <AuthenticateFeature authenticate="Yes"
+                         description="Access to permissions for file scopes"
+                         path="FileScopeAccess"
+                         resource="FileScopeAccess"
+                         service="ws_access"/>
+    <AuthenticateFeature authenticate="Yes"
                          description="Access to WS ECL service"
                          path="WsEclAccess"
                          resource="WsEclAccess"
@@ -695,6 +704,10 @@
                          path="FileIOAccess"
                          resource="FileIOAccess"
                          service="ws_fileio"/>
+    <AuthenticateFeature description="Access to permissions for file scopes"
+                         path="FileScopeAccess"
+                         resource="FileScopeAccess"
+                         service="ws_access"/>
     <AuthenticateFeature description="Access to WS ECL service"
                          path="WsEclAccess"
                          resource="WsEclAccess"

+ 1 - 0
roxie/ccd/ccdcontext.cpp

@@ -2637,6 +2637,7 @@ protected:
         wu->addTimeStamp("Roxie", GetCachedHostName(), "Started");
         if (!context->getPropBool("@outputToSocket", false))
             client = NULL;
+        updateSuppliedXmlParams(wu);
         SCMStringBuffer wuParams;
         if (workUnit->getXmlParams(wuParams).length())
         {

+ 67 - 30
roxie/ccd/ccdserver.cpp

@@ -891,6 +891,7 @@ protected:
     IRoxieSlaveContext *ctx;
     const IRoxieServerActivityFactory *factory;
     IRoxieServerActivityCopyArray dependencies;
+    IntArray dependencyIndexes;
     IntArray dependencyControlIds;
     IArrayOf<IActivityGraph> childGraphs;
     CachedOutputMetaData meta;
@@ -1255,6 +1256,12 @@ public:
                     }
                 }
 #endif
+                // NOTE - this is needed to ensure that dependencies which were not used are properly stopped
+                ForEachItemIn(idx, dependencies)
+                {
+                    if (dependencyControlIds.item(idx) == 0)
+                        dependencies.item(idx).stopSink(dependencyIndexes.item(idx));
+                }
                 if (input)
                     input->stop(aborting);
             }
@@ -1311,6 +1318,7 @@ public:
     virtual void addDependency(IRoxieServerActivity &source, unsigned sourceIdx, int controlId) 
     {
         dependencies.append(source);
+        dependencyIndexes.append(sourceIdx);
         dependencyControlIds.append(controlId);
     } 
 
@@ -1330,6 +1338,11 @@ public:
         throw MakeStringException(ROXIE_SINK, "Internal error: executeChild() requires a suitable sink");
     }
 
+    virtual void stopSink(unsigned idx)
+    {
+        throw MakeStringException(ROXIE_SINK, "Internal error: stopSink() requires a suitable sink");
+    }
+
     virtual __int64 evaluate() 
     {
         throw MakeStringException(ROXIE_SINK, "Internal error: evaluate() requires a function");
@@ -2128,19 +2141,31 @@ public:
 class CRoxieServerInternalSinkActivity : public CRoxieServerActivity
 {
 protected:
+    unsigned numOutputs;
     bool executed;
+    bool *stopped;
     CriticalSection ecrit;
     Owned<IException> exception;
 
 public:
-    CRoxieServerInternalSinkActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerActivity(_factory, _probeManager)
+    CRoxieServerInternalSinkActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _numOutputs)
+        : CRoxieServerActivity(_factory, _probeManager), numOutputs(_numOutputs)
     {
         executed = false;
+        stopped = new bool[numOutputs];
+        for (unsigned s = 0; s < numOutputs; s++)
+            stopped[s] = false;
+    }
+
+    ~CRoxieServerInternalSinkActivity()
+    {
+        delete [] stopped;
     }
 
     virtual void reset()
     {
+        for (unsigned s = 0; s < numOutputs; s++)
+            stopped[s] = false;
         executed = false;
         exception.clear();
         CRoxieServerActivity::reset();
@@ -2151,6 +2176,18 @@ public:
         return NULL;
     }
 
+    virtual void stopSink(unsigned outputIdx)
+    {
+        if (!stopped[outputIdx])
+        {
+            stopped[outputIdx] = true;
+            for (unsigned s = 0; s < numOutputs; s++)
+                if (!stopped[s])
+                    return;
+            stop(false); // all outputs stopped - stop parent.
+        }
+    }
+
     virtual const void *nextInGroup()
     {
         throwUnexpected(); // I am nobody's input
@@ -4460,7 +4497,7 @@ class CRoxieServerApplyActivity : public CRoxieServerInternalSinkActivity
 
 public:
     CRoxieServerApplyActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorApplyArg &) basehelper)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, 0), helper((IHThorApplyArg &) basehelper)
     {
     }
 
@@ -5033,7 +5070,7 @@ class CRoxieServerDistributionActivity : public CRoxieServerInternalSinkActivity
 
 public:
     CRoxieServerDistributionActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorDistributionArg &)basehelper)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, 0), helper((IHThorDistributionArg &)basehelper)
     {
     }
 
@@ -5908,8 +5945,8 @@ class CRoxieServerLocalResultWriteActivity : public CRoxieServerInternalSinkActi
     unsigned graphId;
 
 public:
-    CRoxieServerLocalResultWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _graphId)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorLocalResultWriteArg &)basehelper), graphId(_graphId)
+    CRoxieServerLocalResultWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _graphId, unsigned _numOutputs)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _numOutputs), helper((IHThorLocalResultWriteArg &)basehelper), graphId(_graphId)
     {
         graph = NULL;
     }
@@ -5954,7 +5991,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerLocalResultWriteActivity(this, _probeManager, graphId);
+        return new CRoxieServerLocalResultWriteActivity(this, _probeManager, graphId, usageCount);
     }
 
 };
@@ -5973,8 +6010,8 @@ class CRoxieServerDictionaryResultWriteActivity : public CRoxieServerInternalSin
     unsigned graphId;
 
 public:
-    CRoxieServerDictionaryResultWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _graphId)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorDictionaryResultWriteArg &)basehelper), graphId(_graphId)
+    CRoxieServerDictionaryResultWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _usageCount, unsigned _graphId)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _usageCount), helper((IHThorDictionaryResultWriteArg &)basehelper), graphId(_graphId)
     {
         graph = NULL;
     }
@@ -6031,7 +6068,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerDictionaryResultWriteActivity(this, _probeManager, graphId);
+        return new CRoxieServerDictionaryResultWriteActivity(this, _probeManager, usageCount, graphId);
     }
 };
 
@@ -6208,8 +6245,8 @@ class CRoxieServerGraphLoopResultWriteActivity : public CRoxieServerInternalSink
     unsigned graphId;
 
 public:
-    CRoxieServerGraphLoopResultWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _graphId)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorGraphLoopResultWriteArg &)basehelper), graphId(_graphId)
+    CRoxieServerGraphLoopResultWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _graphId, unsigned _numOutputs)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _numOutputs), helper((IHThorGraphLoopResultWriteArg &)basehelper), graphId(_graphId)
     {
         graph = NULL;
     }
@@ -6294,7 +6331,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerGraphLoopResultWriteActivity(this, _probeManager, graphId);
+        return new CRoxieServerGraphLoopResultWriteActivity(this, _probeManager, graphId, usageCount);
     }
 
 };
@@ -9114,8 +9151,8 @@ class CRoxieServerPipeWriteActivity : public CRoxieServerInternalSinkActivity
     bool recreate;
     bool inputExhausted;
 public:
-    CRoxieServerPipeWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorPipeWriteArg &)basehelper)
+    CRoxieServerPipeWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _numOutputs)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _numOutputs), helper((IHThorPipeWriteArg &)basehelper)
     {
         recreate = helper.recreateEachRow();
         firstRead = false;
@@ -9235,7 +9272,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerPipeWriteActivity(this, _probeManager);
+        return new CRoxieServerPipeWriteActivity(this, _probeManager, usageCount);
     }
 };
 
@@ -9758,8 +9795,8 @@ class CRoxieServerActionActivity : public CRoxieServerInternalSinkActivity
     IHThorActionArg &helper;
 public:
 
-    CRoxieServerActionActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorActionArg &)basehelper)
+    CRoxieServerActionActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _numOutputs)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _numOutputs), helper((IHThorActionArg &)basehelper)
     {
     }
 
@@ -9779,7 +9816,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerActionActivity(this, _probeManager);
+        return new CRoxieServerActionActivity(this, _probeManager, usageCount);
     }
 };
 
@@ -10806,7 +10843,7 @@ protected:
 
 public:
     CRoxieServerDiskWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorDiskWriteArg &)basehelper)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, 0), helper((IHThorDiskWriteArg &)basehelper)
     {
         extend = ((helper.getFlags() & TDWextend) != 0);
         overwrite = ((helper.getFlags() & TDWoverwrite) != 0);
@@ -11290,7 +11327,7 @@ class CRoxieServerIndexWriteActivity : public CRoxieServerInternalSinkActivity,
 
 public:
     CRoxieServerIndexWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper(static_cast<IHThorIndexWriteArg &>(basehelper))
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, 0), helper(static_cast<IHThorIndexWriteArg &>(basehelper))
     {
         overwrite = ((helper.getFlags() & TIWoverwrite) != 0);
         reccount = 0;
@@ -19709,8 +19746,8 @@ class CRoxieServerWorkUnitWriteActivity : public CRoxieServerInternalSinkActivit
     IRoxieServerContext *serverContext;
 
 public:
-    CRoxieServerWorkUnitWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, bool _isReread)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorWorkUnitWriteArg &)basehelper), isReread(_isReread)
+    CRoxieServerWorkUnitWriteActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, bool _isReread, unsigned _numOutputs)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _numOutputs), helper((IHThorWorkUnitWriteArg &)basehelper), isReread(_isReread)
     {
         grouped = (helper.getFlags() & POFgrouped) != 0;
         serverContext = NULL;
@@ -19881,7 +19918,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerWorkUnitWriteActivity(this, _probeManager, isReread);
+        return new CRoxieServerWorkUnitWriteActivity(this, _probeManager, isReread, usageCount);
     }
 
 };
@@ -19900,8 +19937,8 @@ class CRoxieServerWorkUnitWriteDictActivity : public CRoxieServerInternalSinkAct
     IRoxieServerContext *serverContext;
 
 public:
-    CRoxieServerWorkUnitWriteDictActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorDictionaryWorkUnitWriteArg &)basehelper)
+    CRoxieServerWorkUnitWriteDictActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _usageCount)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _usageCount), helper((IHThorDictionaryWorkUnitWriteArg &)basehelper)
     {
         serverContext = NULL;
     }
@@ -19955,7 +19992,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerWorkUnitWriteDictActivity(this, _probeManager);
+        return new CRoxieServerWorkUnitWriteDictActivity(this, _probeManager, usageCount);
     }
 
 };
@@ -19973,8 +20010,8 @@ class CRoxieServerRemoteResultActivity : public CRoxieServerInternalSinkActivity
     IHThorRemoteResultArg &helper;
 
 public:
-    CRoxieServerRemoteResultActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager)
-        : CRoxieServerInternalSinkActivity(_factory, _probeManager), helper((IHThorRemoteResultArg &)basehelper)
+    CRoxieServerRemoteResultActivity(const IRoxieServerActivityFactory *_factory, IProbeManager *_probeManager, unsigned _numOutputs)
+        : CRoxieServerInternalSinkActivity(_factory, _probeManager, _numOutputs), helper((IHThorRemoteResultArg &)basehelper)
     {
     }
 
@@ -19998,7 +20035,7 @@ public:
 
     virtual IRoxieServerActivity *createActivity(IProbeManager *_probeManager) const
     {
-        return new CRoxieServerRemoteResultActivity(this, _probeManager);
+        return new CRoxieServerRemoteResultActivity(this, _probeManager, usageCount);
     }
 
 };

+ 1 - 0
roxie/ccd/ccdserver.hpp

@@ -159,6 +159,7 @@ interface IRoxieServerActivity : extends IActivityBase
     virtual void executeChild(size32_t & retSize, void * & ret, unsigned parentExtractSize, const byte * parentExtract) = 0;
     virtual void serializeCreateStartContext(MemoryBuffer &out) = 0;
     virtual void serializeExtra(MemoryBuffer &out) = 0;
+    virtual void stopSink(unsigned idx) = 0;
 //Functions to support result streaming between parallel loop/graphloop/library implementations
     virtual IRoxieInput * querySelectOutput(unsigned id) = 0;
     virtual bool querySetStreamInput(unsigned id, IRoxieInput * _input) = 0;

+ 26 - 4
roxie/ccd/ccdstate.cpp

@@ -609,6 +609,11 @@ public:
         // we could set the cache field to null here for any objects still in cache but there would be a race condition
     }
 
+    virtual void setHash(hash64_t newhash)
+    {
+        hash = newhash;
+    }
+
     virtual IPropertyTreeIterator *getInMemoryIndexInfo(const IPropertyTree &graphNode) const 
     {
         StringBuffer xpath;
@@ -775,6 +780,7 @@ extern const IRoxiePackage &queryRootRoxiePackage()
     {
         // Set up the root package. This contains global settings from topology file
         rootPackage = new CRoxiePackage(topology); // attributes become control: environment settings. Rest of topology ignored.
+        rootPackage->setHash(0);  // we don't include the topology in the package hashes...
         rootPackage->resolveBases(NULL);
     }
     return *rootPackage;
@@ -2369,8 +2375,13 @@ private:
                     reply.appendf("<Dali connected='1'/>");
                 else
                     reply.appendf("<Dali connected='0'/>");
-                ReadLockBlock readBlock(packageCrit);
-                reply.appendf("<State hash='%"I64F"u'/>", (unsigned __int64) allQueryPackages->queryHash());
+                unsigned __int64 thash = getTopologyHash();
+                unsigned __int64 shash;
+                {
+                    ReadLockBlock readBlock(packageCrit);
+                    shash = allQueryPackages->queryHash();
+                }
+                reply.appendf("<State hash='%"I64F"u' topologyHash='%"I64F"u'/>", shash, thash);
             }
             else if (stricmp(queryName, "control:resetindexmetrics")==0)
             {
@@ -2455,8 +2466,13 @@ private:
                     reply.appendf("<Dali connected='1'/>");
                 else
                     reply.appendf("<Dali connected='0'/>");
-                ReadLockBlock readBlock(packageCrit);
-                reply.appendf("<State hash='%"I64F"u'/>", (unsigned __int64) allQueryPackages->queryHash());
+                unsigned __int64 thash = getTopologyHash();
+                unsigned __int64 shash;
+                {
+                    ReadLockBlock readBlock(packageCrit);
+                    shash = allQueryPackages->queryHash();
+                }
+                reply.appendf("<State hash='%"I64F"u' topologyHash='%"I64F"u'/>", shash, thash);
             }
             else if (stricmp(queryName, "control:steppingEnabled")==0)
             {
@@ -2672,6 +2688,12 @@ private:
         throw MakeStringException(ROXIE_INVALID_INPUT, "Badly formated control query");
     }
 
+    hash64_t getTopologyHash()
+    {
+        StringBuffer xml;
+        toXML(topology, xml, 0, XML_SortTags);
+        return rtlHash64Data(xml.length(), xml.str(), 707018);
+    }
 };
 
 extern IRoxieQueryPackageManagerSet *createRoxiePackageSetManager(const IQueryDll *standAloneDll)

+ 2 - 0
roxie/ccd/ccdstate.hpp

@@ -60,6 +60,8 @@ interface IRoxiePackage : public IHpccPackage
     virtual IRoxieWriteHandler *createFileName(const char *fileName, bool overwrite, bool extend, const StringArray &clusters, IConstWorkUnit *wu) const = 0;
     // Lookup information in package about what in-memory indexes should be built for file
     virtual IPropertyTreeIterator *getInMemoryIndexInfo(const IPropertyTree &graphNode) const = 0;
+    // Set the hash to be used for this package
+    virtual void setHash(hash64_t newhash)  = 0;
 };
 
 interface IResolvedFileCache

+ 26 - 8
system/jlib/jstring.cpp

@@ -1963,9 +1963,9 @@ jlib_decl StringBuffer &appendJSONDataValue(StringBuffer& s, const char *name, u
     return s.append('"');
 }
 
-inline StringBuffer &encodeJSON(StringBuffer &s, const char ch)
+inline StringBuffer &encodeJSONChar(StringBuffer &s, const char *&ch)
 {
-    switch (ch)
+    switch (*ch)
     {
         case '\b':
             s.append("\\b");
@@ -1985,10 +1985,29 @@ inline StringBuffer &encodeJSON(StringBuffer &s, const char ch)
         case '\"':
         case '\\':
         case '/':
-            s.append('\\'); //fall through
+            s.append('\\');
+            s.append(*ch);
+            break;
         default:
-            s.append(ch);
+            if (*ch >= ' ' && ((byte)*ch) < 128)
+                s.append(*ch);
+            else if (*ch < ' ' && *ch > 0)
+                s.append("\\u00").appendhex(*ch, true);
+            else //json is always supposed to be utf8 (or other unicode formats)
+            {
+                unsigned chlen = utf8CharLen((const unsigned char *)ch);
+                if (chlen==0)
+                    s.append("\\u00").appendhex(*ch, true);
+                else
+                {
+                    s.append(*ch);
+                    while(--chlen)
+                        s.append(*(++ch));
+                }
+            }
+            break;
     }
+    ch++;
     return s;
 }
 
@@ -1996,9 +2015,8 @@ StringBuffer &encodeJSON(StringBuffer &s, unsigned len, const char *value)
 {
     if (!value)
         return s;
-    unsigned pos=0;
-    while(pos<len && value[pos]!=0)
-        encodeJSON(s, value[pos++]);
+    while (len-- && *value)
+        encodeJSONChar(s, value);
     return s;
 }
 
@@ -2007,7 +2025,7 @@ StringBuffer &encodeJSON(StringBuffer &s, const char *value)
     if (!value)
         return s;
     while (*value)
-        encodeJSON(s, *value++);
+        encodeJSONChar(s, value);
     return s;
 }
 

+ 1 - 1
testing/regress/ecl-test

@@ -32,7 +32,7 @@ from hpcc.util.ecl.file import ECLFile
 from hpcc.util.util import checkPqParam,  getVersionNumbers
 from hpcc.common.error import Error
 
-prog_version = "0.0.16"
+prog_version = "0.0.19"
 
 # For coverage
 if ('coverage' in os.environ) and (os.environ['coverage'] == '1'):

+ 12 - 0
testing/regress/ecl/key/resetsplitter.xml

@@ -0,0 +1,12 @@
+<Dataset name='Result 1'>
+ <Row><id>64</id></Row>
+ <Row><id>65</id></Row>
+ <Row><id>66</id></Row>
+ <Row><id>67</id></Row>
+ <Row><id>68</id></Row>
+ <Row><id>69</id></Row>
+ <Row><id>70</id></Row>
+ <Row><id>71</id></Row>
+ <Row><id>72</id></Row>
+ <Row><id>73</id></Row>
+</Dataset>

+ 36 - 0
testing/regress/ecl/resetsplitter.ecl

@@ -0,0 +1,36 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+idRecord := { unsigned id; };
+
+myDataset := DATASET(100, TRANSFORM(idRecord, SELF.id := COUNTER), DISTRIBUTED);
+
+filtered := NOFOLD(myDataset)(id % 20 != 0);
+
+filter1 := NOFOLD(filtered)(id % 3 != 0);
+
+filter2 := NOFOLD(filtered)(id % 3 != 1);
+
+p1 := PROJECT(NOFOLD(filtered), TRANSFORM(idRecord, SELF.id := LEFT.id + COUNT(filter1)));
+
+p2 := PROJECT(NOFOLD(filtered), TRANSFORM(idRecord, SELF.id := LEFT.id + COUNT(filter2)));
+
+boolean test := false : stored('test');
+
+r := IF(test, NOFOLD(p1), NOFOLD(p2));
+
+output(CHOOSEN(NOFOLD(r), 10));

+ 1 - 1
testing/regress/hpcc/regression/suite.py

@@ -80,7 +80,7 @@ class Suite:
                     self.exclude.append(format(file, "25")+" skipped (reason:"+skipResult['reason']+")");
 
                 if eclfile.testPublish():
-                    self.publish.append(file)
+                    self.publish.append(eclfile.getBaseEcl())
 
     def testPublish(self, ecl):
         if ecl in self.publish:

+ 1 - 1
testing/regress/hpcc/util/ecl/command.py

@@ -49,7 +49,7 @@ class ECLcmd(Shell):
             args.append("--password=" + password)
 
         if cmd == 'publish':
-            args.append(eclfile.getEcl())
+            args.append(eclfile.getArchive())
         else:
             args.append('--noroot')
             server = kwargs.pop('server', False)