From 559d0ba258f5320b562cfdf483698118df00b2bd Mon Sep 17 00:00:00 2001
From: Jorrit Schaap <schaap@astron.nl>
Date: Fri, 1 Jul 2016 12:43:36 +0000
Subject: [PATCH] Task #9351 #9353 #9355: integrated cleanup and pie-chart
 datamanagement overview in webscheduler

---
 .../DataManagementCommon/path.py              |   4 +-
 .../StorageQueryService/diskusage.py          |   4 +-
 .../StorageQueryService/service.py            |   4 +
 .../app/controllers/cleanupcontroller.js      | 196 +++++++++++++++++-
 .../static/app/controllers/datacontroller.js  |  64 ++++--
 .../static/app/controllers/gridcontroller.js  |   7 +
 .../lib/static/css/main.css                   |  14 +-
 .../lib/webservice.py                         |  74 ++++++-
 8 files changed, 332 insertions(+), 35 deletions(-)

diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py
index 4fe151e88c4..86acf0e8c6d 100644
--- a/SAS/DataManagement/DataManagementCommon/path.py
+++ b/SAS/DataManagement/DataManagementCommon/path.py
@@ -151,8 +151,8 @@ class PathResolver:
 
         if proc.returncode != 0:
             # lfs puts it's error message in stdout
-            logger.error(out)
-            return {'found': False, 'path': path, 'message': out}
+            logger.error(out + err)
+            return {'found': False, 'path': path, 'message': out + err}
 
         logger.debug(out)
 
diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py
index 6a3887f971b..febd4c36351 100644
--- a/SAS/DataManagement/StorageQueryService/diskusage.py
+++ b/SAS/DataManagement/StorageQueryService/diskusage.py
@@ -46,7 +46,7 @@ def getDiskUsageForPath(path):
         parts = [p.strip() for p in file_lines[0].split(',')]
         partsDict = {p.split(':')[0].strip():p.split(':')[1].strip() for p in parts}
 
-        result = {'found': True, 'disk_usage': None, 'path': path}
+        result = {'found': True, 'disk_usage': None, 'path': path, 'name': path.split('/')[-1]}
 
         if 'size' in partsDict:
             result['disk_usage'] = int(partsDict['size'])
@@ -58,7 +58,7 @@ def getDiskUsageForPath(path):
     else:
         dir_lines = [l for l in lines if 'dir count' in l]
         if dir_lines:
-            result = {'found': True, 'disk_usage': 0, 'nr_of_files': 0, 'path': path}
+            result = {'found': True, 'disk_usage': 0, 'nr_of_files': 0, 'path': path, 'name': path.split('/')[-1]}
         else:
             result = {'found': False, 'path': path }
 
diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py
index c7dfd74e2e1..a40f341861e 100644
--- a/SAS/DataManagement/StorageQueryService/service.py
+++ b/SAS/DataManagement/StorageQueryService/service.py
@@ -72,6 +72,10 @@ def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, brok
                                  'cache_manager':cache_manager})
 
 def main():
+    # make sure we run in UTC timezone
+    import os
+    os.environ['TZ'] = 'UTC'
+    
     # Check the invocation arguments
     parser = OptionParser("%prog [options]",
                           description='runs the storagequery service')
diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js
index 50af9f2dac5..d0848e7ee6f 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js
+++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js
@@ -2,7 +2,7 @@
 
 var cleanupControllerMod = angular.module('CleanupControllerMod', ['ui.bootstrap']);
 
-cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$http', '$q', function($scope, $uibModal, $http, $q) {
+cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$http', '$q', 'dataService', function($scope, $uibModal, $http, $q, dataService) {
     var self = this;
 
     self.getTaskDataPath = function(task) {
@@ -19,8 +19,10 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h
     };
 
     self.deleteTaskDataWithConfirmation = function(task) {
-        self.getTaskDataPath(task).then(function(result) {
-            openDeleteConfirmationDialog(task, result.datapath);
+        self.getTaskDataPath(task).then(function(path_result) {
+            dataService.getTaskDiskUsageByOTDBId(task.otdb_id).then(function(du_result) {
+                openDeleteConfirmationDialog(task, path_result.datapath, du_result.found ? du_result.task_directory.disk_usage_readable : "??B");
+            });
         });
     };
 
@@ -34,19 +36,19 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h
         });
     };
 
-    function openDeleteConfirmationDialog(task, path) {
+    function openDeleteConfirmationDialog(task, path, amount) {
         var modalInstance = $uibModal.open({
             animation: false,
             template: '<div class="modal-header">\
                             <h3 class="modal-title">Are you sure?</h3>\
                         </div>\
                         <div class="modal-body">\
-                            <p>This will delete all data in ' + path + '<br>\
+                            <p>This will delete ' + amount + ' of data in ' + path + '<br>\
                             Are you sure?</p>\
                         </div>\
                         <div class="modal-footer">\
                             <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>\
-                            <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>\
+                            <button class="btn btn-warning" type="button" autofocus ng-click="cancel()">Cancel</button>\
                         </div>',
             controller: function ($scope, $uibModalInstance) {
                 $scope.ok = function () {
@@ -60,5 +62,187 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h
             }
         });
     };
+
+
+    self.showTaskDiskUsage = function(task) {
+        var modalInstance = $uibModal.open({
+            animation: false,
+            template: '<div class="modal-header">\
+                            <h3 class="modal-title">Disk usage</h3>\
+                        </div>\
+                        <div class="modal-body" style="text-align:right">\
+                            <highchart id="chart_disk_usage" config="diskUsageChartConfig" style="width: 960px; height: 720px;" ></highchart>\
+                            <p>\
+                              <span style="margin-right:50px">Last updated at: {{leastRecentCacheTimestamp | date }}</span>\
+                              <button class="btn btn-primary glyphicon glyphicon-level-up" type="button" ng-click="up()" title="Up one level"></button>\
+                            </p>\
+                        </div>\
+                        <div class="modal-footer">\
+                            <button class="btn btn-primary" type="button" autofocus ng-click="ok()">OK</button>\
+                        </div>',
+            controller: function ($scope, $uibModalInstance) {
+                $scope.ok = function () {
+                    $uibModalInstance.close();
+                };
+
+                const OBJECT_TYPE_TASK = 'task';
+                const OBJECT_TYPE_PROJECT = 'project';
+                const OBJECT_TYPE_PROJECTS = 'projects';
+                $scope.watchedObjectType = OBJECT_TYPE_TASK;
+
+                $scope.leastRecentCacheTimestamp = '';
+
+                $scope.onPieClicked = function(event) {
+                    switch($scope.watchedObjectType) {
+                        case OBJECT_TYPE_PROJECT:
+                            loadTaskDiskUsage(this.otdb_id);
+                            break;
+                        case OBJECT_TYPE_PROJECTS:
+                            loadProjectDiskUsage(this.project_name);
+                            break;
+                    }
+                };
+
+                $scope.up = function () {
+                    switch($scope.watchedObjectType) {
+                        case OBJECT_TYPE_TASK:
+                            loadProjectDiskUsage($scope.diskUsageChartSeries[0].project_name);
+                            break;
+                        case OBJECT_TYPE_PROJECT:
+                            loadAllProjectsDiskUsage();
+                            break;
+                    }
+                };
+
+                $scope.diskUsageChartSeries = [{name:'Loading data...', data:[]}];
+
+                $scope.diskUsageChartConfig = {
+                    options: {
+                        chart: {
+                            type: 'pie',
+                            animation: {
+                                duration: 200
+                            }
+                        },
+                        plotOptions: {
+                            pie: {
+                                allowPointSelect: true,
+                                cursor: 'pointer',
+                                dataLabels: {
+                                    enabled: true
+                                },
+                                showInLegend: true
+                            },
+                            series: {
+                                point: {
+                                    events: {
+                                        click: $scope.onPieClicked
+                                    }
+                                }
+                            }
+                        },
+                        tooltip: {
+                            headerFormat: '{series.name}<br/>',
+                            pointFormat: '{point.name}: <b>{point.percentage:.1f}%</b>'
+                        }
+                    },
+                    series: $scope.diskUsageChartSeries,
+                    title: {
+                        text: 'Loading data...'
+                    },
+                    credits: {
+                        enabled: false
+                    },
+                    loading: false
+                }
+
+                var loadTaskDiskUsage = function(otdb_id) {
+                    dataService.getTaskDiskUsageByOTDBId(otdb_id).then(function(result) {
+                        if(result.found) {
+                            $scope.watchedObjectType = OBJECT_TYPE_TASK;
+                            $scope.diskUsageChartConfig.title.text = result.task_directory.name + ' ' + result.task_directory.disk_usage_readable;
+                            $scope.diskUsageChartSeries[0].name = result.task_directory.name + ' ' + result.task_directory.disk_usage_readable;
+                            var path_parts = result.task_directory.path.split('/');
+                            $scope.diskUsageChartSeries[0].project_name = path_parts[path_parts.length-2];
+                            $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length);
+                            $scope.leastRecentCacheTimestamp = result.task_directory.cache_timestamp;
+
+                            var sub_directory_names = Object.keys(result.sub_directories);
+                            sub_directory_names.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); });
+                            for(var sub_dir of sub_directory_names) {
+                                var sub_dir_result = result.sub_directories[sub_dir];
+                                $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable,y:sub_dir_result.disk_usage});
+
+                                if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) {
+                                    $scope.leastRecentCacheTimestamp = sub_dir_result.cache_timestamp;
+                                }
+                            }
+                            $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp);
+                        }else {
+                            $scope.ok();
+                        }
+                    });
+                };
+
+                var loadProjectDiskUsage = function(project_name) {
+                    dataService.getProjectDiskUsage(project_name).then(function(result) {
+                        if(result.found) {
+                            $scope.watchedObjectType = OBJECT_TYPE_PROJECT;
+                            $scope.diskUsageChartConfig.title.text = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable;
+                            $scope.diskUsageChartSeries[0].name = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable;
+                            $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length);
+                            $scope.leastRecentCacheTimestamp = result.projectdir.cache_timestamp;
+
+                            var sub_directory_names = Object.keys(result.sub_directories);
+                            sub_directory_names.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); });
+                            for(var sub_dir of sub_directory_names) {
+                                var sub_dir_result = result.sub_directories[sub_dir];
+                                $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable,
+                                                                        y:sub_dir_result.disk_usage,
+                                                                        otdb_id: parseInt(sub_dir_result.name.slice(1)) });
+
+                                if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) {
+                                    $scope.leastRecentCacheTimestamp = sub_dir_result.cache_timestamp;
+                                }
+                            }
+                            $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp);
+                        }else {
+                            $scope.ok();
+                        }
+                    });
+                };
+
+                var loadAllProjectsDiskUsage = function() {
+                    dataService.getProjectsDiskUsage().then(function(result) {
+                        if(result.found) {
+                            $scope.watchedObjectType = OBJECT_TYPE_PROJECTS;
+                            $scope.diskUsageChartConfig.title.text = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable;
+                            $scope.diskUsageChartSeries[0].name = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable;
+                            $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length);
+                            $scope.leastRecentCacheTimestamp = result.projectdir.cache_timestamp;
+
+                            var sub_directory_names = Object.keys(result.sub_directories);
+                            sub_directory_names.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); });
+                            for(var sub_dir of sub_directory_names) {
+                                var sub_dir_result = result.sub_directories[sub_dir];
+                                $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable,
+                                                                        y:sub_dir_result.disk_usage,
+                                                                        project_name: sub_dir_result.name });
+
+                                if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) {
+                                    $scope.leastRecentCacheTimestamp = sub_dir_result.cache_timestamp;
+                                }
+                            }
+                            $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp);
+                        }else {
+                            $scope.ok();
+                        }
+                    });
+                };
+
+                loadTaskDiskUsage(task.otdb_id);
+            }
+        });
+    };
 }]);
 
diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js
index 1406d27d525..ffda8c2e32a 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js
+++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js
@@ -91,14 +91,14 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
     //It's a stupid solution, but it works.
     //-- IMPORTANT REMARKS ABOUT UTC/LOCAL DATE --
 
-    convertDatestringToLocalUTCDate = function(dateString) {
+    self.convertDatestringToLocalUTCDate = function(dateString) {
         //first convert the dateString to proper Date
         var date = new Date(dateString)
         //then do our trick to offset the timestamp with the utcOffset, see explanation above.
         return new Date(date.getTime() - self.utcOffset)
     };
 
-    convertLocalUTCDateToISOString = function(local_utc_date) {
+    self.convertLocalUTCDateToISOString = function(local_utc_date) {
         //reverse trick to offset the timestamp with the utcOffset, see explanation above.
         var real_utc = new Date(local_utc_date.getTime() + self.utcOffset)
         return real_utc.toISOString();
@@ -122,7 +122,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
             changedObj.hasOwnProperty(prop) &&
             existingObj[prop] != changedObj[prop]) {
                 if(existingObj[prop] instanceof Date && typeof changedObj[prop] === "string") {
-                    existingObj[prop] = convertDatestringToLocalUTCDate(changedObj[prop]);
+                    existingObj[prop] = self.convertDatestringToLocalUTCDate(changedObj[prop]);
                 } else {
                     existingObj[prop] = changedObj[prop];
                 }
@@ -207,10 +207,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
         var defer = $q.defer();
         var url = '/rest/tasks';
         if(from) {
-            url += '/' + convertLocalUTCDateToISOString(from);
+            url += '/' + self.convertLocalUTCDateToISOString(from);
 
             if(until) {
-                url += '/' + convertLocalUTCDateToISOString(until);
+                url += '/' + self.convertLocalUTCDateToISOString(until);
             }
         }
 
@@ -218,8 +218,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
             //convert datetime strings to Date objects
             for(var i in result.tasks) {
                 var task = result.tasks[i];
-                task.starttime = convertDatestringToLocalUTCDate(task.starttime);
-                task.endtime = convertDatestringToLocalUTCDate(task.endtime);
+                task.starttime = self.convertDatestringToLocalUTCDate(task.starttime);
+                task.endtime = self.convertDatestringToLocalUTCDate(task.endtime);
 
             }
 
@@ -277,8 +277,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
     };
 
     self.putTask = function(task) {
-        task.starttime = convertLocalUTCDateToISOString(task.starttime);
-        task.endtime = convertLocalUTCDateToISOString(task.endtime);
+        task.starttime = self.convertLocalUTCDateToISOString(task.starttime);
+        task.endtime = self.convertLocalUTCDateToISOString(task.endtime);
         $http.put('/rest/tasks/' + task.id, task).error(function(result) {
             console.log("Error. Could not update task. " + result);
             //TODO: revert to old state
@@ -292,6 +292,18 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
         })
     };
 
+    self.getTaskDiskUsageByOTDBId = function(otdb_id) {
+        var defer = $q.defer();
+        $http.get('/rest/tasks/otdb/' + otdb_id + '/diskusage').success(function(result) {
+            console.log(result);
+            defer.resolve(result);
+        }).error(function(result) {
+            defer.resolve({found:false});
+        });
+
+        return defer.promise;
+    };
+
     self.computeMinMaxTaskTimes = function() {
         var starttimes = self.filteredTasks.map(function(t) { return t.starttime;});
         var endtimes = self.filteredTasks.map(function(t) { return t.endtime;});
@@ -336,7 +348,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
                 for(var status in resource_usages) {
                     var usages = resource_usages[status];
                     for(var usage of usages) {
-                        usage.timestamp = convertDatestringToLocalUTCDate(usage.timestamp);
+                        usage.timestamp = self.convertDatestringToLocalUTCDate(usage.timestamp);
                     }
                 }
                 self.resourceUsagesDict[result.resourceusages[i].resource_id] = result.resourceusages[i];
@@ -352,10 +364,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
         var defer = $q.defer();
         var url = '/rest/resourceclaims';
         if(from) {
-            url += '/' + convertLocalUTCDateToISOString(from);
+            url += '/' + self.convertLocalUTCDateToISOString(from);
 
             if(until) {
-                url += '/' + convertLocalUTCDateToISOString(until);
+                url += '/' + self.convertLocalUTCDateToISOString(until);
             }
         }
 
@@ -363,8 +375,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
             //convert datetime strings to Date objects
             for(var i in result.resourceclaims) {
                 var resourceclaim = result.resourceclaims[i];
-                resourceclaim.starttime = convertDatestringToLocalUTCDate(resourceclaim.starttime);
-                resourceclaim.endtime = convertDatestringToLocalUTCDate(resourceclaim.endtime);
+                resourceclaim.starttime = self.convertDatestringToLocalUTCDate(resourceclaim.starttime);
+                resourceclaim.endtime = self.convertDatestringToLocalUTCDate(resourceclaim.endtime);
             }
 
             var newClaimDict = self.toIdBasedDict(result.resourceclaims);
@@ -487,6 +499,28 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
         });
     };
 
+    self.getProjectDiskUsage = function(project_name) {
+        var defer = $q.defer();
+        $http.get('/rest/momprojects/' + project_name + '/diskusage').success(function(result) {
+            defer.resolve(result);
+        }).error(function(result) {
+            defer.resolve({found:false});
+        });
+
+        return defer.promise;
+    };
+
+    self.getProjectsDiskUsage = function() {
+        var defer = $q.defer();
+        $http.get('/rest/momprojects/diskusage').success(function(result) {
+            defer.resolve(result);
+        }).error(function(result) {
+            defer.resolve({found:false});
+        });
+
+        return defer.promise;
+    };
+
     self.getConfig = function() {
         var defer = $q.defer();
         $http.get('/rest/config').success(function(result) {
@@ -506,7 +540,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http,
 
     self._syncLofarTimeWithServer = function() {
         $http.get('/rest/lofarTime', {timeout:1000}).success(function(result) {
-            self.lofarTime = convertDatestringToLocalUTCDate(result.lofarTime);
+            self.lofarTime = self.convertDatestringToLocalUTCDate(result.lofarTime);
 
             //check if local to utc offset has changed
             self.utcOffset = moment().utcOffset()*60000;
diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js
index dc6a2b40a69..6c5b2f8bd3c 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js
+++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js
@@ -286,6 +286,13 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) {
                 cleanupCtrl.deleteTaskDataWithConfirmation(task);
             });
 
+            var liElement = angular.element('<li><a href="#">Show disk usage</a></li>');
+            ulElement.append(liElement);
+            liElement.on('click', function() {
+                closeContextMenu();
+                cleanupCtrl.showTaskDiskUsage(task);
+            });
+
             var closeContextMenu = function(cme) {
                 contextmenuElement.remove();
 
diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css
index 2abc793dd10..d03e408e6a4 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css
+++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css
@@ -17,7 +17,7 @@
 }
 
 .dropdown-menu {
-    z-index: 10000;
+    z-index: 1010;
 }
 
 .ui-grid-cell {
@@ -28,6 +28,10 @@
 /*     visibility: hidden; */
 }
 
+.ui-splitbar {
+    z-index: 1000;
+}
+
 .ui-layout-row > .ui-splitbar {
     height: 6px;
     background-color: #CCCCCC;
@@ -60,6 +64,14 @@ table.uib-timepicker td.uib-time {
     top: 65px;
 }
 
+.modal-content {
+    width: 105%
+}
+
+.modal-dialog {
+    width: 960px
+}
+
 .gantt-task-content {
   margin-top: 2.2px;
 }
diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py
index 174cbbabdef..0f562d0b597 100755
--- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py
+++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py
@@ -292,22 +292,25 @@ def getTaskDataPath(task_id):
         return jsonify({'datapath': result['path']})
     abort(404, result['message'] if result and 'message' in result else '')
 
-@app.route('/rest/tasks/<int:task_id>/diskusage', methods=['GET'])
-def getTaskDiskUsage(task_id):
+@app.route('/rest/tasks/otdb/<int:otdb_id>/diskusage', methods=['GET'])
+def getTaskDiskUsageByOTDBId(otdb_id):
     try:
-        task = rarpc.getTask(task_id)
+        result = sqrpc.getDiskUsageForTaskAndSubDirectories(otdb_id=otdb_id)
+    except Exception as e:
+        abort(500, str(e))
 
-        if not task:
-            abort(404, 'No such task (id=%s)' % task_id)
+    if result['found']:
+        return jsonify(result)
+    abort(404, result['message'] if result and 'message' in result else '')
 
-        result = sqrpc.getDiskUsageForOTDBId(task['otdb_id'])
+@app.route('/rest/tasks/<int:task_id>/diskusage', methods=['GET'])
+def getTaskDiskUsage(task_id):
+    try:
+        result = sqrpc.getDiskUsageForTaskAndSubDirectories(radb_id=task_id)
     except Exception as e:
         abort(500, str(e))
 
     if result['found']:
-        del result['found']
-        if 'disk_usage' in result:
-            result['disk_usage_readable'] = humanreadablesize(result['disk_usage'])
         return jsonify(result)
     abort(404, result['message'] if result and 'message' in result else '')
 
@@ -447,6 +450,59 @@ def getUpdateEvents():
 def getLofarTime():
     return jsonify({'lofarTime': asIsoFormat(datetime.utcnow())})
 
+@app.route('/diskusage.html')
+def getDiskUsagePage():
+    html = '<!DOCTYPE html><html><head><title>Disk Usage</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style>\n'
+
+    html += '<script src="https://code.jquery.com/jquery-1.11.3.js"></script>\n'
+    html += '<script src="https://code.highcharts.com/highcharts.js"></script>\n'
+    html += '<script src="https://code.highcharts.com/modules/exporting.js"></script>\n'
+
+    result = sqrpc.getDiskUsageForProjectDirAndSubDirectories(project_name='CEP4_commissioning')
+
+    html += """<script type="text/javascript">
+    $(function () {
+    $(document).ready(function () {
+
+        // Build the chart
+        $('#container').highcharts({
+            chart: {
+                plotBackgroundColor: null,
+                plotBorderWidth: null,
+                plotShadow: true,
+                type: 'pie'
+            },
+            title: {
+                text: 'Disk usage'
+            },
+            tooltip: {
+                pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
+            },
+            plotOptions: {
+                pie: {
+                    allowPointSelect: true,
+                    cursor: 'pointer',
+                    dataLabels: {
+                        enabled: true
+                    },
+                    showInLegend: true
+                }
+            },
+            series: [{
+                name: 'Projects',
+                colorByPoint: true,
+                data: [""" + ',\n'.join(["{name:\'%s\',y:%s}" % (x['path'].split('/')[-1], x['disk_usage']) for x in result['sub_directories'].values()]) + """]}]
+        });
+    });
+});</script>\n"""
+
+    html += '</head><body>\n'
+    html += '<div id="container" style="min-width: 310px; height: 400px; max-width: 600px; margin: 0 auto"></div>\n'
+
+    html += '</body></html>\n'
+
+    return html
+
 def main():
     # make sure we run in UTC timezone
     import os
-- 
GitLab