diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 4fe151e88c407772b23889cb69f5a594575a4a91..86acf0e8c6dcaad3310f73056b10efb5d4346495 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 6a3887f971bd54647bec46eeb794b9da4b58e1de..febd4c363511e154691b4abde4fcdeb6c6863bfe 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 c7dfd74e2e19acfdd15e35bffd3314eaed3312ce..a40f341861eb37a6fe27f0d84f4b00df8b65f8ab 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 50af9f2dac56527545edb91442ca6ae1b8100908..d0848e7ee6ff5705a6fd65ab3083c9eb793d80a3 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 1406d27d525adfcee78133183317cbd3211e3498..ffda8c2e32a8bdc250d77afe836c623bdba37bcc 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 dc6a2b40a694a1854966b67c717cb4451f5c942f..6c5b2f8bd3cf74f7161025f703f6bad6fcfcc83f 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 2abc793dd109eb8c1d970c65ed641eec7c7fa3ec..d03e408e6a40c726e8f72564e525f285a2a121f2 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 174cbbabdef16470be3ef90a3a62f5a8f3bf8931..0f562d0b5979cb04d13b2d812112a8723145fab5 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