ソースを参照

HPCC-10712 Add "active" manipulation

Fixes HPCC-10712

Signed-off-by: Gordon Smith <gordon.smith@lexisnexis.com>
Gordon Smith 11 年 前
コミット
e19b58e4c5

BIN
esp/files/img/priority.png


BIN
esp/files/img/priority_high.png


BIN
esp/files/img/priority_low.png


BIN
esp/files/img/server_notfound.png


BIN
esp/files/img/server_paused.png


+ 308 - 33
esp/files/scripts/ActivityWidget.js

@@ -22,7 +22,9 @@ define([
     "dojo/_base/array",
     "dojo/on",
 
+    "dijit/registry",
     "dijit/form/Button",
+    "dijit/ToolbarSeparator",
 
     "dgrid/OnDemandGrid",
     "dgrid/Keyboard",
@@ -39,7 +41,7 @@ define([
     "hpcc/ESPUtil"
 
 ], function (declare, lang, i18n, nlsCommon, nlsSpecific, arrayUtil, on,
-                Button,
+                registry, Button, ToolbarSeparator,
                 OnDemandGrid, Keyboard, Selection, selector, tree, ColumnResizer, DijitRegistry,
                 GridDetailsWidget, ESPActivity, WUDetailsWidget, DFUWUDetailsWidget, ESPUtil) {
     return declare("ActivityWidget", [GridDetailsWidget], {
@@ -48,6 +50,110 @@ define([
         gridTitle: nlsSpecific.title,
         idProperty: "Wuid",
 
+        _onPause: function (event, params) {
+            arrayUtil.forEach(this.grid.getSelected(), function (item, idx) {
+                if (this.activity.isInstanceOfQueue(item)) {
+                    var context = this;
+                    item.pause().then(function(response) {
+                        context._refreshActionState();
+                    });
+                }
+            }, this);
+        },
+
+        _onResume: function (event, params) {
+            arrayUtil.forEach(this.grid.getSelected(), function (item, idx) {
+                if (this.activity.isInstanceOfQueue(item)) {
+                    var context = this;
+                    item.resume().then(function (response) {
+                        context._refreshActionState();
+                    });
+                }
+            }, this);
+        },
+
+        _onClear: function (event, params) {
+            arrayUtil.forEach(this.grid.getSelected(), function (item, idx) {
+                if (this.activity.isInstanceOfQueue(item)) {
+                    item.clear();
+                }
+            }, this);
+            this._onRefresh();
+        },
+
+        _onWUAbort: function (event, params) {
+            arrayUtil.forEach(this.grid.getSelected(), function (item, idx) {
+                if (this.activity.isInstanceOfWorkunit(item)) {
+                    item.abort();
+                }
+            }, this);
+            this._onRefresh();
+        },
+
+        _onWUPriority: function (event, priority) {
+            arrayUtil.forEach(this.grid.getSelected(), function (item, idx) {
+                if (this.activity.isInstanceOfWorkunit(item)) {
+                    var queue = item.get("ESPQueue");
+                    if (queue) {
+                        queue.setPriority(item.Wuid, priority);
+                    }
+                }
+            }, this);
+            this._onRefresh();
+        },
+
+        _onWUTop: function (event, params) {
+            var selected = this.grid.getSelected();
+            for (var i = selected.length - 1; i >= 0; --i) {
+                var item = selected[i];
+                if (this.activity.isInstanceOfWorkunit(item)) {
+                    var queue = item.get("ESPQueue");
+                    if (queue) {
+                        queue.moveTop(item.Wuid);
+                    }
+                }
+            }
+            this._onRefresh();
+        },
+
+        _onWUUp: function (event, params) {
+            arrayUtil.forEach(this.grid.getSelected(), function (item, idx) {
+                if (this.activity.isInstanceOfWorkunit(item)) {
+                    var queue = item.get("ESPQueue");
+                    if (queue) {
+                        queue.moveUp(item.Wuid);
+                    }
+                }
+            }, this);
+            this._onRefresh();
+        },
+
+        _onWUDown: function (event, params) {
+            var selected = this.grid.getSelected();
+            for (var i = selected.length - 1; i >= 0; --i) {
+                var item = selected[i];
+                if (this.activity.isInstanceOfWorkunit(item)) {
+                    var queue = item.get("ESPQueue");
+                    if (queue) {
+                        queue.moveDown(item.Wuid);
+                    }
+                }
+            }
+            this._onRefresh();
+        },
+
+        _onWUBottom: function (event, params) {
+            arrayUtil.forEach(this.grid.getSelected(), function (item, idx) {
+                if (this.activity.isInstanceOfWorkunit(item)) {
+                    var queue = item.get("ESPQueue");
+                    if (queue) {
+                        queue.moveBottom(item.Wuid);
+                    }
+                }
+            }, this);
+            this._onRefresh();
+        },
+
         doSearch: function (searchText) {
             this.searchText = searchText;
             this.selectChild(this.gridTab);
@@ -59,8 +165,9 @@ define([
                 return;
 
             var context = this;
-            this.activity.monitor(function (activity) {
+            this.activity.watch("changedCount", function (item, oldValue, newValue) {
                 context.grid.set("query", {});
+                context._refreshActionState();
             });
 
             this._refreshActionState();
@@ -68,6 +175,90 @@ define([
 
         createGrid: function (domID) {
             var context = this;
+
+            this.openButton = registry.byId(this.id + "Open");
+            this.clusterPauseButton = new Button({
+                id: this.id + "PauseButton",
+                label: "Pause",
+                onClick: function (event) {
+                    context._onPause(event);
+                }
+            }).placeAt(this.openButton.domNode, "before");
+            this.clusterResumeButton = new Button({
+                id: this.id + "ResumeButton",
+                label: "Resume",
+                onClick: function (event) {
+                    context._onResume(event);
+                }
+            }).placeAt(this.openButton.domNode, "before");
+            this.clusterClearButton = new Button({
+                id: this.id + "ClearButton",
+                label: "Clear",
+                onClick: function (event) {
+                    context._onClear(event);
+                }
+            }).placeAt(this.openButton.domNode, "before");
+            var tmpSplitter = new ToolbarSeparator().placeAt(this.openButton.domNode, "before");
+
+            this.wuMoveBottomButton = new Button({
+                id: this.id + "MoveBottomButton",
+                label: "Bottom",
+                onClick: function (event) {
+                    context._onWUBottom(event);
+                }
+            }).placeAt(this.openButton.domNode, "after");
+            this.wuMoveDownButton = new Button({
+                id: this.id + "MoveDownButton",
+                label: "Down",
+                onClick: function (event) {
+                    context._onWUDown(event);
+                }
+            }).placeAt(this.openButton.domNode, "after");
+            this.wuMoveUpButton = new Button({
+                id: this.id + "MoveUpButton",
+                label: "Up",
+                onClick: function (event) {
+                    context._onWUUp(event);
+                }
+            }).placeAt(this.openButton.domNode, "after");
+            this.wuMoveTopButton = new Button({
+                id: this.id + "MoveTopButton",
+                label: "Top",
+                onClick: function (event) {
+                    context._onWUTop(event);
+                }
+            }).placeAt(this.openButton.domNode, "after");
+            tmpSplitter = new ToolbarSeparator().placeAt(this.openButton.domNode, "after");
+            this.wuLowPriorityButton = new Button({
+                id: this.id + "LowPriorityButton",
+                label: "Low",
+                onClick: function (event) {
+                    context._onWUPriority(event, "low");
+                }
+            }).placeAt(this.openButton.domNode, "after");
+            this.wuNormalPriorityButton = new Button({
+                id: this.id + "NormalPriorityButton",
+                label: "Normal",
+                onClick: function (event) {
+                    context._onWUPriority(event, "normal");
+                }
+            }).placeAt(this.openButton.domNode, "after");
+            this.wuHighPriorityButton = new Button({
+                id: this.id + "HighPriorityButton",
+                label: "High",
+                onClick: function (event) {
+                    context._onWUPriority(event, "high");
+                }
+            }).placeAt(this.openButton.domNode, "after");
+            tmpSplitter = new ToolbarSeparator().placeAt(this.openButton.domNode, "after");
+            this.wuAbortButton = new Button({
+                id: this.id + "AbortButton",
+                label: "Abort",
+                onClick: function (event) {
+                    context._onWUAbort(event);
+                }
+            }).placeAt(this.openButton.domNode, "after");
+
             this.noDataMessage = this.i18n.loadingMessage;
             this.activity = ESPActivity.Get();
             var retVal = new declare([OnDemandGrid, Keyboard, Selection, ColumnResizer, DijitRegistry, ESPUtil.GridHelper])({
@@ -78,30 +269,41 @@ define([
                     col1: selector({
                         width: 27,
                         selectorType: 'checkbox',
-                        disabled: function (item) {
-                            if (item.__hpcc_type) {
-                                switch (item.__hpcc_type) {
-                                    case "TargetCluster":
-                                        return true;
-                                }
-                            }
-                            return false;
-                        },
                         sortable: false
                     }),
+                    Priority: {
+                        renderHeaderCell: function (node) {
+                            node.innerHTML = "<img src='../files/img/priority.png'>";
+                        },
+                        width: 25,
+                        sortable: false,
+                        formatter: function (Priority) {
+                            switch (Priority) {
+                                case "high":
+                                    return "<img src='../files/img/priority_high.png'>";
+                                case "low":
+                                    return "<img src='../files/img/priority_low.png'>";
+                            }
+                            return "";
+                        }
+                    },
                     DisplayName: tree({
                         label: this.i18n.Target,
-                        width: 225,
+                        width: 270,
                         sortable: true,
-                        shouldExpand: function(row, level, previouslyExpanded) {
+                        shouldExpand: function (row, level, previouslyExpanded) {
                             return true;
                         },
                         formatter: function (_name, row) {
                             var img = "../files/";
                             var name = "";
-                            if (row.__hpcc_type === "TargetCluster") {
-                                img += "img/server.png";
-                                name = row.__hpcc_id;
+                            if (context.activity.isInstanceOfQueue(row)) {
+                                if (row.isPaused()) {
+                                    img += "img/server_paused.png";
+                                } else {
+                                    img += "img/server.png";
+                                }
+                                name = row.getDisplayName();
                             } else {
                                 img += row.getStateImage();
                                 name = "<a href='#' class='" + context.id + "WuidClick'>" + row.Wuid + "</a>";
@@ -113,7 +315,10 @@ define([
                         label: this.i18n.State,
                         sortable: true,
                         formatter: function (state, row) {
-                            if (row.__hpcc_type === "TargetCluster") {
+                            if (context.activity.isInstanceOfQueue(row)) {
+                                if (row.isPaused()) {
+                                    return row.StatusDetails;
+                                }
                                 return "";
                             }
                             if (row.Duration) {
@@ -126,6 +331,16 @@ define([
                     },
                     Owner: { label: this.i18n.Owner, width: 90, sortable: true },
                     Jobname: { label: this.i18n.JobName, sortable: true }
+                },
+                getSelected: function () {
+                    var retVal = [];
+                    for (var id in this.selection) {
+                        var item = context.activity.resolve(id)
+                        if (item) {
+                            retVal.push(item);
+                        }
+                    }
+                    return retVal;
                 }
             }, domID);
 
@@ -139,28 +354,32 @@ define([
         },
 
         createDetail: function (id, row, params) {
-            if (row.Server === "DFUserver") {
-                return new DFUWUDetailsWidget.fixCircularDependency({
+            if (this.activity.isInstanceOfQueue(row)) {
+            } else if (this.activity.isInstanceOfWorkunit(row)) {
+                if (row.Server === "DFUserver") {
+                    return new DFUWUDetailsWidget.fixCircularDependency({
+                        id: id,
+                        title: row.ID,
+                        closable: true,
+                        hpcc: {
+                            params: {
+                                Wuid: row.ID
+                            }
+                        }
+                    });
+                }
+                return new WUDetailsWidget({
                     id: id,
-                    title: row.ID,
+                    title: row.Wuid,
                     closable: true,
                     hpcc: {
                         params: {
-                            Wuid: row.ID
+                            Wuid: row.Wuid
                         }
                     }
                 });
-            } 
-            return new WUDetailsWidget({
-                id: id,
-                title: row.Wuid,
-                closable: true,
-                hpcc: {
-                    params: {
-                        Wuid: row.Wuid
-                    }
-                }
-            });
+            }
+            return null;
         },
 
         loadRunning: function (response) {
@@ -189,7 +408,63 @@ define([
         },
 
         refreshActionState: function (selection) {
-            this.inherited(arguments);
+            var clusterSelected = false;
+            var wuSelected = false;
+            var clusterPausedSelected = false;
+            var clusterNotPausedSelected = false;
+            var clusterHasItems = false;
+            var wuCanHigh = false;
+            var wuCanNormal = false;
+            var wuCanLow = false;
+            var wuCanUp = false;
+            var wuCanDown = false;
+            var context = this;
+            arrayUtil.forEach(selection, function (item, idx) {
+                if (context.activity.isInstanceOfQueue(item)) {
+                    clusterSelected = true;
+                    if (item.isPaused()) {
+                        clusterPausedSelected = true;
+                    } else {
+                        clusterNotPausedSelected = true;
+                    }
+                    if (item.getChildCount()) {
+                        clusterHasItems = true;
+                    }
+                } else if (context.activity.isInstanceOfWorkunit(item)) {
+                    wuSelected = true;
+                    var queue = item.get("ESPQueue");
+                    if (queue) {
+                        if (queue.canChildMoveUp(item.__hpcc_id)) {
+                            wuCanUp = true;
+                        }
+                        if (queue.canChildMoveDown(item.__hpcc_id)) {
+                            wuCanDown = true;
+                        }
+                    }
+                    if (item.get("Priority") !== "high") {
+                        wuCanHigh = true;
+                    }
+                    if (item.get("Priority") !== "normal") {
+                        wuCanNormal = true;
+                    }
+                    if (item.get("Priority") !== "low") {
+                        wuCanLow = true;
+                    }
+                }
+            });
+
+            this.clusterPauseButton.set("disabled", !clusterNotPausedSelected);
+            this.clusterResumeButton.set("disabled", !clusterPausedSelected);
+            this.clusterClearButton.set("disabled", !clusterHasItems);
+            this.openButton.set("disabled", !wuSelected);
+            this.wuAbortButton.set("disabled", !wuSelected);
+            this.wuHighPriorityButton.set("disabled", !wuCanHigh);
+            this.wuNormalPriorityButton.set("disabled", !wuCanNormal);
+            this.wuLowPriorityButton.set("disabled", !wuCanLow);
+            this.wuMoveTopButton.set("disabled", !wuCanUp);
+            this.wuMoveUpButton.set("disabled", !wuCanUp);
+            this.wuMoveDownButton.set("disabled", !wuCanDown);
+            this.wuMoveBottomButton.set("disabled", !wuCanDown);
         }
     });
 });

+ 84 - 71
esp/files/scripts/ESPActivity.js

@@ -23,42 +23,22 @@ define([
     "hpcc/WsSMC",
     "hpcc/ESPUtil",
     "hpcc/ESPRequest",
+    "hpcc/ESPQueue",
     "hpcc/ESPWorkunit",
     "hpcc/ESPDFUWorkunit"
 
 ], function (declare, arrayUtil, lang, Memory, Observable,
-    WsSMC, ESPUtil, ESPRequest, ESPWorkunit, ESPDFUWorkunit) {
+    WsSMC, ESPUtil, ESPRequest, ESPQueue, ESPWorkunit, ESPDFUWorkunit) {
 
     var _workunits = {};
 
     var Store = declare([Memory], {
         idProperty: "__hpcc_id",
         mayHaveChildren: function (item) {
-            return (item.children && item.children.length)
+            return (item.getChildCount && item.getChildCount());
         },
         getChildren: function (parent, options) {
-            var store = Observable(new Memory({
-                idProperty: "__hpcc_id",
-                parent: parent,
-                _watched: [],
-                data: []
-            }));
-            arrayUtil.forEach(parent.children, function (item, idx) {
-                var wu = item.Server === "DFUserver" ? ESPDFUWorkunit.Get(item.Wuid) : ESPWorkunit.Get(item.Wuid);
-                wu.updateData(item);
-                try {
-                    store.add(wu);
-                    if (!store._watched[item.Wuid]) {
-                        store._watched[item.Wuid] = wu.watch("changedCount", function (name, oldValue, newValue) {
-                            if (oldValue !== newValue) {
-                                store.notify(wu, item.__hpcc_id);
-                            }
-                        });
-                    }
-                } catch (e) {
-                }
-            });
-            return store.query();
+            return parent.queryChildren();
         }
     });
 
@@ -78,10 +58,48 @@ define([
 
         //  ---  ---  ---
         constructor: function (args) {
-            this.inherited(arguments);
             this.store = new Store();
+            this._watched = [];
             this.observableStore = new Observable(this.store)
         },
+
+        isInstanceOfQueue: function (obj) {
+            return ESPQueue.isInstanceOfQueue(obj);
+        },
+
+        isInstanceOfWorkunit: function (obj) {
+            return ESPWorkunit.isInstanceOfWorkunit(obj) || ESPDFUWorkunit.isInstanceOfWorkunit(obj);
+        },
+
+        setBanner: function (bannerText) {
+            this.getActivity({
+                FromSubmitBtn: true,
+                BannerAction: bannerText != "",
+                EnableChatURL: 0,
+                BannerContent: bannerText,
+                BannerColor: "red",
+                BannerSize: 4,
+                BannerScroll: 2
+            });
+        },
+
+        resolve: function (id) {
+            var queue = this.observableStore.get(id);
+            if (queue) {
+                return queue;
+            }
+
+            var wu = id[0] === "D" ? ESPDFUWorkunit.Get(id) : ESPWorkunit.Get(id);
+            if (wu) {
+                //  is wu still in a queue?
+                queue = wu.get("ESPQueue");
+                if (queue) {
+                    return queue.getChild(id);
+                }
+            }
+            return null;
+        },
+
         monitor: function (callback) {
             if (callback && this.changedCount) {
                 callback(this);
@@ -104,65 +122,60 @@ define([
                 request: request
             }).then(function (response) {
                 if (lang.exists("ActivityResponse", response)) {
-                    context.updateData(response.ActivityResponse);
-
                     var targetClusters = [];
                     var targetClusterMap = {};
-                    context.refreshTargetClusters("HThorClusterList.TargetCluster", targetClusters, targetClusterMap);
-                    context.refreshTargetClusters("ThorClusterList.TargetCluster", targetClusters, targetClusterMap);
-                    context.refreshTargetClusters("RoxieClusterList.TargetCluster", targetClusters, targetClusterMap);
-                    context.refreshServerJobQueue("ServerJobQueues.ServerJobQueue", targetClusters, targetClusterMap);
-                    context.refreshActiveWorkunits("Running.ActiveWorkunit", targetClusters, targetClusterMap);
+                    context.refreshTargetClusters(lang.getObject("ActivityResponse.HThorClusterList.TargetCluster", false, response), targetClusters, targetClusterMap);
+                    context.refreshTargetClusters(lang.getObject("ActivityResponse.ThorClusterList.TargetCluster", false, response), targetClusters, targetClusterMap);
+                    context.refreshTargetClusters(lang.getObject("ActivityResponse.RoxieClusterList.TargetCluster", false, response), targetClusters, targetClusterMap);
+                    context.refreshTargetClusters(lang.getObject("ActivityResponse.ServerJobQueues.ServerJobQueue", false, response), targetClusters, targetClusterMap);
+                    context.refreshActiveWorkunits(lang.getObject("ActivityResponse.Running.ActiveWorkunit", false, response), targetClusters, targetClusterMap);
                     context.store.setData(targetClusters);
-                    context.updateData({
-                        targetClusters: targetClusters
-                    });
+                    context.updateData(response.ActivityResponse);
                 }
                 return response;
             });
         },
 
-        setBanner: function (bannerText) {
-            this.getActivity({
-                FromSubmitBtn: true,
-                BannerAction: bannerText != "",
-                EnableChatURL: 0,
-                BannerContent: bannerText,
-                BannerColor: "red",
-                BannerSize: 4,
-                BannerScroll: 2
-            });
-        },
-
-        refreshTargetClusters: function (targetClusterStr, targetClusters, targetClusterMap) {
-            if (lang.exists(targetClusterStr, this)) {
-                arrayUtil.forEach(lang.getObject(targetClusterStr, false, this), function (item, idx) {
-                    item["__hpcc_type"] = "TargetCluster";
-                    item["__hpcc_id"] = item.ClusterName;
-                    item.children = [];
-                    targetClusters.push(item);
-                    targetClusterMap[item.ClusterName] = item;
-                });
-            }
-        },
-
-        refreshServerJobQueue: function (serverJobQueueStr, targetClusters, targetClusterMap) {
-            if (lang.exists(serverJobQueueStr, this)) {
-                arrayUtil.forEach(lang.getObject(serverJobQueueStr, false, this), function (item, idx) {
-                    item["__hpcc_type"] = "TargetCluster";
-                    item["__hpcc_id"] = item.QueueName;
-                    item.children = [];
-                    targetClusters.push(item);
-                    targetClusterMap[item.QueueName] = item;
+        refreshTargetClusters: function (responseTargetClusters, targetClusters, targetClusterMap) {
+            var context = this;
+            if (responseTargetClusters) {
+                arrayUtil.forEach(responseTargetClusters, function (item, idx) {
+                    var queue = null;
+                    if (item.ClusterName) {
+                        queue = ESPQueue.GetTargetCluster(item.ClusterName);
+                    } else {
+                        queue = ESPQueue.GetServerJobQueue(item.QueueName);
+                    }
+                    queue.updateData(item);
+                    queue.clearChildren();
+                    targetClusters.push(queue);
+                    targetClusterMap[queue.__hpcc_id] = queue;
+                    if (!context._watched[queue.__hpcc_id]) {
+                        context._watched[queue.__hpcc_id] = queue.watch("changedCount", function (name, oldValue, newValue) {
+                            if (oldValue !== newValue) {
+                                context.observableStore.notify(queue, queue.__hpcc_id);
+                            }
+                        });
+                    }
                 });
             }
         },
 
-        refreshActiveWorkunits: function (activeWorkunitsStr, targetClusters, targetClusterMap) {
-            if (lang.exists(activeWorkunitsStr, this)) {
-                arrayUtil.forEach(lang.getObject(activeWorkunitsStr, false, this), function (item, idx) {
+        refreshActiveWorkunits: function (responseActiveWorkunits, targetClusters, targetClusterMap) {
+            if (responseActiveWorkunits) {
+                arrayUtil.forEach(responseActiveWorkunits, function (item, idx) {
                     item["__hpcc_id"] = item.Wuid;
-                    targetClusterMap[item.ClusterName ? item.ClusterName : item.QueueName].children.push(item);
+                    var queue = null;
+                    if (item.ClusterName) {
+                        queue = ESPQueue.GetTargetCluster(item.ClusterName);
+                    } else {
+                        queue = ESPQueue.GetServerJobQueue(item.QueueName);
+                    }
+                    var wu = item.Server === "DFUserver" ? ESPDFUWorkunit.Get(item.Wuid) : ESPWorkunit.Get(item.Wuid);
+                    wu.updateData(lang.mixin({
+                        __hpcc_id: item.Wuid,
+                    }, item));
+                    queue.addChild(wu);
                 });
             }
         },

+ 10 - 1
esp/files/scripts/ESPDFUWorkunit.js

@@ -203,7 +203,12 @@ define([
         },
         _action: function (action, callback) {
         },
-        abort: function (callback) {
+        abort: function () {
+            return FileSpray.AbortDFUWorkunit({
+                request: {
+                    wuid: this.Wuid
+                }
+            });
         },
         doDelete: function (callback) {
         },
@@ -287,6 +292,10 @@ define([
     });
 
     return {
+        isInstanceOfWorkunit: function (obj) {
+            return obj.isInstanceOf(Workunit);
+        },
+
         Get: function (wuid) {
             var store = new Store();
             return store.get(wuid);

+ 271 - 0
esp/files/scripts/ESPQueue.js

@@ -0,0 +1,271 @@
+/*##############################################################################
+#    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/array",
+    "dojo/_base/lang",
+    "dojo/store/Memory",
+    "dojo/store/Observable",
+
+    "hpcc/WsSMC",
+    "hpcc/ESPUtil",
+    "hpcc/ESPRequest",
+    "hpcc/ESPWorkunit",
+    "hpcc/ESPDFUWorkunit"
+
+], function (declare, arrayUtil, lang, Memory, Observable,
+    WsSMC, ESPUtil, ESPRequest, ESPWorkunit, ESPDFUWorkunit) {
+
+    var Store = declare([Memory], {
+        idProperty: "__hpcc_id",
+    });
+
+    var Queue = declare([ESPUtil.Singleton, ESPUtil.Monitor], {
+
+        constructor: function (id) {
+            this.__hpcc_id = id;
+
+            this._watched = [];
+            this.children = Observable(new Memory({
+                idProperty: "__hpcc_id",
+                parent: this,
+                data: []
+            }));
+        },
+
+        pause: function () {
+            return WsSMC.PauseQueue({
+                request: {
+                    QueueName: this.QueueName
+                }
+            });
+        },
+
+        resume: function () {
+            return WsSMC.ResumeQueue({
+                request: {
+                    QueueName: this.QueueName
+                }
+            });
+        },
+
+        clear: function () {
+            var context = this;
+            return WsSMC.ClearQueue({
+                request: {
+                    QueueName: this.QueueName
+                }
+            }).then(function (response) {
+                context.clearChildren();
+                return response;
+            });
+        },
+
+        setPriority: function (wuid, priority) {    //  high, normal, low
+            return WsSMC.SetJobPriority({
+                request: {
+                    QueueName: this.QueueName,
+                    Wuid: wuid,
+                    Priority: priority
+                }
+            });
+        },
+
+        moveTop: function (wuid) {
+            return WsSMC.MoveJobFront({
+                request: {
+                    QueueName: this.QueueName,
+                    Wuid: wuid
+                }
+            });
+        },
+
+        moveUp: function (wuid) {
+            return WsSMC.MoveJobUp({
+                request: {
+                    QueueName: this.QueueName,
+                    Wuid: wuid
+                }
+            });
+        },
+
+        moveDown: function (wuid) {
+            return WsSMC.MoveJobDown({
+                request: {
+                    QueueName: this.QueueName,
+                    Wuid: wuid
+                }
+            });
+        },
+
+        moveBottom: function (wuid) {
+            return WsSMC.MoveJobBack({
+                request: {
+                    QueueName: this.QueueName,
+                    Wuid: wuid
+                }
+            });
+        },
+
+        canChildMoveUp: function (id) {
+            return (this.getChildIndex(id) > 0);
+        },
+
+        canChildMoveDown: function (id) {
+            return (this.getChildIndex(id) < this.getChildCount() - 1);
+        },
+
+        clearChildren: function () {
+            this.children.setData([]);
+        },
+
+        addChild: function (wu) {
+            wu.set("ESPQueue", this);
+            this.children.put(wu, {
+                overwrite: true
+            });
+            if (!this._watched[wu.__hpcc_id]) {
+                var context = this;
+                this._watched[wu.__hpcc_id] = wu.watch("changedCount", function (name, oldValue, newValue) {
+                    if (oldValue !== newValue) {
+                        context.children.notify(wu, wu.__hpcc_id);
+                    }
+                });
+            }
+        },
+
+        getChild: function (id) {
+            return this.children.get(id);
+        },
+
+        getChildIndex: function (id) {
+            return this.children.index[id];
+        },
+
+        getChildCount: function () {
+            return this.children.data.length;
+        },
+
+        queryChildren: function () {
+            return this.children.query();
+        }
+    });
+
+    var TargetCluster = declare([Queue], {
+        getDisplayName: function () {
+            return this.ClusterName;
+        },
+
+        isPaused: function () {
+            switch (this.ClusterStatus) {
+                case 1:
+                case 2:
+                    return true;
+            }
+            return false;
+        },
+
+        pause: function () {
+            var context = this;
+            return this.inherited(arguments).then(function (response) {
+                context.updateData({
+                    ClusterStatus: 2
+                });
+                return response;
+            });
+        },
+
+        resume: function () {
+            var context = this;
+            return this.inherited(arguments).then(function (response) {
+                context.updateData({
+                    ClusterStatus: 0
+                });
+                return response;
+            });
+        }
+    });
+
+    var ServerJobQueue = declare([Queue], {
+        getDisplayName: function () {
+            return this.QueueName;
+        },
+
+        isPaused: function () {
+            if (this.QueueStatus === "paused") {
+                return true;
+            }
+            return false;
+        },
+
+        pause: function () {
+            var context = this;
+            return this.inherited(arguments).then(function (response) {
+                context.updateData({
+                    QueueStatus: "paused"
+                });
+                return response;
+            });
+        },
+
+        resume: function () {
+            var context = this;
+            return this.inherited(arguments).then(function (response) {
+                context.updateData({
+                    QueueStatus: null
+                });
+                return response;
+            });
+        }
+
+    });
+
+    var globalQueueStore = null;
+    GetGlobalQueueStore = function () {
+        if (!globalQueueStore) {
+            globalQueueStore = new Store();
+        }
+        return globalQueueStore;
+    }
+    
+    return {
+        isInstanceOfQueue: function (obj) {
+            return obj.isInstanceOf(Queue);
+        },
+
+        GetTargetCluster: function (name) {
+            var store = GetGlobalQueueStore();
+            var id = "TargetCluster::" + name;
+            var retVal = store.get(id);
+            if (!retVal) {
+                retVal = new TargetCluster(id);
+                store.put(retVal);
+            }
+            return retVal;
+        },
+
+        GetServerJobQueue: function (name) {
+            var store = GetGlobalQueueStore();
+            var id = "ServerJobQueue::" + name;
+            var retVal = store.get(id);
+            if (!retVal) {
+                retVal = new ServerJobQueue(id);
+                store.put(retVal);
+            }
+            return retVal;
+        }
+    };
+});

+ 4 - 0
esp/files/scripts/ESPWorkunit.js

@@ -634,6 +634,10 @@ define([
     });
 
     return {
+        isInstanceOfWorkunit: function (obj) {
+            return obj.isInstanceOf(Workunit);
+        },
+
         Create: function (params) {
             retVal = new Workunit(params);
             retVal.create();

+ 3 - 0
esp/files/scripts/FileSpray.js

@@ -302,6 +302,9 @@ define([
         UpdateDFUWorkunit: function (params) {
             return ESPRequest.send("FileSpray", "UpdateDFUWorkunit", params);
         },
+        AbortDFUWorkunit: function(params) {
+            return ESPRequest.send("FileSpray", "AbortDFUWorkunit", params);
+        },
         DFUWUFile: function (params) {
             lang.mixin(params, {
                 handleAs: "text"

+ 6 - 2
esp/files/scripts/GridDetailsWidget.js

@@ -90,7 +90,9 @@ define([
 
         _onRowDblClick: function (row) {
             var tab = this.ensurePane(row);
-            this.selectChild(tab);
+            if (tab) {
+                this.selectChild(tab);
+            }
         },
 
         //  Implementation  ---
@@ -189,7 +191,9 @@ define([
             var retVal = registry.byId(id);
             if (!retVal) {
                 retVal = this.createDetail(id, row, params);
-                this.addChild(retVal);
+                if (retVal) {
+                    this.addChild(retVal);
+                }
             } else {
                 lang.mixin(retVal.hpcc, {
                     refreshParams: params

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

@@ -116,7 +116,6 @@ define([
             this.resultsWidget = registry.byId(this.id + "_Results");
             this.filesWidget = registry.byId(this.id + "_Files");
             this.vizWidget = registry.byId(this.id + "_Visualize");
-            this.vizWidget.set("disabled", true);
             this.timersWidget = registry.byId(this.id + "_Timers");
             this.graphsWidget = registry.byId(this.id + "_Graphs");
             this.sourceWidget = registry.byId(this.id + "_Source");

+ 24 - 0
esp/files/scripts/WsSMC.js

@@ -24,6 +24,30 @@ define([
     return {
         Activity: function (params) {
             return ESPRequest.send("WsSMC", "Activity", params);
+        },
+        PauseQueue: function (params) {
+            return ESPRequest.send("WsSMC", "PauseQueue", params);
+        },
+        ResumeQueue: function (params) {
+            return ESPRequest.send("WsSMC", "ResumeQueue", params);
+        },
+        ClearQueue: function (params) {
+            return ESPRequest.send("WsSMC", "ClearQueue", params);
+        },
+        SetJobPriority: function (params) {
+            return ESPRequest.send("WsSMC", "SetJobPriority", params);
+        },
+        MoveJobFront: function (params) {
+            return ESPRequest.send("WsSMC", "MoveJobFront", params);
+        },
+        MoveJobUp: function (params) {
+            return ESPRequest.send("WsSMC", "MoveJobUp", params);
+        },
+        MoveJobDown: function (params) {
+            return ESPRequest.send("WsSMC", "MoveJobDown", params);
+        },
+        MoveJobBack: function (params) {
+            return ESPRequest.send("WsSMC", "MoveJobBack", params);
         }
     };
 });