mirror of
https://github.com/ansible/awx.git
synced 2024-10-30 22:21:13 +03:00
Merge pull request #42 from joefiorini/job-status-graph
Fixes responsiveness of graphs on dashboard
This commit is contained in:
commit
6a4104e913
3
.gitignore
vendored
3
.gitignore
vendored
@ -6,6 +6,7 @@ awx/projects
|
||||
awx/job_output
|
||||
awx/public/media
|
||||
awx/public/static
|
||||
awx/ui/tests/test-results.xml
|
||||
awx/ui/static/js/awx.min.js
|
||||
awx/ui/static/js/local_config.js
|
||||
awx/ui/static/css/awx.min.css
|
||||
@ -26,7 +27,7 @@ tar-build
|
||||
*.py[c,o]
|
||||
|
||||
# JavaScript
|
||||
/GruntFile.js
|
||||
/Gruntfile.js
|
||||
/bower.json
|
||||
/package.json
|
||||
node_modules/**
|
||||
|
24
.jshintrc
24
.jshintrc
@ -1,21 +1,31 @@
|
||||
{
|
||||
// Details: https://github.com/victorporof/Sublime-JSHint#using-your-own-jshintrc-options
|
||||
// Example: https://github.com/jshint/jshint/blob/master/examples/.jshintrc
|
||||
// Documentation: http://www.jshint.com/docs/
|
||||
|
||||
"browser": true,
|
||||
"jquery": true,
|
||||
"esnext": true,
|
||||
"globalstrict": true,
|
||||
"globals": { "angular":false, "alert":false, "$AnsibleConfig":true, "$basePath":true, "jsyaml":false, "_":false, "d3":false, "Donut3D":false, "nv":false },
|
||||
"curly": true,
|
||||
"immed": true,
|
||||
"latedef": "nofunc",
|
||||
"noarg": true,
|
||||
"nonew": true,
|
||||
"notypeof": true,
|
||||
"globals": {
|
||||
"angular":false,
|
||||
"alert":false,
|
||||
"$AnsibleConfig":true,
|
||||
"$basePath":true,
|
||||
"jsyaml":false,
|
||||
"_":false,
|
||||
"d3":false,
|
||||
"Donut3D":false,
|
||||
"nv":false
|
||||
},
|
||||
"strict": false,
|
||||
"quotmark": false,
|
||||
"smarttabs": true,
|
||||
"trailing": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"eqeqeq": true,
|
||||
"indent": 4,
|
||||
"onevar": true,
|
||||
"newcap": false
|
||||
}
|
||||
|
11
Makefile
11
Makefile
@ -66,7 +66,7 @@ MOCK_CFG ?=
|
||||
|
||||
.PHONY: clean rebase push requirements requirements_pypi requirements_jenkins \
|
||||
develop refresh adduser syncdb migrate dbchange dbshell runserver celeryd \
|
||||
receiver test test_coverage coverage_html test_ui test_jenkins dev_build \
|
||||
receiver test test_coverage coverage_html ui_analysis_report test_ui test_jenkins dev_build \
|
||||
release_build release_clean sdist rpmtar mock-rpm mock-srpm \
|
||||
deb deb-src debian reprepro setup_tarball
|
||||
|
||||
@ -240,9 +240,12 @@ test_coverage:
|
||||
coverage_html:
|
||||
coverage html
|
||||
|
||||
# Run UI unit tests using Selenium.
|
||||
test_ui:
|
||||
$(PYTHON) manage.py test -v2 awx.ui.tests
|
||||
ui_analysis_report: node_modules
|
||||
$(GRUNT) plato:report
|
||||
|
||||
# Run UI unit tests
|
||||
test_ui: node_modules
|
||||
$(GRUNT) karma:ci
|
||||
|
||||
# Run API unit tests across multiple Python/Django versions with Tox.
|
||||
test_tox:
|
||||
|
@ -18,11 +18,14 @@ if ($basePath) {
|
||||
urlPrefix = $basePath;
|
||||
}
|
||||
|
||||
|
||||
angular.module('Tower', [
|
||||
'ngRoute',
|
||||
'ngSanitize',
|
||||
'ngCookies',
|
||||
'RestServices',
|
||||
'DataServices',
|
||||
'DashboardGraphs',
|
||||
'AuthService',
|
||||
'Utilities',
|
||||
'LicenseHelper',
|
||||
@ -84,9 +87,6 @@ angular.module('Tower', [
|
||||
'SelectionHelper',
|
||||
'HostGroupsFormDefinition',
|
||||
'DashboardCountsWidget',
|
||||
'JobStatusGraphWidget',
|
||||
'HostPieChartWidget',
|
||||
'HostGraphWidget',
|
||||
'DashboardJobsWidget',
|
||||
'PortalJobsWidget',
|
||||
'StreamWidget',
|
||||
@ -131,7 +131,6 @@ angular.module('Tower', [
|
||||
.constant('AngularScheduler.useTimezone', true)
|
||||
.constant('AngularScheduler.showUTCField', true)
|
||||
.constant('$timezones.definitions.location', urlPrefix + 'lib/angular-tz-extensions/tz/data')
|
||||
|
||||
.config(['$routeProvider',
|
||||
function ($routeProvider) {
|
||||
|
||||
@ -399,7 +398,15 @@ angular.module('Tower', [
|
||||
|
||||
when('/home', {
|
||||
templateUrl: urlPrefix + 'partials/home.html',
|
||||
controller: 'Home'
|
||||
controller: 'Home',
|
||||
resolve: {
|
||||
graphData: function($q, jobStatusGraphData, hostCountGraphData) {
|
||||
return $q.all({
|
||||
jobStatus: jobStatusGraphData.get("month", "all"),
|
||||
hostCounts: hostCountGraphData.get()
|
||||
});
|
||||
}
|
||||
}
|
||||
}).
|
||||
|
||||
when('/home/groups', {
|
||||
|
@ -28,7 +28,7 @@
|
||||
// > password_strength = green
|
||||
// It also controls password validation. Passwords are rejected if the score is not > password_strength.
|
||||
|
||||
session_timeout: 1800, // Number of seconds before an inactive session is automatically timed out and forced to log in again.
|
||||
session_timeout: 1800, // Number of seconds before an inactive session is automatically timed out and forced to log in again.
|
||||
// Separate from time out value set in API.
|
||||
|
||||
|
||||
|
@ -25,12 +25,12 @@
|
||||
* Host count graph should only be loaded if the user is a super user
|
||||
*
|
||||
*/
|
||||
function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, HostGraph, JobStatusGraph, HostPieChart, DashboardJobs,
|
||||
ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button){
|
||||
function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, DashboardJobs,
|
||||
ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, $window, graphData){
|
||||
|
||||
ClearScope('home');
|
||||
|
||||
var buttons, html, e, waitCount, loadedCount,borderStyles, jobs_scope, schedule_scope;
|
||||
var buttons, html, e, borderStyles;
|
||||
|
||||
// Add buttons to the top of the Home page. We're using lib/ansible/generator_helpers.js-> Buttons()
|
||||
// to build buttons dynamically and insure all styling and icons match the rest of the application.
|
||||
@ -64,39 +64,16 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait,
|
||||
e.html(html);
|
||||
$compile(e)($scope);
|
||||
|
||||
waitCount = 4;
|
||||
loadedCount = 0;
|
||||
|
||||
if (!$routeParams.login) {
|
||||
// If we're not logging in, start the Wait widget. Otherwise, it's already running.
|
||||
//Wait('start');
|
||||
}
|
||||
|
||||
if ($scope.removeWidgetLoaded) {
|
||||
$scope.removeWidgetLoaded();
|
||||
}
|
||||
$scope.removeWidgetLoaded = $scope.$on('WidgetLoaded', function (e, label, jobscope, schedulescope) {
|
||||
// Once all the widgets report back 'loaded', turn off Wait widget
|
||||
if(label==="dashboard_jobs"){
|
||||
jobs_scope = jobscope;
|
||||
schedule_scope = schedulescope;
|
||||
}
|
||||
loadedCount++;
|
||||
if (loadedCount === waitCount) {
|
||||
$(window).resize(_.debounce(function() {
|
||||
$scope.$emit('ResizeJobGraph');
|
||||
$scope.$emit('ResizeHostGraph');
|
||||
$scope.$emit('ResizeHostPieGraph');
|
||||
Wait('stop');
|
||||
}, 500));
|
||||
$(window).resize();
|
||||
}
|
||||
});
|
||||
|
||||
if ($scope.removeDashboardReady) {
|
||||
$scope.removeDashboardReady();
|
||||
}
|
||||
$scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) {
|
||||
|
||||
nv.dev=false;
|
||||
|
||||
|
||||
@ -106,64 +83,24 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait,
|
||||
"margin-bottom": "15px"};
|
||||
$('.graph-container').css(borderStyles);
|
||||
|
||||
var winHeight = $(window).height(),
|
||||
available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120;
|
||||
$('.graph-container').height(available_height/2);
|
||||
// // chart.update();
|
||||
|
||||
DashboardCounts({
|
||||
scope: $scope,
|
||||
target: 'dash-counts',
|
||||
dashboard: data
|
||||
});
|
||||
|
||||
JobStatusGraph({
|
||||
scope: $scope,
|
||||
target: 'dash-job-status-graph',
|
||||
dashboard: data
|
||||
});
|
||||
// // chart.update();
|
||||
|
||||
$scope.graphData = graphData;
|
||||
|
||||
if ($rootScope.user_is_superuser === true) {
|
||||
waitCount = 5;
|
||||
HostGraph({
|
||||
scope: $scope,
|
||||
target: 'dash-host-count-graph',
|
||||
dashboard: data
|
||||
});
|
||||
}
|
||||
else{
|
||||
$('#dash-host-count-graph').remove(); //replaceWith("<div id='dash-host-count-graph' class='left-side col-sm-12 col-xs-12'></div>");
|
||||
}
|
||||
DashboardJobs({
|
||||
scope: $scope,
|
||||
target: 'dash-jobs-list',
|
||||
dashboard: data
|
||||
});
|
||||
HostPieChart({
|
||||
scope: $scope,
|
||||
target: 'dash-host-status-graph',
|
||||
dashboard: data
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if ($rootScope.removeJobStatusChange) {
|
||||
$rootScope.removeJobStatusChange();
|
||||
}
|
||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function() {
|
||||
jobs_scope.refreshJobs();
|
||||
$scope.$emit('ReloadJobStatusGraph');
|
||||
|
||||
});
|
||||
|
||||
if ($rootScope.removeScheduleChange) {
|
||||
$rootScope.removeScheduleChange();
|
||||
}
|
||||
$rootScope.removeScheduleChange = $rootScope.$on('ScheduleChange', function() {
|
||||
schedule_scope.refreshSchedules();
|
||||
$scope.$emit('ReloadJobStatusGraph');
|
||||
});
|
||||
|
||||
$scope.showActivity = function () {
|
||||
Stream({
|
||||
scope: $scope
|
||||
@ -172,23 +109,23 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait,
|
||||
|
||||
$scope.refresh = function () {
|
||||
Wait('start');
|
||||
loadedCount = 0;
|
||||
Rest.setUrl(GetBasePath('dashboard'));
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
$scope.$emit('dashboardReady', data);
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status });
|
||||
});
|
||||
.success(function (data) {
|
||||
$scope.dashboardData = data;
|
||||
$scope.$emit('dashboardReady', data);
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status });
|
||||
});
|
||||
};
|
||||
|
||||
$scope.refresh();
|
||||
|
||||
}
|
||||
|
||||
Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph','JobStatusGraph', 'HostPieChart', 'DashboardJobs',
|
||||
'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button'
|
||||
Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'DashboardJobs',
|
||||
'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button', '$window', 'graphData'
|
||||
];
|
||||
|
||||
|
||||
@ -757,4 +694,4 @@ function HomeHosts($scope, $location, $routeParams, HomeHostList, GenerateList,
|
||||
HomeHosts.$inject = ['$scope', '$location', '$routeParams', 'HomeHostList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller',
|
||||
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'SetStatus', 'ToggleHostEnabled', 'HostsEdit', 'Stream',
|
||||
'Find', 'ShowJobSummary', 'ViewJob'
|
||||
];
|
||||
];
|
||||
|
@ -527,7 +527,9 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa
|
||||
|
||||
if($scope.survey_enabled === true && $scope.survey_exists!==true){
|
||||
$scope.$emit("PromptForSurvey");
|
||||
} else $scope.$emit("GatherFormFields");
|
||||
} else {
|
||||
$scope.$emit("GatherFormFields");
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
@ -634,7 +636,9 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP
|
||||
' project or make the playbooks available on the file system.', 'alert-info');
|
||||
});
|
||||
}
|
||||
else Wait('stop');
|
||||
else {
|
||||
Wait('stop');
|
||||
}
|
||||
};
|
||||
|
||||
// Detect and alert user to potential SCM status issues
|
||||
@ -964,7 +968,9 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP
|
||||
|
||||
if($scope.survey_enabled === true && $scope.survey_exists!==true){
|
||||
$scope.$emit("PromptForSurvey");
|
||||
} else $scope.$emit("GatherFormFields");
|
||||
} else {
|
||||
$scope.$emit("GatherFormFields");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -1021,11 +1027,12 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP
|
||||
if($scope.survey_enabled === true && $scope.survey_exists!==true){
|
||||
$scope.$emit("PromptForSurvey");
|
||||
}
|
||||
else
|
||||
else {
|
||||
PlaybookRun({
|
||||
scope: $scope,
|
||||
id: id
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// handler for 'Enable Survey' button
|
||||
@ -1063,4 +1070,4 @@ JobTemplatesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$l
|
||||
'GetBasePath', 'md5Setup', 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait', 'Stream', 'Empty', 'Prompt',
|
||||
'ParseVariableString', 'ToJSON', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'GetChoices',
|
||||
'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit', 'PlaybookRun' , 'SurveyControllerInit', '$sce'
|
||||
];
|
||||
];
|
||||
|
53
awx/ui/static/js/directives/auto-size-module.js
Normal file
53
awx/ui/static/js/directives/auto-size-module.js
Normal file
@ -0,0 +1,53 @@
|
||||
angular.module('DashboardGraphs')
|
||||
.directive('autoSizeModule', ['$window', '$timeout', function($window, $timeout) {
|
||||
|
||||
// Adjusts the size of the module so that all modules
|
||||
// fit into a single a page; assumes there are 2 rows
|
||||
// of modules, with the available height being offset
|
||||
// by the navbar & the count summaries module
|
||||
return function(scope, element) {
|
||||
|
||||
// We need to trigger a resize on the first call
|
||||
// to this when the view things load; but we don't want
|
||||
// to trigger a global window resize for everything that
|
||||
// has an auto resize, since they'll all pick it up with
|
||||
// a single call
|
||||
var triggerResize =
|
||||
_.throttle(function() {
|
||||
$($window).resize();
|
||||
}, 1000);
|
||||
|
||||
function adjustSizeInitially() {
|
||||
adjustSize();
|
||||
triggerResize();
|
||||
}
|
||||
|
||||
function adjustSize() {
|
||||
var winHeight = $($window).height(),
|
||||
available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120;
|
||||
element.height(available_height/2);
|
||||
}
|
||||
|
||||
$($window).resize(adjustSize);
|
||||
|
||||
element.on('$destroy', function() {
|
||||
$($window).off('resize', adjustSize);
|
||||
});
|
||||
|
||||
// Wait a second or until dashboardReady triggers,
|
||||
// whichever comes first. The timeout handles cases
|
||||
// where dashboardReady never fires.
|
||||
|
||||
var dashboardReadyTimeout = $timeout(adjustSizeInitially, 500);
|
||||
|
||||
// This makes sure count-container div is loaded
|
||||
// by controllers/Home.js before we use it
|
||||
// to determine the available window height
|
||||
scope.$on('dashboardReady', function() {
|
||||
$timeout.cancel(dashboardReadyTimeout);
|
||||
adjustSizeInitially();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
}]);
|
1
awx/ui/static/js/directives/dashboard-graphs.js
Normal file
1
awx/ui/static/js/directives/dashboard-graphs.js
Normal file
@ -0,0 +1 @@
|
||||
angular.module('DashboardGraphs', []);
|
126
awx/ui/static/js/directives/host-count-graph.js
Normal file
126
awx/ui/static/js/directives/host-count-graph.js
Normal file
@ -0,0 +1,126 @@
|
||||
angular.module('DashboardGraphs').
|
||||
directive('hostCountGraph', ['GetBasePath', 'Rest', 'adjustGraphSize', '$window', function(getBasePath, Rest, adjustGraphSize, $window) {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/static/partials/host_count_graph.html',
|
||||
link: link
|
||||
};
|
||||
|
||||
function link(scope, element, attr) {
|
||||
var license_graph;
|
||||
|
||||
scope.$watch(attr.data, function(data) {
|
||||
|
||||
if(!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
createGraph(data.hosts, data.license);
|
||||
});
|
||||
|
||||
function onResize() {
|
||||
|
||||
if(!license_graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustGraphSize(license_graph, element);
|
||||
}
|
||||
|
||||
angular.element($window).on('resize', function() {
|
||||
|
||||
if(!license_graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustGraphSize(license_graph, element);
|
||||
});
|
||||
|
||||
element.on('$destroy', function() {
|
||||
angular.element($window).off('resize', onResize);
|
||||
});
|
||||
|
||||
|
||||
|
||||
function createGraph(data, license) {
|
||||
//url = getBasePath('dashboard')+'graphs/';
|
||||
var graphData = [
|
||||
{ "key" : "Hosts" ,
|
||||
"color" : "#1778c3",
|
||||
"values": data.hosts
|
||||
},
|
||||
{ "key" : "License" ,
|
||||
"color" : "#171717",
|
||||
"values": data.hosts
|
||||
}
|
||||
];
|
||||
|
||||
graphData.map(function(series) {
|
||||
if(series.key==="Hosts"){
|
||||
series.values = series.values.map(function(d) {
|
||||
return {
|
||||
x: d[0],
|
||||
y: d[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
if(series.key==="License"){
|
||||
series.values = series.values.map(function(d) {
|
||||
return {
|
||||
x: d[0],
|
||||
y: license
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
return series;
|
||||
|
||||
});
|
||||
|
||||
var width = $('.graph-container').width(), // nv.utils.windowSize().width/3,
|
||||
height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5,
|
||||
license_graph = nv.models.lineChart()
|
||||
.margin({top: 15, right: 75, bottom: 40, left: 85})
|
||||
.x(function(d,i) { return i ;})
|
||||
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
|
||||
.transitionDuration(350) //how fast do you want the lines to transition?
|
||||
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
|
||||
.showYAxis(true) //Show the y-axis
|
||||
.showXAxis(true) //Show the x-axis
|
||||
;
|
||||
|
||||
license_graph.xAxis
|
||||
.axisLabel("Time")
|
||||
.tickFormat(function(d) {
|
||||
var dx = graphData[0].values[d] && graphData[0].values[d].x || 0;
|
||||
return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : '';
|
||||
});
|
||||
|
||||
license_graph.yAxis //Chart y-axis settings
|
||||
.axisLabel('Hosts')
|
||||
.tickFormat(d3.format('.f'));
|
||||
|
||||
d3.select(element.find('svg')[0])
|
||||
.datum(graphData).transition()
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.duration(500)
|
||||
.call(license_graph)
|
||||
.style({
|
||||
"font-family": 'Open Sans',
|
||||
"font-style": "normal",
|
||||
"font-weight":400,
|
||||
"src": "url(/static/fonts/OpenSans-Regular.ttf)"
|
||||
});
|
||||
|
||||
|
||||
scope.$emit('WidgetLoaded');
|
||||
|
||||
adjustGraphSize(license_graph, element);
|
||||
|
||||
return license_graph;
|
||||
|
||||
}
|
||||
}
|
||||
}]);
|
102
awx/ui/static/js/directives/host-status-graph.js
Normal file
102
awx/ui/static/js/directives/host-status-graph.js
Normal file
@ -0,0 +1,102 @@
|
||||
angular.module('DashboardGraphs')
|
||||
.directive('hostStatusGraph', ['$compile', '$window',
|
||||
function ($compile, $window) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: link,
|
||||
templateUrl: '/static/partials/host_status_graph.html'
|
||||
};
|
||||
|
||||
function link(scope, element, attr) {
|
||||
var host_pie_chart;
|
||||
|
||||
scope.$watch(attr.data, function(data) {
|
||||
if (data && data.hosts) {
|
||||
createGraph(data);
|
||||
}
|
||||
});
|
||||
|
||||
function adjustGraphSize() {
|
||||
|
||||
if (angular.isUndefined(host_pie_chart)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parentHeight = element.parent().parent().height();
|
||||
var toolbarHeight = element.find('.toolbar').height();
|
||||
var container = element.find('svg').parent();
|
||||
var margins = host_pie_chart.margin();
|
||||
|
||||
var newHeight = parentHeight - toolbarHeight - margins.bottom;
|
||||
|
||||
$(container).height(newHeight);
|
||||
|
||||
host_pie_chart.update();
|
||||
}
|
||||
|
||||
angular.element($window).on('resize', adjustGraphSize);
|
||||
|
||||
element.on('$destroy', function() {
|
||||
angular.element($window).off('resize', adjustGraphSize);
|
||||
});
|
||||
|
||||
function createGraph(data) {
|
||||
if(data.hosts.total+data.hosts.failed>0){
|
||||
data = [
|
||||
{ "label": "Successful",
|
||||
"color": "#00aa00",
|
||||
"value" : data.hosts.total
|
||||
} ,
|
||||
{ "label": "Failed",
|
||||
"color" : "#aa0000",
|
||||
"value" : data.hosts.failed
|
||||
}
|
||||
];
|
||||
|
||||
host_pie_chart = nv.models.pieChart()
|
||||
.margin({top: 5, right: 75, bottom: 25, left: 85})
|
||||
.x(function(d) { return d.label; })
|
||||
.y(function(d) { return d.value; })
|
||||
.showLabels(true)
|
||||
.labelThreshold(0.01)
|
||||
.tooltipContent(function(x, y) {
|
||||
return '<b>'+x+'</b>'+ '<p>' + Math.floor(y.replace(',','')) + ' Hosts ' + '</p>';
|
||||
})
|
||||
.color(['#00aa00', '#aa0000']);
|
||||
|
||||
host_pie_chart.pie.pieLabelsOutside(true).labelType("percent");
|
||||
|
||||
d3.select(element.find('svg')[0])
|
||||
.datum(data)
|
||||
.transition().duration(350)
|
||||
.call(host_pie_chart)
|
||||
.style({
|
||||
"font-family": 'Open Sans',
|
||||
"font-style": "normal",
|
||||
"font-weight":400,
|
||||
"src": "url(/static/fonts/OpenSans-Regular.ttf)"
|
||||
});
|
||||
|
||||
adjustGraphSize();
|
||||
return host_pie_chart;
|
||||
}
|
||||
else{
|
||||
// This should go in a template or something
|
||||
// but I'm at the end of a card and need to get this done.
|
||||
// We definitely need to refactor this, I'm letting
|
||||
// good enough be good enough for right now.
|
||||
var notFoundContainer = $('<div></div>');
|
||||
notFoundContainer.css({
|
||||
'text-align': 'center',
|
||||
'width': '100%',
|
||||
'padding-top': '2em'
|
||||
});
|
||||
|
||||
notFoundContainer.text('No host data');
|
||||
|
||||
element.find('svg').replaceWith(notFoundContainer);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}]);
|
120
awx/ui/static/js/directives/job-status-graph.js
Normal file
120
awx/ui/static/js/directives/job-status-graph.js
Normal file
@ -0,0 +1,120 @@
|
||||
angular.module('DashboardGraphs')
|
||||
.directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'adjustGraphSize', 'jobStatusGraphData',
|
||||
function ($rootScope, $compile , $location, $window, Wait, adjustGraphSize) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/static/partials/job_status_graph.html',
|
||||
link: link
|
||||
};
|
||||
|
||||
function link(scope, element, attr) {
|
||||
var job_type, job_status_chart = nv.models.lineChart();
|
||||
|
||||
scope.period="month";
|
||||
scope.jobType="all";
|
||||
|
||||
scope.$watch(attr.data, function(value) {
|
||||
if (value) {
|
||||
createGraph(value, scope.period, scope.jobType);
|
||||
}
|
||||
});
|
||||
|
||||
function createGraph(data, period, jobtype){
|
||||
|
||||
scope.period = period;
|
||||
scope.jobType = jobtype;
|
||||
|
||||
var timeFormat, graphData = [
|
||||
{ "color": "#00aa00",
|
||||
"key": "Successful",
|
||||
"values": data.jobs.successful
|
||||
},
|
||||
{ "key" : "Failed" ,
|
||||
"color" : "#aa0000",
|
||||
"values": data.jobs.failed
|
||||
}
|
||||
];
|
||||
|
||||
if(period==="day") {
|
||||
timeFormat="%H:%M";
|
||||
}
|
||||
else {
|
||||
timeFormat = '%m/%d';
|
||||
}
|
||||
graphData.map(function(series) {
|
||||
series.values = series.values.map(function(d) {
|
||||
return {
|
||||
x: d[0],
|
||||
y: d[1]
|
||||
};
|
||||
});
|
||||
return series;
|
||||
});
|
||||
|
||||
job_status_chart
|
||||
.margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room.
|
||||
.x(function(d,i) { return i; })
|
||||
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
|
||||
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
|
||||
.showYAxis(true) //Show the y-axis
|
||||
.showXAxis(true); //Show the x-axis
|
||||
|
||||
|
||||
job_status_chart.xAxis
|
||||
.axisLabel("Time")//.showMaxMin(true)
|
||||
.tickFormat(function(d) {
|
||||
var dx = graphData[0].values[d] && graphData[0].values[d].x || 0;
|
||||
return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : '';
|
||||
});
|
||||
|
||||
job_status_chart.yAxis //Chart y-axis settings
|
||||
.axisLabel('Jobs')
|
||||
.tickFormat(d3.format('.f'));
|
||||
|
||||
d3.select(element.find('svg')[0])
|
||||
.datum(graphData)
|
||||
.call(job_status_chart)
|
||||
.style({
|
||||
"font-family": 'Open Sans',
|
||||
"font-style": "normal",
|
||||
"font-weight":400,
|
||||
"src": "url(/static/fonts/OpenSans-Regular.ttf)"
|
||||
});
|
||||
|
||||
// when the Period drop down filter is used, create a new graph based on the
|
||||
d3.selectAll(element.find(".n"))
|
||||
.on("click", function() {
|
||||
period = this.getAttribute("id");
|
||||
$('#period-dropdown').replaceWith("<a id=\"period-dropdown\" role=\"button\" data-toggle=\"dropdown\" data-target=\"#\" href=\"/page.html\">"+this.text+"<span class=\"caret\"><span>\n");
|
||||
|
||||
createGraph(data, period, job_type);
|
||||
});
|
||||
|
||||
//On click, update with new data
|
||||
d3.selectAll(element.find(".m"))
|
||||
.on("click", function() {
|
||||
job_type = this.getAttribute("id");
|
||||
$('#type-dropdown').replaceWith("<a id=\"type-dropdown\" role=\"button\" data-toggle=\"dropdown\" data-target=\"#\" href=\"/page.html\">"+this.text+"<span class=\"caret\"><span>\n");
|
||||
|
||||
createGraph(data, period, job_type);
|
||||
});
|
||||
|
||||
adjustGraphSize(job_status_chart, element);
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
adjustGraphSize(job_status_chart, element);
|
||||
}
|
||||
|
||||
angular.element($window).on('resize', onResize);
|
||||
|
||||
element.on('$destroy', function() {
|
||||
angular.element($window).off('resize', onResize);
|
||||
});
|
||||
|
||||
if (scope.removeGraphDataReady) {
|
||||
scope.removeGraphDataReady();
|
||||
}
|
||||
|
||||
}
|
||||
}]);
|
@ -1074,8 +1074,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
|
||||
msg: 'Failed to retrieve inventory source. GET status: ' + status });
|
||||
});
|
||||
}
|
||||
else
|
||||
else {
|
||||
modal_scope.$emit('groupVariablesLoaded'); // JT-- "groupVariablesLoaded" is where the schedule info is loaded, so I make a call after the sources_scope.source has been loaded
|
||||
}
|
||||
});
|
||||
|
||||
if (sources_scope.removeScopeSourceTypeOptionsReady) {
|
||||
|
@ -1208,10 +1208,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
||||
keys;
|
||||
|
||||
function listSort(a,b) {
|
||||
if (parseInt(a,10) < parseInt(b,10))
|
||||
if (parseInt(a,10) < parseInt(b,10)) {
|
||||
return -1;
|
||||
if (parseInt(a,10) > parseInt(b,10))
|
||||
}
|
||||
if (parseInt(a,10) > parseInt(b,10)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1276,10 +1278,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
||||
idx, key, keys, newKeys, tasks, t;
|
||||
|
||||
function listSort(a,b) {
|
||||
if (parseInt(a,10) < parseInt(b,10))
|
||||
if (parseInt(a,10) < parseInt(b,10)) {
|
||||
return -1;
|
||||
if (parseInt(a,10) > parseInt(b,10))
|
||||
}
|
||||
if (parseInt(a,10) > parseInt(b,10)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1385,15 +1389,19 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
||||
keys = Object.keys(filteredListB);
|
||||
keys.sort(function compare(a, b) {
|
||||
if (filteredListB[a].name === filteredListB[b].name) {
|
||||
if (filteredListB[a].counter < filteredListB[b].counter)
|
||||
if (filteredListB[a].counter < filteredListB[b].counter) {
|
||||
return -1;
|
||||
if (filteredListB[a].counter >filteredListB[b].counter)
|
||||
}
|
||||
if (filteredListB[a].counter >filteredListB[b].counter) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (filteredListB[a].name < filteredListB[b].name)
|
||||
if (filteredListB[a].name < filteredListB[b].name) {
|
||||
return -1;
|
||||
if (filteredListB[a].name > filteredListB[b].name)
|
||||
}
|
||||
if (filteredListB[a].name > filteredListB[b].name) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
@ -1453,10 +1461,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
||||
keys = Object.keys(filteredListB);
|
||||
|
||||
keys.sort(function(a,b) {
|
||||
if (filteredListB[a].name > filteredListB[b].name)
|
||||
if (filteredListB[a].name > filteredListB[b].name) {
|
||||
return 1;
|
||||
if (filteredListB[a].name < filteredListB[b].name)
|
||||
}
|
||||
if (filteredListB[a].name < filteredListB[b].name) {
|
||||
return -1;
|
||||
}
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
});
|
||||
|
@ -111,7 +111,9 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
|
||||
if(scope.prompt_for_vars===false && scope.survey_enabled===true){
|
||||
scope.$emit('GetExtraVars');
|
||||
}
|
||||
else scope.$emit('BuildData');
|
||||
else {
|
||||
scope.$emit('BuildData');
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
@ -831,7 +833,9 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
|
||||
else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) {
|
||||
scope.$emit('PromptForSurvey', html, url);
|
||||
}
|
||||
else scope.$emit('StartPlaybookRun', url);
|
||||
else {
|
||||
scope.$emit('StartPlaybookRun', url);
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
@ -1021,4 +1025,4 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
|
@ -95,7 +95,9 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job
|
||||
if(scope.$parent.portalMode===true){
|
||||
$window.open('/#/jobs/' + job.id, '_blank');
|
||||
}
|
||||
else $location.url('/jobs/' + job.id);
|
||||
else {
|
||||
$location.url('/jobs/' + job.id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogViewer({
|
||||
|
@ -34,8 +34,8 @@ angular.module('PermissionsHelper', [])
|
||||
} else {
|
||||
scope.projectrequired = true;
|
||||
html = "<dl>\n" +
|
||||
"<dt>Create</dt>\n" +
|
||||
"<dd>Allow the user or team to create job templates. This implies that they have the Run and Check permissions.</dd>\n" +
|
||||
"<dt>Create</dt>\n" +
|
||||
"<dd>Allow the user or team to create job templates. This implies that they have the Run and Check permissions.</dd>\n" +
|
||||
"<dt>Run</dt>\n" +
|
||||
"<dd>Allow the user or team to run a job template from the project against the inventory. In Run mode modules will " +
|
||||
"be executed, and changes to the inventory will occur.</dd>\n" +
|
||||
|
@ -73,7 +73,9 @@ angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper',
|
||||
if(scope.can_edit === false){
|
||||
$('#survey-save-button').attr('disabled', "disabled");
|
||||
}
|
||||
else $('#survey-save-button').attr('ng-disabled', "survey_questions.length<1 ");
|
||||
else {
|
||||
$('#survey-save-button').attr('ng-disabled', "survey_questions.length<1 ");
|
||||
}
|
||||
element = angular.element(document.getElementById('survey-save-button'));
|
||||
$compile(element)(scope);
|
||||
|
||||
@ -513,7 +515,9 @@ angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper',
|
||||
if(scope.mode === 'add'){
|
||||
questions = [];
|
||||
}
|
||||
else scope.survey_questions = [];
|
||||
else {
|
||||
scope.survey_questions = [];
|
||||
}
|
||||
$(me).dialog('close');
|
||||
};
|
||||
|
||||
|
90
awx/ui/static/js/services/adjust-graph-size.js
Normal file
90
awx/ui/static/js/services/adjust-graph-size.js
Normal file
@ -0,0 +1,90 @@
|
||||
angular.module('DashboardGraphs').
|
||||
factory('adjustGraphSize', function() {
|
||||
|
||||
|
||||
// Adjusts the size of graphs based on the current height
|
||||
// of the outer parent (see auto-size-module directive).
|
||||
//
|
||||
// Since the graph's svg element is set to width & height of 100%,
|
||||
// it will automatically size itself when the size of its container
|
||||
// changes. Since boxes in HTML automatically fill the width of their
|
||||
// parent, we don't have to change the container's width. However,
|
||||
// since the makers HTML never heard of vertical rhythm,
|
||||
// we have to manually set a new height on the container.
|
||||
//
|
||||
// ## Calculating the container's new height
|
||||
//
|
||||
// newHeight is the height we assign to the graph's immediate parent.
|
||||
// This is calculated as the height of the graph-container (the
|
||||
// outer parent), offset by the height of the toolbar row
|
||||
// (the contains the title and/or any filters) and the
|
||||
// bottom margin.
|
||||
//
|
||||
// ## Responsive Graph Stuff
|
||||
//
|
||||
// Letting the svg element automatically scale only solves part of
|
||||
// the responsive graph problem. d3 draws graphs as paths, with static
|
||||
// positioning of all elements. Therefore, we need to tell the graph how
|
||||
// to adjust itself so that it can resize properly.
|
||||
//
|
||||
// ### Resizing the axes
|
||||
//
|
||||
// First we get the width & height of the chart after it has been modified
|
||||
// by setting the height on its parent (see Calculating the New Container's
|
||||
// Height above). Note that we need to offset the width/height by the margins
|
||||
// to make sure we keep all the spacing intact.
|
||||
//
|
||||
// Next, we update the range for x & y to take the new width & height into
|
||||
// account. d3 uses this range to map domain values (the actual data) onto
|
||||
// pixels.
|
||||
//
|
||||
// After that we adjust the number of ticks on the axes. This makes sure we
|
||||
// will never have overlapping ticks. If that does become a problem, try
|
||||
// changing the divisor in the calculations to a different number until you
|
||||
// find something that helps. For example, (width / 75) should make the x
|
||||
// axis only ever display 1 tick per every 75 pixels.
|
||||
//
|
||||
// ### Redrawing the line
|
||||
//
|
||||
// Since this is a line graph, now that we've changed the range & ticks,
|
||||
// we need to instruct d3 to repaint (redraw) the actual lines representing
|
||||
// the data. We do this by setting the "d" attribute of the path element
|
||||
// that represents the line to the line function on the chart model. This
|
||||
// function triggers the mapping of domain to range, and plots the chart.
|
||||
// Calling chartModel.update() at the end instructs nv to process our changes.
|
||||
//
|
||||
return function adjustGraphSize(chartModel, element) {
|
||||
var parentHeight = element.parent().parent().height();
|
||||
var toolbarHeight = element.find('.toolbar').height();
|
||||
var container = element.find('svg').parent();
|
||||
var margins = chartModel.margin();
|
||||
|
||||
var newHeight = parentHeight - toolbarHeight - margins.bottom;
|
||||
|
||||
$(container).height(newHeight);
|
||||
|
||||
var graph = d3.select(element.find('svg')[0]);
|
||||
var width = parseInt(graph.style('width')) - margins.left - margins.right;
|
||||
var height = parseInt(graph.style('height')) - margins.top - margins.bottom;
|
||||
|
||||
chartModel.xRange([0, width]);
|
||||
chartModel.yRange([height, 0]);
|
||||
|
||||
chartModel.xAxis.ticks(Math.max(width / 75, 2));
|
||||
chartModel.yAxis.ticks(Math.max(height / 50, 2));
|
||||
|
||||
if (height < 160) {
|
||||
graph.select('.y.nv-axis').select('.domain').style('display', 'none');
|
||||
graph.select('.y.nv-axis').select('.domain').style('display', 'initial');
|
||||
}
|
||||
|
||||
graph.select('.x.nv-axis')
|
||||
.attr('transform', 'translate(0, ' + height + ')')
|
||||
.call(chartModel.xAxis);
|
||||
|
||||
graph.selectAll('.line')
|
||||
.attr('d', chartModel.lines);
|
||||
|
||||
chartModel.update();
|
||||
};
|
||||
});
|
1
awx/ui/static/js/services/data-services.js
Normal file
1
awx/ui/static/js/services/data-services.js
Normal file
@ -0,0 +1 @@
|
||||
angular.module('DataServices', []);
|
47
awx/ui/static/js/services/host-count-graph-data.js
Normal file
47
awx/ui/static/js/services/host-count-graph-data.js
Normal file
@ -0,0 +1,47 @@
|
||||
angular.module('DataServices')
|
||||
.service('hostCountGraphData',
|
||||
["Rest",
|
||||
"GetBasePath",
|
||||
"ProcessErrors",
|
||||
"$q",
|
||||
HostCountGraphData]);
|
||||
|
||||
function HostCountGraphData(Rest, getBasePath, processErrors, $q) {
|
||||
|
||||
function pluck(property, promise) {
|
||||
return promise.then(function(value) {
|
||||
return value[property];
|
||||
});
|
||||
}
|
||||
|
||||
function getLicenseData() {
|
||||
var url = getBasePath('config');
|
||||
Rest.setUrl(url);
|
||||
return Rest.get()
|
||||
.then(function (data){
|
||||
var license = data.data.license_info.instance_count;
|
||||
return license;
|
||||
});
|
||||
}
|
||||
|
||||
function getHostData() {
|
||||
var url = getBasePath('dashboard')+'graphs/inventory/';
|
||||
Rest.setUrl(url);
|
||||
return pluck('data', Rest.get());
|
||||
}
|
||||
|
||||
return {
|
||||
get: function() {
|
||||
return $q.all({
|
||||
license: getLicenseData(),
|
||||
hosts: getHostData()
|
||||
}).catch(function (response) {
|
||||
var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status;
|
||||
processErrors(null, response.data, response.status, null, { hdr: 'Error!',
|
||||
msg: errorMessage
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
59
awx/ui/static/js/services/job-status-graph-data.js
Normal file
59
awx/ui/static/js/services/job-status-graph-data.js
Normal file
@ -0,0 +1,59 @@
|
||||
angular.module('DataServices')
|
||||
.service('jobStatusGraphData',
|
||||
["Rest",
|
||||
"GetBasePath",
|
||||
"ProcessErrors",
|
||||
"$rootScope",
|
||||
JobStatusGraphData]);
|
||||
|
||||
function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope) {
|
||||
|
||||
function pluck(property, promise) {
|
||||
return promise.then(function(value) {
|
||||
return value[property];
|
||||
});
|
||||
}
|
||||
|
||||
function getData(period, jobType) {
|
||||
var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType;
|
||||
Rest.setUrl(url);
|
||||
var result = Rest.get()
|
||||
.catch(function(response) {
|
||||
var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status;
|
||||
|
||||
processErrors(null,
|
||||
response.data,
|
||||
response.status,
|
||||
null, {
|
||||
hdr: 'Error!',
|
||||
msg: errorMessage
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
||||
return pluck('data', result);
|
||||
}
|
||||
|
||||
return {
|
||||
destroyWatcher: angular.noop,
|
||||
setupWatcher: function(period, jobType) {
|
||||
this.destroyWatcher =
|
||||
$rootScope.$on('JobStatusChange', function() {
|
||||
getData(period, jobType).then(function(result) {
|
||||
$rootScope.
|
||||
$broadcast('DataReceived:JobStatusGraph',
|
||||
result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
},
|
||||
get: function(period, jobType) {
|
||||
|
||||
this.destroyWatcher();
|
||||
this.setupWatcher(period, jobType);
|
||||
|
||||
return getData(period, jobType);
|
||||
|
||||
}
|
||||
};
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
/*********************************************
|
||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:HostGraph
|
||||
* @description
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('HostGraphWidget', ['RestServices', 'Utilities'])
|
||||
.factory('HostGraph', ['$rootScope', '$compile', '$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
|
||||
function ($rootScope, $compile, $location, Rest, GetBasePath, ProcessErrors) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
target = params.target,
|
||||
html, element, url, license, license_graph;
|
||||
|
||||
|
||||
// html = "<div class=\"graph-container\">\n";
|
||||
html ="<div class=\"row\">\n";
|
||||
html += "<div class=\"h6 col-xs-8 text-center\"><b>Host Count</b></div>\n";
|
||||
html += "</div>\n";
|
||||
html +="<div class=\"row\">\n";
|
||||
html += "<div class=\"host-count-graph\"><svg></svg></div>\n";
|
||||
|
||||
// html += "</div>\n";
|
||||
|
||||
|
||||
|
||||
element = angular.element(document.getElementById(target));
|
||||
element.html(html);
|
||||
$compile(element)(scope);
|
||||
|
||||
url = GetBasePath('config');
|
||||
|
||||
if (scope.removeResizeHostGraph) {
|
||||
scope.removeResizeHostGraph();
|
||||
}
|
||||
scope.removeResizeHostGraph= scope.$on('ResizeHostGraph', function () {
|
||||
if($(window).width()<500){
|
||||
$('.graph-container').height(300);
|
||||
}
|
||||
else{
|
||||
var winHeight = $(window).height(),
|
||||
available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120;
|
||||
$('.graph-container').height(available_height/2);
|
||||
license_graph.update();
|
||||
}
|
||||
});
|
||||
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.success(function (data){
|
||||
license = data.license_info.instance_count;
|
||||
scope.$emit('licenseCountReady', license);
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get: ' + url + ' GET returned: ' + status });
|
||||
});
|
||||
|
||||
if (scope.removeLicenseCountReady) {
|
||||
scope.removeLicenseCountReady();
|
||||
}
|
||||
scope.removeLicenseCountReady = scope.$on('licenseCountReady', function (e, license) {
|
||||
url = GetBasePath('dashboard')+'graphs/inventory/';
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
scope.$emit('hostDataReady', data, license);
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get: ' + url + ' GET returned: ' + status });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if (scope.removeHostDataReady) {
|
||||
scope.removeHostDataReady();
|
||||
}
|
||||
scope.removeHostDataReady = scope.$on('hostDataReady', function (e, data, license) {
|
||||
|
||||
//url = GetBasePath('dashboard')+'graphs/';
|
||||
var graphData = [
|
||||
{
|
||||
"key" : "Hosts" ,
|
||||
"color" : "#1778c3",
|
||||
"values": data.hosts
|
||||
},
|
||||
{
|
||||
"key" : "License" ,
|
||||
"color" : "#171717",
|
||||
"values": data.hosts
|
||||
}
|
||||
];
|
||||
|
||||
graphData.map(function(series) {
|
||||
if(series.key==="Hosts"){
|
||||
series.values = series.values.map(function(d) {
|
||||
return {
|
||||
x: d[0],
|
||||
y: d[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
if(series.key==="License"){
|
||||
series.values = series.values.map(function(d) {
|
||||
return {
|
||||
x: d[0],
|
||||
y: license
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
return series;
|
||||
|
||||
});
|
||||
|
||||
nv.addGraph({
|
||||
generate: function() {
|
||||
var width = $('.graph-container').width(), // nv.utils.windowSize().width/3,
|
||||
height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5,
|
||||
license_graph = nv.models.lineChart()
|
||||
.margin({top: 15, right: 75, bottom: 40, left: 85})
|
||||
.x(function(d,i) { return i ;})
|
||||
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
|
||||
.transitionDuration(350) //how fast do you want the lines to transition?
|
||||
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
|
||||
.showYAxis(true) //Show the y-axis
|
||||
.showXAxis(true) //Show the x-axis
|
||||
;
|
||||
|
||||
license_graph.xAxis
|
||||
.axisLabel("Time")
|
||||
.tickFormat(function(d) {
|
||||
var dx = graphData[0].values[d] && graphData[0].values[d].x || 0;
|
||||
return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : '';
|
||||
});
|
||||
|
||||
license_graph.yAxis //Chart y-axis settings
|
||||
.axisLabel('Hosts')
|
||||
.tickFormat(d3.format('.f'));
|
||||
|
||||
d3.select('.host-count-graph svg')
|
||||
.datum(graphData).transition()
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.duration(500)
|
||||
.call(license_graph)
|
||||
.style({
|
||||
// 'width': width,
|
||||
// 'height': height,
|
||||
"font-family": 'Open Sans',
|
||||
"font-style": "normal",
|
||||
"font-weight":400,
|
||||
"src": "url(/static/fonts/OpenSans-Regular.ttf)"
|
||||
});
|
||||
|
||||
|
||||
// nv.utils.windowResize(license_graph.update);
|
||||
scope.$emit('WidgetLoaded');
|
||||
return license_graph;
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
//});
|
||||
});
|
||||
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
]);
|
@ -1,128 +0,0 @@
|
||||
/*********************************************
|
||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:HostPieChart
|
||||
* @description
|
||||
* HostPieChart.js
|
||||
*
|
||||
* file for the host status pie chart
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('HostPieChartWidget', ['RestServices', 'Utilities'])
|
||||
.factory('HostPieChart', ['$rootScope', '$compile',
|
||||
//'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
|
||||
function ($rootScope, $compile){
|
||||
//, Rest, GetBasePath, ProcessErrors) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
target = params.target,
|
||||
dashboard = params.dashboard,
|
||||
html, element, data,
|
||||
canvas, context, winHeight, available_height, host_pie_chart;
|
||||
|
||||
// html = "<div class=\"graph-container\">\n";
|
||||
|
||||
html ="<div class=\"row\">\n";
|
||||
html += "<div id=\"job-status-title\" class=\"h6 col-xs-8 text-center\"><b>Host Status</b></div>\n";
|
||||
html += "</div>\n";
|
||||
|
||||
html +="<div class=\"row\">\n";
|
||||
html += "<div class=\"host-pie-chart text-center\"><svg></svg></div>\n";
|
||||
html += "</div>\n";
|
||||
|
||||
// html += "</div>\n";
|
||||
|
||||
element = angular.element(document.getElementById(target));
|
||||
element.html(html);
|
||||
$compile(element)(scope);
|
||||
|
||||
if (scope.removeResizeHostPieGraph) {
|
||||
scope.removeResizeHostPieGraph();
|
||||
}
|
||||
scope.removeResizeHostPieGraph= scope.$on('ResizeHostPieGraph', function () {
|
||||
if($(window).width()<500){
|
||||
$('.graph-container').height(300);
|
||||
}
|
||||
else{
|
||||
var winHeight = $(window).height(),
|
||||
available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120;
|
||||
$('.graph-container').height(available_height/2);
|
||||
if(host_pie_chart){
|
||||
host_pie_chart.update();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(dashboard.hosts.total+dashboard.hosts.failed>0){
|
||||
data = [
|
||||
{
|
||||
"label": "Successful",
|
||||
"color": "#00aa00",
|
||||
"value" : dashboard.hosts.total
|
||||
} ,
|
||||
{
|
||||
"label": "Failed",
|
||||
"color" : "#aa0000",
|
||||
"value" : dashboard.hosts.failed
|
||||
}
|
||||
];
|
||||
|
||||
nv.addGraph(function() {
|
||||
var width = $('.graph-container').width(), // nv.utils.windowSize().width/3,
|
||||
height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5,
|
||||
host_pie_chart = nv.models.pieChart()
|
||||
.margin({top: 5, right: 75, bottom: 40, left: 85})
|
||||
.x(function(d) { return d.label; })
|
||||
.y(function(d) { return d.value; })
|
||||
.showLabels(true)
|
||||
.labelThreshold(0.01)
|
||||
.tooltipContent(function(x, y) {
|
||||
return '<b>'+x+'</b>'+ '<p>' + Math.floor(y.replace(',','')) + ' Hosts ' + '</p>';
|
||||
})
|
||||
.color(['#00aa00', '#aa0000']);
|
||||
|
||||
host_pie_chart.pie.pieLabelsOutside(true).labelType("percent");
|
||||
|
||||
d3.select(".host-pie-chart svg")
|
||||
.datum(data)
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.transition().duration(350)
|
||||
.call(host_pie_chart)
|
||||
.style({
|
||||
"font-family": 'Open Sans',
|
||||
"font-style": "normal",
|
||||
"font-weight":400,
|
||||
"src": "url(/static/fonts/OpenSans-Regular.ttf)"
|
||||
});
|
||||
// nv.utils.windowResize(host_pie_chart.update);
|
||||
scope.$emit('WidgetLoaded');
|
||||
return host_pie_chart;
|
||||
});
|
||||
}
|
||||
else{
|
||||
winHeight = $(window).height();
|
||||
available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120;
|
||||
$('.graph-container:eq(1)').height(available_height/2);
|
||||
$('.host-pie-chart svg').replaceWith('<canvas id="circlecanvas" width="120" height="120"></canvas>');
|
||||
|
||||
canvas = document.getElementById("circlecanvas");
|
||||
context = canvas.getContext("2d");
|
||||
context.arc(55, 55, 50, 0, Math.PI * 2, false);
|
||||
context.lineWidth = 1;
|
||||
context.strokeStyle = '#1778c3';
|
||||
context.stroke();
|
||||
context.font = "12px Open Sans";
|
||||
context.fillText("No Host data",18,55);
|
||||
|
||||
scope.$emit('WidgetLoaded');
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
@ -1,219 +0,0 @@
|
||||
/*********************************************
|
||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:JobStatusGraph
|
||||
* @description
|
||||
*/
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities'])
|
||||
.factory('JobStatusGraph', ['$rootScope', '$compile', '$location' , 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
|
||||
function ($rootScope, $compile , $location, Rest, GetBasePath, ProcessErrors) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
target = params.target,
|
||||
// dashboard = params.dashboard,
|
||||
html, element, url, job_status_chart,
|
||||
period="month",
|
||||
job_type="all";
|
||||
|
||||
// html = "<div class=\"graph-container\">\n";
|
||||
|
||||
html = "<div class=\"row\">\n";
|
||||
html += "<div id=\"job-status-title\" class=\"h6 col-xs-2 col-sm-3 col-lg-4 text-center\"><b>Job Status</b></div>\n"; // for All Jobs, Past Month
|
||||
|
||||
html += "<div class=\"h6 col-xs-5 col-sm-5 col-lg-4\">\n";
|
||||
html += "<div class=\"dropdown\">\n";
|
||||
html += "Job Type: <a id=\"type-dropdown\" role=\"button\" data-toggle=\"dropdown\" data-target=\"#\" href=\"/page.html\">\n";
|
||||
html += "All<span class=\"caret\"></span>\n";
|
||||
html += " </a>\n";
|
||||
|
||||
html += "<ul class=\"dropdown-menu\" role=\"menu\" aria-labelledby=\"type-dropdown\">\n";
|
||||
html += "<li><a class=\"m\" id=\"all\">All</a></li>\n";
|
||||
html += "<li><a class=\"m\" id=\"inv_sync\">Inventory Sync</a></li>\n";
|
||||
html += "<li><a class=\"m\" id=\"scm_update\">SCM Update</a></li>\n";
|
||||
html += "<li><a class=\"m\" id=\"playbook_run\">Playbook Run</a></li>\n";
|
||||
html += "</ul>\n";
|
||||
html += "</div>\n";
|
||||
|
||||
html += "</div>\n"; //end of filter div
|
||||
|
||||
html += "<div class=\"h6 col-xs-5 col-sm-4 col-lg-4\">\n";
|
||||
html += "<div class=\"dropdown\">\n";
|
||||
html += "Period: <a id=\"period-dropdown\" role=\"button\" data-toggle=\"dropdown\" data-target=\"#\" href=\"/page.html\">\n";
|
||||
html += "Past Month<span class=\"caret\"></span>\n";
|
||||
html += " </a>\n";
|
||||
|
||||
html += "<ul class=\"dropdown-menu\" role=\"menu\" aria-labelledby=\"period-dropdown\">\n";
|
||||
html += "<li><a class=\"n\" id=\"day\" >Past 24 Hours </a></li>\n";
|
||||
html += "<li><a class=\"n\" id=\"week\">Past Week</a></li>\n";
|
||||
html += "<li><a class=\"n\" id=\"month\">Past Month</a></li>\n";
|
||||
html += "</ul>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n"; //end of filter div
|
||||
|
||||
html += "</div>\n"; // end of row
|
||||
|
||||
html +="<div class=\"row\">\n";
|
||||
html += "<div class=\"job-status-graph\"><svg></svg></div>\n";
|
||||
html += "</div>\n";
|
||||
|
||||
// html += "</div>\n";
|
||||
|
||||
function createGraph(){
|
||||
|
||||
url = GetBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+job_type;
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.success(function (data){
|
||||
scope.$emit('graphDataReady', data);
|
||||
return job_type, period;
|
||||
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get: ' + url + ' GET returned: ' + status });
|
||||
});
|
||||
}
|
||||
|
||||
if ($rootScope.removeReloadJobStatusGraph) {
|
||||
$rootScope.removeReloadJobStatusGraph();
|
||||
}
|
||||
$rootScope.removeReloadJobStatusGraph = $rootScope.$on('ReloadJobStatusGraph', function() {
|
||||
createGraph();
|
||||
});
|
||||
|
||||
element = angular.element(document.getElementById(target));
|
||||
element.html(html);
|
||||
$compile(element)(scope);
|
||||
|
||||
createGraph();
|
||||
|
||||
if (scope.removeResizeJobGraph) {
|
||||
scope.removeResizeJobGraph();
|
||||
}
|
||||
scope.removeResizeJobGraph= scope.$on('ResizeJobGraph', function () {
|
||||
if($(window).width()<500){
|
||||
$('.graph-container').height(300);
|
||||
}
|
||||
else{
|
||||
var winHeight = $(window).height(),
|
||||
available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120;
|
||||
$('.graph-container').height(available_height/2);
|
||||
job_status_chart.update();
|
||||
}
|
||||
});
|
||||
|
||||
if (scope.removeGraphDataReady) {
|
||||
scope.removeGraphDataReady();
|
||||
}
|
||||
scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data) {
|
||||
|
||||
|
||||
var timeFormat, graphData = [
|
||||
{
|
||||
"color": "#00aa00",
|
||||
"key": "Successful",
|
||||
"values": data.jobs.successful
|
||||
},
|
||||
{
|
||||
"key" : "Failed" ,
|
||||
"color" : "#aa0000",
|
||||
"values": data.jobs.failed
|
||||
}
|
||||
];
|
||||
|
||||
if(period==="day"){
|
||||
timeFormat="%H:%M";
|
||||
}
|
||||
else {
|
||||
timeFormat = '%m/%d';
|
||||
}
|
||||
graphData.map(function(series) {
|
||||
series.values = series.values.map(function(d) {
|
||||
return {
|
||||
x: d[0],
|
||||
y: d[1]
|
||||
};
|
||||
});
|
||||
return series;
|
||||
});
|
||||
|
||||
nv.addGraph({
|
||||
generate: function() {
|
||||
var width = $('.graph-container').width(), // nv.utils.windowSize().width/3,
|
||||
height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5,
|
||||
job_status_chart = nv.models.lineChart()
|
||||
.margin({top: 5, right: 75, bottom: 80, left: 85}) //Adjust chart margins to give the x-axis some breathing room.
|
||||
.x(function(d,i) { return i; })
|
||||
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
|
||||
.transitionDuration(350) //how fast do you want the lines to transition?
|
||||
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
|
||||
.showYAxis(true) //Show the y-axis
|
||||
.showXAxis(true) //Show the x-axis
|
||||
// .width(width)
|
||||
// .height(height)
|
||||
;
|
||||
|
||||
job_status_chart.xAxis
|
||||
.axisLabel("Time")//.showMaxMin(true)
|
||||
.tickFormat(function(d) {
|
||||
var dx = graphData[0].values[d] && graphData[0].values[d].x || 0;
|
||||
return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : '';
|
||||
});
|
||||
|
||||
job_status_chart.yAxis //Chart y-axis settings
|
||||
.axisLabel('Jobs')
|
||||
.tickFormat(d3.format('.f'));
|
||||
|
||||
d3.select('.job-status-graph svg')
|
||||
.datum(graphData).transition()
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.duration(1000)
|
||||
.call(job_status_chart)
|
||||
.style({
|
||||
// 'width': width,
|
||||
// 'height': height,
|
||||
"font-family": 'Open Sans',
|
||||
"font-style": "normal",
|
||||
"font-weight":400,
|
||||
"src": "url(/static/fonts/OpenSans-Regular.ttf)"
|
||||
});
|
||||
|
||||
// when the Period drop down filter is used, create a new graph based on the
|
||||
d3.selectAll(".n")
|
||||
.on("click", function() {
|
||||
period = this.getAttribute("id");
|
||||
$('#period-dropdown').replaceWith("<a id=\"period-dropdown\" role=\"button\" data-toggle=\"dropdown\" data-target=\"#\" href=\"/page.html\">"+this.text+"<span class=\"caret\"><span>\n");
|
||||
|
||||
createGraph();
|
||||
});
|
||||
|
||||
//On click, update with new data
|
||||
d3.selectAll(".m")
|
||||
.on("click", function() {
|
||||
job_type = this.getAttribute("id");
|
||||
$('#type-dropdown').replaceWith("<a id=\"type-dropdown\" role=\"button\" data-toggle=\"dropdown\" data-target=\"#\" href=\"/page.html\">"+this.text+"<span class=\"caret\"><span>\n");
|
||||
|
||||
createGraph();
|
||||
});
|
||||
|
||||
scope.$emit('WidgetLoaded');
|
||||
return job_status_chart;
|
||||
|
||||
},
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
]);
|
@ -58,7 +58,7 @@
|
||||
@import "breadcrumbs.less";
|
||||
@import "stdout.less";
|
||||
@import "lists.less";
|
||||
@import "new-dashboard.less";
|
||||
@import "dashboard.less";
|
||||
@import "jPushMenu.less";
|
||||
@import "survey-maker.less";
|
||||
@import "portal.less";
|
||||
|
@ -1,13 +1,26 @@
|
||||
/*********************************************
|
||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||
*
|
||||
* new-dashboard.css
|
||||
* dashboard.css
|
||||
*
|
||||
* custom styles for the new dashboard
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
.graph-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.graph {
|
||||
background-color: white;
|
||||
// @include transition(width 2s ease-in-out, height 2s ease-in-out);
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.job-status-graph, .host-count-graph{
|
||||
font: 10px sans-serif;
|
||||
@ -99,4 +112,4 @@ due to the login screen showing on top of the dashboard, we're hiding the border
|
||||
|
||||
.m, .n{
|
||||
cursor:pointer;
|
||||
}
|
||||
}
|
@ -49,7 +49,6 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
$(window).unbind('resize');
|
||||
};
|
||||
}])
|
||||
|
||||
@ -859,4 +858,4 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
|
||||
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
|
@ -100,8 +100,9 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
|
||||
scope.update = function(){
|
||||
var val = [];
|
||||
angular.forEach(scope.cbModel, function(v,k){
|
||||
if (v)
|
||||
if (v) {
|
||||
val.push(k);
|
||||
}
|
||||
});
|
||||
if (val.length>0){
|
||||
scope.ngModel.value = val;
|
||||
|
@ -223,8 +223,6 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
$(window).unbind('resize');
|
||||
|
||||
// Prepend an asterisk to required field label
|
||||
$('.form-control[required], input[type="radio"][required]').each(function () {
|
||||
var label, span;
|
||||
@ -1656,4 +1654,4 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
|
@ -186,7 +186,6 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
|
||||
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
|
||||
$(this).remove();
|
||||
});
|
||||
$(window).unbind('resize');
|
||||
|
||||
try {
|
||||
$('#help-modal').empty().dialog('destroy');
|
||||
@ -426,10 +425,11 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
|
||||
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-value=\"1\" " +
|
||||
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
|
||||
}
|
||||
else // its assumed that options.input_type = checkbox
|
||||
else { // its assumed that options.input_type = checkbox
|
||||
html += "<td><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
|
||||
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " +
|
||||
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
|
||||
}
|
||||
} else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) {
|
||||
|
||||
// Row level actions
|
||||
@ -582,4 +582,4 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
|
||||
return html;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
}]);
|
||||
|
@ -63,14 +63,16 @@ angular.module('PromptDialog', ['Utilities'])
|
||||
|
||||
focus = function() {
|
||||
var focusableElement = focusableChildren[currentIndex];
|
||||
if (focusableElement)
|
||||
if (focusableElement) {
|
||||
focusableElement.focus();
|
||||
}
|
||||
};
|
||||
|
||||
focusPrevious = function () {
|
||||
currentIndex--;
|
||||
if (currentIndex < 0)
|
||||
if (currentIndex < 0) {
|
||||
currentIndex = numElements - 1;
|
||||
}
|
||||
|
||||
focus();
|
||||
|
||||
@ -79,8 +81,9 @@ angular.module('PromptDialog', ['Utilities'])
|
||||
|
||||
focusNext = function () {
|
||||
currentIndex++;
|
||||
if (currentIndex >= numElements)
|
||||
if (currentIndex >= numElements) {
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
focus();
|
||||
|
||||
@ -118,4 +121,4 @@ angular.module('PromptDialog', ['Utilities'])
|
||||
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
<div class="tab-pane" id="home">
|
||||
<div class="tab-pane" id="home" ng-if="current_user">
|
||||
<div ng-cloak id="htmlTemplate" style="padding:10px">
|
||||
<div id="refresh-row" class="row">
|
||||
<div class="col-lg-12">
|
||||
@ -12,12 +12,24 @@
|
||||
<div id="dash-counts" class="col-sm-12 col-xs-12"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="left-side col-sm-6 col-xs-12"><div id="dash-job-status-graph" class="graph-container"></div></div>
|
||||
<div class="right-side col-sm-6 col-xs-12"><div id="dash-host-status-graph" class="graph-container"></div></div>
|
||||
<div class="left-side col-sm-6 col-xs-12">
|
||||
<div id="dash-job-status-graph" auto-size-module class="graph-container">
|
||||
<job-status-graph data="graphData.jobStatus" period="month" job-type="all"></job-status-graph>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-side col-sm-6 col-xs-12">
|
||||
<div id="dash-host-status-graph" auto-size-module class="graph-container">
|
||||
<host-status-graph data="dashboardData"></host-status-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="dash-jobs-list" class="left-side col-sm-6 col-xs-12"></div>
|
||||
<div class="right-side col-sm-6 col-xs-12"><div id="dash-host-count-graph" class="graph-container"></div></div>
|
||||
<div class="right-side col-sm-6 col-xs-12">
|
||||
<div id="dash-host-count-graph" auto-size-module class="graph-container">
|
||||
<host-count-graph ng-if="user_is_superuser" data="graphData.hostCounts"></host-count-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
12
awx/ui/static/partials/host_count_graph.html
Normal file
12
awx/ui/static/partials/host_count_graph.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="graph-wrapper">
|
||||
<div class="clearfix toolbar">
|
||||
<div class="h6 pull-left">
|
||||
<b>Host Count</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="graph">
|
||||
<svg width="100%" height="100%"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
12
awx/ui/static/partials/host_status_graph.html
Normal file
12
awx/ui/static/partials/host_status_graph.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="graph-wrapper">
|
||||
<div class="clearfix toolbar">
|
||||
<div id="job-status-title" class="h6 pull-left">
|
||||
<b>Host Status</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="graph">
|
||||
<svg width="100%" height="100%" preserveAspectRatio="xMinYMin"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
39
awx/ui/static/partials/job_status_graph.html
Normal file
39
awx/ui/static/partials/job_status_graph.html
Normal file
@ -0,0 +1,39 @@
|
||||
<div class="graph-wrapper job-status-graph">
|
||||
<div class="clearfix toolbar">
|
||||
<div id="job-status-title" class="h6 pull-left">
|
||||
<b>Job Status</b>
|
||||
</div>
|
||||
|
||||
<div class="h6 dropdown pull-right">
|
||||
Period: <a id="period-dropdown" role="button" data-toggle="dropdown" data-target="#" href="/page.html">
|
||||
Past Month<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="period-dropdown">
|
||||
<li><a class="n" id="day" >Past 24 Hours </a></li>
|
||||
<li><a class="n" id="week">Past Week</a></li>
|
||||
<li><a class="n" id="month">Past Month</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="h6 dropdown pull-right" style="padding-right: .5em;">
|
||||
Job Type: <a id="type-dropdown" role="button" data-toggle="dropdown" data-target="#" href="/page.html">
|
||||
All<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="type-dropdown">
|
||||
<li><a class="m" id="all">All</a></li>
|
||||
<li><a class="m" id="inv_sync">Inventory Sync</a></li>
|
||||
<li><a class="m" id="scm_update">SCM Update</a></li>
|
||||
<li><a class="m" id="playbook_run">Playbook Run</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="graph">
|
||||
<svg width="100%" height="100%"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -81,6 +81,15 @@
|
||||
<script src="{{ STATIC_URL }}js/controllers/Permissions.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/Schedules.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/Sockets.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/services/data-services.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/services/host-count-graph-data.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/services/job-status-graph-data.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/directives/dashboard-graphs.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/services/adjust-graph-size.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/directives/auto-size-module.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/directives/job-status-graph.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/directives/host-status-graph.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/directives/host-count-graph.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/Users.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/Organizations.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/Inventories.js"></script>
|
||||
@ -170,9 +179,6 @@
|
||||
<script src="{{ STATIC_URL }}js/helpers/AboutAnsible.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/JobStatus.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/DashboardCounts.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/HostPieChart.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/HostGraph.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/JobStatusGraph.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/DashboardJobs.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/PortalJobs.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/widgets/Stream.js"></script>
|
||||
|
@ -10,7 +10,8 @@ module.exports = function(config) {
|
||||
conf.files = conf.files.concat([
|
||||
'../static/lib/angular-mocks/angular-mocks.js',
|
||||
'../../../node_modules/ng-midway-tester/src/ngMidwayTester.js',
|
||||
'./unit/*'
|
||||
'./unit/*',
|
||||
'./unit/**/*'
|
||||
]);
|
||||
|
||||
// level of logging
|
||||
|
@ -1,23 +1,23 @@
|
||||
// Karma configuration
|
||||
// Generated on Mon Aug 04 2014 21:17:04 GMT-0400 (EDT)
|
||||
|
||||
module.exports = function() {
|
||||
return {
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '',
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['jasmine'],
|
||||
frameworks: ['mocha', 'chai', 'sinon-chai', 'chai-as-promised'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'../static/lib/jquery/dist/jquery.min.js',
|
||||
'../static/lib/angular/angular.min.js',
|
||||
'../static/lib/angular-route/angular-route.min.js',
|
||||
'../static/lib/angular-resource/angular-resource.min.js',
|
||||
'../static/lib/angular-cookies/angular-cookies.min.js',
|
||||
'../static/lib/angular/angular.js',
|
||||
'../static/lib/angular-route/angular-route.js',
|
||||
'../static/lib/angular-resource/angular-resource.js',
|
||||
'../static/lib/angular-cookies/angular-cookies.js',
|
||||
'../static/lib/angular-sanitize/angular-sanitize.min.js',
|
||||
'../static/lib/angular-md5/angular-md5.min.js',
|
||||
'../static/lib/angular-codemirror/lib/AngularCodeMirror.js',
|
||||
@ -30,9 +30,8 @@ module.exports = function() {
|
||||
'../static/lib/angular-scheduler/lib/angular-scheduler.min.js',
|
||||
'../static/lib/jqueryui/ui/minified/jquery-ui.min.js',
|
||||
'../static/lib/bootstrap/dist/js/bootstrap.min.js',
|
||||
'../static/lib/js-yaml/js-yaml.min.js',
|
||||
'../static/lib/js-yaml/dist/js-yaml.min.js',
|
||||
'../static/lib/select2/select2.min.js',
|
||||
'../static/lib/js-yaml/js-yaml.min.js',
|
||||
'../static/lib/jsonlint/lib/jsonlint.js',
|
||||
'../static/lib/codemirror/lib/codemirror.js',
|
||||
'../static/lib/codemirror/mode/javascript/javascript.js',
|
||||
@ -52,8 +51,13 @@ module.exports = function() {
|
||||
'../static/lib/lrInfiniteScroll/lrInfiniteScroll.js',
|
||||
'../static/lib/ansible/*.js',
|
||||
'../static/js/config.js',
|
||||
'../static/js/directives/dashboard-graphs.js',
|
||||
'../static/js/*/*.js',
|
||||
'../static/js/app.js'
|
||||
'../static/js/app.js',
|
||||
'../static/lib/angular-mocks/angular-mocks.js',
|
||||
'../../../node_modules/ng-midway-tester/src/ngMidwayTester.js',
|
||||
'./unit/*',
|
||||
'./unit/**/*'
|
||||
],
|
||||
|
||||
|
||||
@ -72,7 +76,13 @@ module.exports = function() {
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress'],
|
||||
reporters: ['dots', 'progress'],
|
||||
|
||||
client: {
|
||||
mocha: {
|
||||
ui: 'bdd'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// web server port
|
||||
@ -84,7 +94,7 @@ module.exports = function() {
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
autoWatch: true,
|
||||
|
||||
|
||||
// start these browsers
|
||||
@ -96,5 +106,5 @@ module.exports = function() {
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false
|
||||
|
||||
};
|
||||
});
|
||||
};
|
@ -1,183 +0,0 @@
|
||||
/**********************************
|
||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||
*
|
||||
* CheckLicense.js
|
||||
*
|
||||
* Tests the CheckLicense service- helpers/CheckLicense.js
|
||||
*
|
||||
*/
|
||||
|
||||
/* global describe, it, beforeEach, expect, module, inject */
|
||||
|
||||
var licenses = [{
|
||||
desc: 'expired license with < 1 day grace period',
|
||||
valid_key: true,
|
||||
time_remaining: 0,
|
||||
grace_period_remaining: 85000,
|
||||
free_instances: 10,
|
||||
expects: 'grace period has been exceeded'
|
||||
}, {
|
||||
desc: 'expired license with > 1 day grace period',
|
||||
valid_key: true,
|
||||
time_remaining: 0,
|
||||
grace_period_remaining: (86400 * 2),
|
||||
free_instances: 10,
|
||||
expects: '2 grace days'
|
||||
}, {
|
||||
desc: 'valid license with time remaining = 15 days',
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 15),
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 10,
|
||||
expects: 'license is valid'
|
||||
}, {
|
||||
desc: 'valid license with time remaining < 15 days',
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 10) ,
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 10,
|
||||
expects: 'license has 10 days remaining'
|
||||
}, {
|
||||
desc: 'valid license with time remaining > 15 days and remaining hosts > 0',
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 20),
|
||||
free_instances: 10,
|
||||
grace_period_remaining: 0,
|
||||
expects: 'license is valid'
|
||||
}, {
|
||||
desc: 'valid license with time remaining > 15 days and remaining hosts = 0',
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 20) ,
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 0,
|
||||
expects: 'license has reached capacity'
|
||||
}, {
|
||||
desc: 'expired trial license with > 1 day grace period',
|
||||
valid_key: true,
|
||||
trial: true,
|
||||
time_remaining: 0,
|
||||
grace_period_remaining: (86400 * 2),
|
||||
free_instances: 10,
|
||||
notExpects: 'grace days'
|
||||
} , {
|
||||
desc: 'expired trial license with < 1 day grace period',
|
||||
valid_key: true,
|
||||
trial: true,
|
||||
time_remaining: 0,
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 10,
|
||||
notExpects: '30 day grace period'
|
||||
}, {
|
||||
desc: 'trial license with time remaining = 15 days',
|
||||
trial: true,
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 15),
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 10,
|
||||
notExpects: 'grace period'
|
||||
}, {
|
||||
desc: 'trial license with time remaining < 15 days',
|
||||
valid_key: true,
|
||||
trial: true,
|
||||
time_remaining: (86400 * 10) ,
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 10,
|
||||
notExpects: 'grace period'
|
||||
}];
|
||||
|
||||
var should_notify = [{
|
||||
desc: 'should notify when license expired',
|
||||
valid_key: true,
|
||||
time_remaining: 0,
|
||||
grace_period_remaining: 85000,
|
||||
free_instances: 10
|
||||
}, {
|
||||
desc: 'should notify when license time remaining < 15 days',
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 10) ,
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 10
|
||||
}, {
|
||||
desc: 'should notify when host count <= 0',
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 200) ,
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 0
|
||||
}, {
|
||||
desc: 'should notify when license is invalid',
|
||||
valid_key: false
|
||||
},{
|
||||
desc: 'should notify when license is empty',
|
||||
}];
|
||||
|
||||
describe('Unit:CheckLicense', function() {
|
||||
|
||||
beforeEach(module('Tower'));
|
||||
|
||||
/*beforeEach(inject(function($rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
}));*/
|
||||
|
||||
it('should contain CheckLicense service', inject(function(CheckLicense) {
|
||||
expect(CheckLicense).not.toBe(null);
|
||||
}));
|
||||
|
||||
it('should have a getRemainingDays method', inject(function(CheckLicense) {
|
||||
expect(CheckLicense.getRemainingDays).not.toBe(null);
|
||||
}));
|
||||
|
||||
it('should have a getHTML method', inject(function(CheckLicense) {
|
||||
expect(CheckLicense.getHTML).not.toBe(null);
|
||||
}));
|
||||
|
||||
it('should have a getAdmin method', inject(function(CheckLicense) {
|
||||
expect(CheckLicense.getAdmin).not.toBe(null);
|
||||
}));
|
||||
|
||||
it('should have a shouldNotify method', inject(function(CheckLicense) {
|
||||
expect(CheckLicense.shouldNotify).not.toBe(null);
|
||||
}));
|
||||
|
||||
it('should not notify when license valid, time remaining > 15 days and host count > 0', inject(function(CheckLicense) {
|
||||
expect(CheckLicense.shouldNotify({
|
||||
valid_key: true,
|
||||
time_remaining: (86400 * 20),
|
||||
grace_period_remaining: 0,
|
||||
free_instances: 10 })).toBe(false);
|
||||
}));
|
||||
|
||||
should_notify.forEach(function(lic) {
|
||||
it(lic.desc, inject(function(CheckLicense) {
|
||||
expect(CheckLicense.shouldNotify(lic)).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
licenses.forEach(function(lic) {
|
||||
it(lic.desc, inject(function(CheckLicense) {
|
||||
var r;
|
||||
if (lic.expects) {
|
||||
r = new RegExp(lic.expects);
|
||||
expect(CheckLicense.getHTML(lic).body).toMatch(r);
|
||||
} else {
|
||||
r = new RegExp(lic.notExpects);
|
||||
expect(CheckLicense.getHTML(lic).body).not.toMatch(r);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
it('should recognize empty license as invalid', inject(function(CheckLicense) {
|
||||
expect(CheckLicense.getHTML({}).title).toMatch(/license required/i);
|
||||
}));
|
||||
|
||||
it('should show license update form to admin users when license is invalid', inject(function(CheckLicense, $rootScope) {
|
||||
$rootScope.current_user = {};
|
||||
$rootScope.current_user.is_superuser = true;
|
||||
expect(CheckLicense.getHTML({}).body).toMatch(/license\_license\_json/);
|
||||
}));
|
||||
|
||||
it('should not show license update form to non-admin users when license is invalid', inject(function(CheckLicense, $rootScope) {
|
||||
$rootScope.current_user = {};
|
||||
$rootScope.current_user.is_superuser = false;
|
||||
expect(CheckLicense.getHTML({}).body).not.toMatch(/license\_license\_json/);
|
||||
}));
|
||||
});
|
88
awx/ui/tests/unit/directives/job-status-graph-test.js
Normal file
88
awx/ui/tests/unit/directives/job-status-graph-test.js
Normal file
@ -0,0 +1,88 @@
|
||||
describe('Job Status Graph Directive', function() {
|
||||
var element, scope, httpBackend;
|
||||
|
||||
var resizeHandler = sinon.spy();
|
||||
|
||||
beforeEach(module('Tower'));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('LoadBasePaths', angular.noop);
|
||||
$provide.value('adjustGraphSize', resizeHandler);
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($rootScope, $compile, $httpBackend) {
|
||||
httpBackend = $httpBackend;
|
||||
$httpBackend.expectGET('/static/js/local_config.js').respond({
|
||||
});
|
||||
|
||||
$httpBackend.whenGET('/static/partials/job_status_graph.html')
|
||||
.respond("<div class='m'></div><div class='n'></div><div class='job-status-graph'><svg></svg></div>");
|
||||
|
||||
scope = $rootScope.$new();
|
||||
|
||||
element = '<job-status-graph class="job-status-graph" data="data" job-type="all" period="month"></job-status-graph>';
|
||||
|
||||
// Takes jobs grouped by result (successful or failure
|
||||
// Then looks at each array of arrays, where index 0 is the timestamp & index 1 is the count of jobs with that status
|
||||
scope.data =
|
||||
{ jobs:
|
||||
{ successful: [[1, 0], [2, 0], [3,0], [4,0], [5,0]],
|
||||
failed: [[1,0],[2,0],[3,0],[4,0],[5,0]]
|
||||
}
|
||||
};
|
||||
|
||||
element = $compile(element)(scope);
|
||||
scope.$digest();
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
element.trigger('$destroy');
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
httpBackend.verifyNoOutstandingRequest();
|
||||
});
|
||||
|
||||
function filterDataSeries(key, data) {
|
||||
return data.map(function(datum) {
|
||||
return datum.values;
|
||||
})[key];
|
||||
}
|
||||
|
||||
it('uses successes & failures from scope', function() {
|
||||
var chartContainer = d3.select(element.find('svg')[0]);
|
||||
var lineData = chartContainer.datum();
|
||||
|
||||
var successfulSeries = filterDataSeries(0, lineData);
|
||||
var failedSeries = filterDataSeries(1, lineData);
|
||||
|
||||
expect(successfulSeries).to.eql(
|
||||
[ {x: 1, y: 0, series: 0},
|
||||
{x: 2, y: 0, series: 0},
|
||||
{x: 3, y: 0, series: 0},
|
||||
{x: 4, y: 0, series: 0},
|
||||
{x: 5, y: 0, series: 0}]);
|
||||
|
||||
expect(failedSeries).to.eql(
|
||||
[ {x: 1, y: 0, series: 1},
|
||||
{x: 2, y: 0, series: 1},
|
||||
{x: 3, y: 0, series: 1},
|
||||
{x: 4, y: 0, series: 1},
|
||||
{x: 5, y: 0, series: 1}]);
|
||||
});
|
||||
|
||||
it('cleans up external bindings', function() {
|
||||
element.trigger('$destroy');
|
||||
|
||||
resizeHandler.reset();
|
||||
|
||||
inject(['$window', function($window) {
|
||||
angular.element($window).trigger('resize');
|
||||
}]);
|
||||
|
||||
expect(resizeHandler).not.to.have.been.called;
|
||||
});
|
||||
|
||||
});
|
||||
|
131
awx/ui/tests/unit/services/host-count-graph-data-test.js
Normal file
131
awx/ui/tests/unit/services/host-count-graph-data-test.js
Normal file
@ -0,0 +1,131 @@
|
||||
describe('Host Count Graph Data Service', function() {
|
||||
|
||||
var q;
|
||||
|
||||
var hostCountGraphData, httpBackend, rootScope, timeout;
|
||||
|
||||
var processErrors = sinon.spy();
|
||||
|
||||
var getBasePath = function(path) {
|
||||
return '/' + path + '/';
|
||||
}
|
||||
|
||||
function flushPromises() {
|
||||
window.setTimeout(function() {
|
||||
inject(function($rootScope) {
|
||||
$rootScope.$apply();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assertUrlDeferred(url, obj) {
|
||||
if (angular.isUndefined(obj[url]) ||
|
||||
angular.isUndefined(obj[url].then) &&
|
||||
angular.isUndefined(obj[url].promise.then)) {
|
||||
var urls = [];
|
||||
|
||||
for (key in obj) {
|
||||
if (/\//.test(key)) {
|
||||
urls.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
var registered = urls.map(function(url) {
|
||||
return "\t\"" + url + "\"";
|
||||
}).join("\n");
|
||||
|
||||
throw "Could not find a thenable registered for url \"" + url + "\". Registered URLs include:\n\n" + registered + "\n\nPerhaps you typo'd the URL?\n"
|
||||
}
|
||||
}
|
||||
|
||||
var restStub = {
|
||||
setUrl: function(url) {
|
||||
restStub[url] = q.defer();
|
||||
restStub.currentUrl = url;
|
||||
},
|
||||
reset: function() {
|
||||
delete restStub.deferred;
|
||||
},
|
||||
get: function() {
|
||||
// allow a single deferred on restStub in case we don't need URL
|
||||
restStub.deferred = restStub[restStub.currentUrl];
|
||||
|
||||
return restStub.deferred.promise;
|
||||
},
|
||||
succeedAt: function(url, value) {
|
||||
assertUrlDeferred(url, restStub);
|
||||
restStub[url].resolve(value);
|
||||
},
|
||||
succeed: function(value) {
|
||||
restStub.deferred.resolve(value);
|
||||
},
|
||||
failAt: function(url, value) {
|
||||
assertUrlDeferred(url, restStub);
|
||||
restStub[url].reject(value);
|
||||
},
|
||||
fail: function(value) {
|
||||
restStub.deferred.reject(value);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(module("Tower"));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
|
||||
$provide.value("$cookieStore", { get: angular.noop });
|
||||
|
||||
$provide.value('Rest', restStub);
|
||||
$provide.value('GetBasePath', getBasePath);
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
restStub.reset();
|
||||
});
|
||||
|
||||
beforeEach(inject(function(_hostCountGraphData_, $httpBackend, $q, $rootScope, $timeout) {
|
||||
hostCountGraphData = _hostCountGraphData_;
|
||||
httpBackend = $httpBackend;
|
||||
rootScope = $rootScope;
|
||||
timeout = $timeout;
|
||||
$httpBackend.expectGET('/static/js/local_config.js').respond({
|
||||
});
|
||||
q = $q;
|
||||
}));
|
||||
|
||||
it('returns a promise to be fulfilled when data comes in', function() {
|
||||
var license = "license";
|
||||
var hostData = "hosts";
|
||||
|
||||
var result = hostCountGraphData.get();
|
||||
|
||||
restStub.succeedAt('/config/', { data: {
|
||||
license_info: {
|
||||
instance_count: license
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
restStub.succeedAt('/dashboard/graphs/inventory/', { data: hostData });
|
||||
|
||||
flushPromises();
|
||||
|
||||
return expect(result).to.eventually.eql({ license: license, hosts: hostData });;
|
||||
});
|
||||
|
||||
it('processes errors through error handler', function() {
|
||||
var expected = { data: "blah", status: "bad" };
|
||||
var actual = hostCountGraphData.get();
|
||||
|
||||
restStub.failAt('/config/', expected);
|
||||
|
||||
flushPromises();
|
||||
|
||||
return actual.catch(function() {
|
||||
expect(processErrors).to
|
||||
.have.been.calledWith(null, expected.data, expected.status);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
124
awx/ui/tests/unit/services/job-status-graph-data-test.js
Normal file
124
awx/ui/tests/unit/services/job-status-graph-data-test.js
Normal file
@ -0,0 +1,124 @@
|
||||
describe('Job Status Graph Data Service', function() {
|
||||
|
||||
var q;
|
||||
|
||||
var jobStatusGraphData, httpBackend, rootScope, timeout;
|
||||
|
||||
var jobStatusChange = {
|
||||
$on: sinon.spy(),
|
||||
};
|
||||
|
||||
var processErrors = sinon.spy();
|
||||
|
||||
var getBasePath = function(path) {
|
||||
return '/' + path + '/';
|
||||
}
|
||||
|
||||
function flushPromises() {
|
||||
window.setTimeout(function() {
|
||||
inject(function($rootScope) {
|
||||
$rootScope.$apply();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var restStub = {
|
||||
setUrl: angular.noop,
|
||||
reset: function() {
|
||||
delete restStub.deferred;
|
||||
},
|
||||
get: function() {
|
||||
if (angular.isUndefined(restStub.deferred)) {
|
||||
restStub.deferred = q.defer();
|
||||
}
|
||||
|
||||
return restStub.deferred.promise;
|
||||
},
|
||||
succeed: function(value) {
|
||||
restStub.deferred.resolve(value);
|
||||
},
|
||||
fail: function(value) {
|
||||
restStub.deferred.reject(value);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(module("Tower"));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
|
||||
$provide.value("$cookieStore", { get: angular.noop });
|
||||
|
||||
$provide.value('ProcessErrors', processErrors);
|
||||
$provide.value('Rest', restStub);
|
||||
$provide.value('GetBasePath', getBasePath);
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
restStub.reset();
|
||||
});
|
||||
|
||||
beforeEach(inject(function(_jobStatusGraphData_, $httpBackend, $q, $rootScope, $timeout) {
|
||||
jobStatusGraphData = _jobStatusGraphData_;
|
||||
httpBackend = $httpBackend;
|
||||
rootScope = $rootScope;
|
||||
timeout = $timeout;
|
||||
$httpBackend.expectGET('/static/js/local_config.js').respond({
|
||||
});
|
||||
q = $q;
|
||||
}));
|
||||
|
||||
it('returns a promise to be fulfilled when data comes in', function() {
|
||||
var firstResult = "result";
|
||||
|
||||
var result = jobStatusGraphData.get('', '');
|
||||
|
||||
restStub.succeed({ data: firstResult });
|
||||
|
||||
flushPromises();
|
||||
|
||||
return expect(result).to.eventually.equal(firstResult);;
|
||||
});
|
||||
|
||||
it('processes errors through error handler', function() {
|
||||
var expected = { data: "blah", status: "bad" };
|
||||
var actual = jobStatusGraphData.get().catch(function() {
|
||||
return processErrors;
|
||||
});
|
||||
|
||||
restStub.fail(expected);
|
||||
|
||||
flushPromises();
|
||||
|
||||
return actual.catch(function() {
|
||||
expect(processErrors).to
|
||||
.have.been.calledWith(null, expected.data, expected.status);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('broadcasts event when data is received', function() {
|
||||
var expected = "value";
|
||||
var result = q.defer();
|
||||
jobStatusGraphData.setupWatcher();
|
||||
|
||||
inject(function($rootScope) {
|
||||
$rootScope.$on('DataReceived:JobStatusGraph', function(e, data) {
|
||||
result.resolve(data);
|
||||
});
|
||||
$rootScope.$emit('JobStatusChange');
|
||||
restStub.succeed({ data: expected });
|
||||
flushPromises();
|
||||
});
|
||||
|
||||
return expect(result.promise).to.eventually.equal(expected);
|
||||
});
|
||||
|
||||
it('requests data with given period and jobType', function() {
|
||||
restStub.setUrl = sinon.spy();
|
||||
|
||||
jobStatusGraphData.get('1', '2');
|
||||
|
||||
expect(restStub.setUrl).to.have.been.calledWith('/dashboard/graphs/jobs/?period=1&job_type=2');
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user