mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 17:37:37 -02:30
Merge pull request #42 from joefiorini/job-status-graph
Fixes responsiveness of graphs on dashboard
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,6 +6,7 @@ awx/projects
|
|||||||
awx/job_output
|
awx/job_output
|
||||||
awx/public/media
|
awx/public/media
|
||||||
awx/public/static
|
awx/public/static
|
||||||
|
awx/ui/tests/test-results.xml
|
||||||
awx/ui/static/js/awx.min.js
|
awx/ui/static/js/awx.min.js
|
||||||
awx/ui/static/js/local_config.js
|
awx/ui/static/js/local_config.js
|
||||||
awx/ui/static/css/awx.min.css
|
awx/ui/static/css/awx.min.css
|
||||||
@@ -26,7 +27,7 @@ tar-build
|
|||||||
*.py[c,o]
|
*.py[c,o]
|
||||||
|
|
||||||
# JavaScript
|
# JavaScript
|
||||||
/GruntFile.js
|
/Gruntfile.js
|
||||||
/bower.json
|
/bower.json
|
||||||
/package.json
|
/package.json
|
||||||
node_modules/**
|
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,
|
"browser": true,
|
||||||
"jquery": true,
|
"jquery": true,
|
||||||
"esnext": true,
|
"esnext": true,
|
||||||
"globalstrict": 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,
|
"strict": false,
|
||||||
"quotmark": false,
|
"quotmark": false,
|
||||||
"smarttabs": true,
|
|
||||||
"trailing": true,
|
"trailing": true,
|
||||||
"undef": true,
|
"undef": true,
|
||||||
"unused": true,
|
"unused": true,
|
||||||
"eqeqeq": true,
|
"eqeqeq": true,
|
||||||
"indent": 4,
|
"indent": 4,
|
||||||
"onevar": true,
|
|
||||||
"newcap": false
|
"newcap": false
|
||||||
}
|
}
|
||||||
|
|||||||
11
Makefile
11
Makefile
@@ -66,7 +66,7 @@ MOCK_CFG ?=
|
|||||||
|
|
||||||
.PHONY: clean rebase push requirements requirements_pypi requirements_jenkins \
|
.PHONY: clean rebase push requirements requirements_pypi requirements_jenkins \
|
||||||
develop refresh adduser syncdb migrate dbchange dbshell runserver celeryd \
|
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 \
|
release_build release_clean sdist rpmtar mock-rpm mock-srpm \
|
||||||
deb deb-src debian reprepro setup_tarball
|
deb deb-src debian reprepro setup_tarball
|
||||||
|
|
||||||
@@ -240,9 +240,12 @@ test_coverage:
|
|||||||
coverage_html:
|
coverage_html:
|
||||||
coverage html
|
coverage html
|
||||||
|
|
||||||
# Run UI unit tests using Selenium.
|
ui_analysis_report: node_modules
|
||||||
test_ui:
|
$(GRUNT) plato:report
|
||||||
$(PYTHON) manage.py test -v2 awx.ui.tests
|
|
||||||
|
# Run UI unit tests
|
||||||
|
test_ui: node_modules
|
||||||
|
$(GRUNT) karma:ci
|
||||||
|
|
||||||
# Run API unit tests across multiple Python/Django versions with Tox.
|
# Run API unit tests across multiple Python/Django versions with Tox.
|
||||||
test_tox:
|
test_tox:
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ if ($basePath) {
|
|||||||
urlPrefix = $basePath;
|
urlPrefix = $basePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
angular.module('Tower', [
|
angular.module('Tower', [
|
||||||
'ngRoute',
|
'ngRoute',
|
||||||
'ngSanitize',
|
'ngSanitize',
|
||||||
'ngCookies',
|
'ngCookies',
|
||||||
'RestServices',
|
'RestServices',
|
||||||
|
'DataServices',
|
||||||
|
'DashboardGraphs',
|
||||||
'AuthService',
|
'AuthService',
|
||||||
'Utilities',
|
'Utilities',
|
||||||
'LicenseHelper',
|
'LicenseHelper',
|
||||||
@@ -84,9 +87,6 @@ angular.module('Tower', [
|
|||||||
'SelectionHelper',
|
'SelectionHelper',
|
||||||
'HostGroupsFormDefinition',
|
'HostGroupsFormDefinition',
|
||||||
'DashboardCountsWidget',
|
'DashboardCountsWidget',
|
||||||
'JobStatusGraphWidget',
|
|
||||||
'HostPieChartWidget',
|
|
||||||
'HostGraphWidget',
|
|
||||||
'DashboardJobsWidget',
|
'DashboardJobsWidget',
|
||||||
'PortalJobsWidget',
|
'PortalJobsWidget',
|
||||||
'StreamWidget',
|
'StreamWidget',
|
||||||
@@ -131,7 +131,6 @@ angular.module('Tower', [
|
|||||||
.constant('AngularScheduler.useTimezone', true)
|
.constant('AngularScheduler.useTimezone', true)
|
||||||
.constant('AngularScheduler.showUTCField', true)
|
.constant('AngularScheduler.showUTCField', true)
|
||||||
.constant('$timezones.definitions.location', urlPrefix + 'lib/angular-tz-extensions/tz/data')
|
.constant('$timezones.definitions.location', urlPrefix + 'lib/angular-tz-extensions/tz/data')
|
||||||
|
|
||||||
.config(['$routeProvider',
|
.config(['$routeProvider',
|
||||||
function ($routeProvider) {
|
function ($routeProvider) {
|
||||||
|
|
||||||
@@ -399,7 +398,15 @@ angular.module('Tower', [
|
|||||||
|
|
||||||
when('/home', {
|
when('/home', {
|
||||||
templateUrl: urlPrefix + 'partials/home.html',
|
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', {
|
when('/home/groups', {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
// > password_strength = green
|
// > password_strength = green
|
||||||
// It also controls password validation. Passwords are rejected if the score is not > password_strength.
|
// 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.
|
// 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
|
* 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,
|
function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, DashboardJobs,
|
||||||
ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button){
|
ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, $window, graphData){
|
||||||
|
|
||||||
ClearScope('home');
|
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()
|
// 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.
|
// 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);
|
e.html(html);
|
||||||
$compile(e)($scope);
|
$compile(e)($scope);
|
||||||
|
|
||||||
waitCount = 4;
|
|
||||||
loadedCount = 0;
|
|
||||||
|
|
||||||
if (!$routeParams.login) {
|
if (!$routeParams.login) {
|
||||||
// If we're not logging in, start the Wait widget. Otherwise, it's already running.
|
// If we're not logging in, start the Wait widget. Otherwise, it's already running.
|
||||||
//Wait('start');
|
//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) {
|
if ($scope.removeDashboardReady) {
|
||||||
$scope.removeDashboardReady();
|
$scope.removeDashboardReady();
|
||||||
}
|
}
|
||||||
$scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) {
|
$scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) {
|
||||||
|
|
||||||
nv.dev=false;
|
nv.dev=false;
|
||||||
|
|
||||||
|
|
||||||
@@ -106,64 +83,24 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait,
|
|||||||
"margin-bottom": "15px"};
|
"margin-bottom": "15px"};
|
||||||
$('.graph-container').css(borderStyles);
|
$('.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({
|
DashboardCounts({
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
target: 'dash-counts',
|
target: 'dash-counts',
|
||||||
dashboard: data
|
dashboard: data
|
||||||
});
|
});
|
||||||
|
|
||||||
JobStatusGraph({
|
// // chart.update();
|
||||||
scope: $scope,
|
|
||||||
target: 'dash-job-status-graph',
|
$scope.graphData = graphData;
|
||||||
dashboard: data
|
|
||||||
});
|
|
||||||
|
|
||||||
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({
|
DashboardJobs({
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
target: 'dash-jobs-list',
|
target: 'dash-jobs-list',
|
||||||
dashboard: data
|
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 () {
|
$scope.showActivity = function () {
|
||||||
Stream({
|
Stream({
|
||||||
scope: $scope
|
scope: $scope
|
||||||
@@ -172,23 +109,23 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait,
|
|||||||
|
|
||||||
$scope.refresh = function () {
|
$scope.refresh = function () {
|
||||||
Wait('start');
|
Wait('start');
|
||||||
loadedCount = 0;
|
|
||||||
Rest.setUrl(GetBasePath('dashboard'));
|
Rest.setUrl(GetBasePath('dashboard'));
|
||||||
Rest.get()
|
Rest.get()
|
||||||
.success(function (data) {
|
.success(function (data) {
|
||||||
$scope.$emit('dashboardReady', 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 });
|
.error(function (data, status) {
|
||||||
});
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.refresh();
|
$scope.refresh();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph','JobStatusGraph', 'HostPieChart', 'DashboardJobs',
|
Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'DashboardJobs',
|
||||||
'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button'
|
'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',
|
HomeHosts.$inject = ['$scope', '$location', '$routeParams', 'HomeHostList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller',
|
||||||
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'SetStatus', 'ToggleHostEnabled', 'HostsEdit', 'Stream',
|
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'SetStatus', 'ToggleHostEnabled', 'HostsEdit', 'Stream',
|
||||||
'Find', 'ShowJobSummary', 'ViewJob'
|
'Find', 'ShowJobSummary', 'ViewJob'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -527,7 +527,9 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa
|
|||||||
|
|
||||||
if($scope.survey_enabled === true && $scope.survey_exists!==true){
|
if($scope.survey_enabled === true && $scope.survey_exists!==true){
|
||||||
$scope.$emit("PromptForSurvey");
|
$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');
|
' 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
|
// 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){
|
if($scope.survey_enabled === true && $scope.survey_exists!==true){
|
||||||
$scope.$emit("PromptForSurvey");
|
$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){
|
if($scope.survey_enabled === true && $scope.survey_exists!==true){
|
||||||
$scope.$emit("PromptForSurvey");
|
$scope.$emit("PromptForSurvey");
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
PlaybookRun({
|
PlaybookRun({
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
id: id
|
id: id
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// handler for 'Enable Survey' button
|
// 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',
|
'GetBasePath', 'md5Setup', 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait', 'Stream', 'Empty', 'Prompt',
|
||||||
'ParseVariableString', 'ToJSON', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'GetChoices',
|
'ParseVariableString', 'ToJSON', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'GetChoices',
|
||||||
'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit', 'PlaybookRun' , 'SurveyControllerInit', '$sce'
|
'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 });
|
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
|
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) {
|
if (sources_scope.removeScopeSourceTypeOptionsReady) {
|
||||||
|
|||||||
@@ -1208,10 +1208,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
keys;
|
keys;
|
||||||
|
|
||||||
function listSort(a,b) {
|
function listSort(a,b) {
|
||||||
if (parseInt(a,10) < parseInt(b,10))
|
if (parseInt(a,10) < parseInt(b,10)) {
|
||||||
return -1;
|
return -1;
|
||||||
if (parseInt(a,10) > parseInt(b,10))
|
}
|
||||||
|
if (parseInt(a,10) > parseInt(b,10)) {
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1276,10 +1278,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
idx, key, keys, newKeys, tasks, t;
|
idx, key, keys, newKeys, tasks, t;
|
||||||
|
|
||||||
function listSort(a,b) {
|
function listSort(a,b) {
|
||||||
if (parseInt(a,10) < parseInt(b,10))
|
if (parseInt(a,10) < parseInt(b,10)) {
|
||||||
return -1;
|
return -1;
|
||||||
if (parseInt(a,10) > parseInt(b,10))
|
}
|
||||||
|
if (parseInt(a,10) > parseInt(b,10)) {
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1385,15 +1389,19 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
keys = Object.keys(filteredListB);
|
keys = Object.keys(filteredListB);
|
||||||
keys.sort(function compare(a, b) {
|
keys.sort(function compare(a, b) {
|
||||||
if (filteredListB[a].name === filteredListB[b].name) {
|
if (filteredListB[a].name === filteredListB[b].name) {
|
||||||
if (filteredListB[a].counter < filteredListB[b].counter)
|
if (filteredListB[a].counter < filteredListB[b].counter) {
|
||||||
return -1;
|
return -1;
|
||||||
if (filteredListB[a].counter >filteredListB[b].counter)
|
}
|
||||||
|
if (filteredListB[a].counter >filteredListB[b].counter) {
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (filteredListB[a].name < filteredListB[b].name)
|
if (filteredListB[a].name < filteredListB[b].name) {
|
||||||
return -1;
|
return -1;
|
||||||
if (filteredListB[a].name > filteredListB[b].name)
|
}
|
||||||
|
if (filteredListB[a].name > filteredListB[b].name) {
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// a must be equal to b
|
// a must be equal to b
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1453,10 +1461,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
keys = Object.keys(filteredListB);
|
keys = Object.keys(filteredListB);
|
||||||
|
|
||||||
keys.sort(function(a,b) {
|
keys.sort(function(a,b) {
|
||||||
if (filteredListB[a].name > filteredListB[b].name)
|
if (filteredListB[a].name > filteredListB[b].name) {
|
||||||
return 1;
|
return 1;
|
||||||
if (filteredListB[a].name < filteredListB[b].name)
|
}
|
||||||
|
if (filteredListB[a].name < filteredListB[b].name) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
// a must be equal to b
|
// a must be equal to b
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
|
|||||||
if(scope.prompt_for_vars===false && scope.survey_enabled===true){
|
if(scope.prompt_for_vars===false && scope.survey_enabled===true){
|
||||||
scope.$emit('GetExtraVars');
|
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) {
|
else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) {
|
||||||
scope.$emit('PromptForSurvey', html, url);
|
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){
|
if(scope.$parent.portalMode===true){
|
||||||
$window.open('/#/jobs/' + job.id, '_blank');
|
$window.open('/#/jobs/' + job.id, '_blank');
|
||||||
}
|
}
|
||||||
else $location.url('/jobs/' + job.id);
|
else {
|
||||||
|
$location.url('/jobs/' + job.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LogViewer({
|
LogViewer({
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ angular.module('PermissionsHelper', [])
|
|||||||
} else {
|
} else {
|
||||||
scope.projectrequired = true;
|
scope.projectrequired = true;
|
||||||
html = "<dl>\n" +
|
html = "<dl>\n" +
|
||||||
"<dt>Create</dt>\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" +
|
"<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" +
|
"<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 " +
|
"<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" +
|
"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){
|
if(scope.can_edit === false){
|
||||||
$('#survey-save-button').attr('disabled', "disabled");
|
$('#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'));
|
element = angular.element(document.getElementById('survey-save-button'));
|
||||||
$compile(element)(scope);
|
$compile(element)(scope);
|
||||||
|
|
||||||
@@ -513,7 +515,9 @@ angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper',
|
|||||||
if(scope.mode === 'add'){
|
if(scope.mode === 'add'){
|
||||||
questions = [];
|
questions = [];
|
||||||
}
|
}
|
||||||
else scope.survey_questions = [];
|
else {
|
||||||
|
scope.survey_questions = [];
|
||||||
|
}
|
||||||
$(me).dialog('close');
|
$(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 "breadcrumbs.less";
|
||||||
@import "stdout.less";
|
@import "stdout.less";
|
||||||
@import "lists.less";
|
@import "lists.less";
|
||||||
@import "new-dashboard.less";
|
@import "dashboard.less";
|
||||||
@import "jPushMenu.less";
|
@import "jPushMenu.less";
|
||||||
@import "survey-maker.less";
|
@import "survey-maker.less";
|
||||||
@import "portal.less";
|
@import "portal.less";
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
/*********************************************
|
/*********************************************
|
||||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||||
*
|
*
|
||||||
* new-dashboard.css
|
* dashboard.css
|
||||||
*
|
*
|
||||||
* custom styles for the new dashboard
|
* 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{
|
.job-status-graph, .host-count-graph{
|
||||||
font: 10px sans-serif;
|
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{
|
.m, .n{
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,6 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// 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(){
|
scope.update = function(){
|
||||||
var val = [];
|
var val = [];
|
||||||
angular.forEach(scope.cbModel, function(v,k){
|
angular.forEach(scope.cbModel, function(v,k){
|
||||||
if (v)
|
if (v) {
|
||||||
val.push(k);
|
val.push(k);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (val.length>0){
|
if (val.length>0){
|
||||||
scope.ngModel.value = val;
|
scope.ngModel.value = val;
|
||||||
|
|||||||
@@ -223,8 +223,6 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator
|
|||||||
$(this).remove();
|
$(this).remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(window).unbind('resize');
|
|
||||||
|
|
||||||
// Prepend an asterisk to required field label
|
// Prepend an asterisk to required field label
|
||||||
$('.form-control[required], input[type="radio"][required]').each(function () {
|
$('.form-control[required], input[type="radio"][required]').each(function () {
|
||||||
var label, span;
|
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
|
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
|
||||||
$(this).remove();
|
$(this).remove();
|
||||||
});
|
});
|
||||||
$(window).unbind('resize');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$('#help-modal').empty().dialog('destroy');
|
$('#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\" " +
|
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>";
|
"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_{{" +
|
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\" " +
|
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>";
|
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
|
||||||
|
}
|
||||||
} else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) {
|
} else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) {
|
||||||
|
|
||||||
// Row level actions
|
// Row level actions
|
||||||
@@ -582,4 +582,4 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@@ -63,14 +63,16 @@ angular.module('PromptDialog', ['Utilities'])
|
|||||||
|
|
||||||
focus = function() {
|
focus = function() {
|
||||||
var focusableElement = focusableChildren[currentIndex];
|
var focusableElement = focusableChildren[currentIndex];
|
||||||
if (focusableElement)
|
if (focusableElement) {
|
||||||
focusableElement.focus();
|
focusableElement.focus();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
focusPrevious = function () {
|
focusPrevious = function () {
|
||||||
currentIndex--;
|
currentIndex--;
|
||||||
if (currentIndex < 0)
|
if (currentIndex < 0) {
|
||||||
currentIndex = numElements - 1;
|
currentIndex = numElements - 1;
|
||||||
|
}
|
||||||
|
|
||||||
focus();
|
focus();
|
||||||
|
|
||||||
@@ -79,8 +81,9 @@ angular.module('PromptDialog', ['Utilities'])
|
|||||||
|
|
||||||
focusNext = function () {
|
focusNext = function () {
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
if (currentIndex >= numElements)
|
if (currentIndex >= numElements) {
|
||||||
currentIndex = 0;
|
currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
focus();
|
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 ng-cloak id="htmlTemplate" style="padding:10px">
|
||||||
<div id="refresh-row" class="row">
|
<div id="refresh-row" class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
@@ -12,12 +12,24 @@
|
|||||||
<div id="dash-counts" class="col-sm-12 col-xs-12"></div>
|
<div id="dash-counts" class="col-sm-12 col-xs-12"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<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="left-side col-sm-6 col-xs-12">
|
||||||
<div class="right-side col-sm-6 col-xs-12"><div id="dash-host-status-graph" class="graph-container"></div></div>
|
<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>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="dash-jobs-list" class="left-side col-sm-6 col-xs-12"></div>
|
<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>
|
</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/Permissions.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/controllers/Schedules.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/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/Users.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/forms/Organizations.js"></script>
|
<script src="{{ STATIC_URL }}js/forms/Organizations.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/forms/Inventories.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/helpers/AboutAnsible.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/JobStatus.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/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/DashboardJobs.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/PortalJobs.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/PortalJobs.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/Stream.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/Stream.js"></script>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ module.exports = function(config) {
|
|||||||
conf.files = conf.files.concat([
|
conf.files = conf.files.concat([
|
||||||
'../static/lib/angular-mocks/angular-mocks.js',
|
'../static/lib/angular-mocks/angular-mocks.js',
|
||||||
'../../../node_modules/ng-midway-tester/src/ngMidwayTester.js',
|
'../../../node_modules/ng-midway-tester/src/ngMidwayTester.js',
|
||||||
'./unit/*'
|
'./unit/*',
|
||||||
|
'./unit/**/*'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// level of logging
|
// level of logging
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
// Karma configuration
|
// Karma configuration
|
||||||
// Generated on Mon Aug 04 2014 21:17:04 GMT-0400 (EDT)
|
// Generated on Mon Aug 04 2014 21:17:04 GMT-0400 (EDT)
|
||||||
|
|
||||||
module.exports = function() {
|
module.exports = function(config) {
|
||||||
return {
|
config.set({
|
||||||
|
|
||||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||||
basePath: '',
|
basePath: '',
|
||||||
|
|
||||||
// frameworks to use
|
// frameworks to use
|
||||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
// 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
|
// list of files / patterns to load in the browser
|
||||||
files: [
|
files: [
|
||||||
'../static/lib/jquery/dist/jquery.min.js',
|
'../static/lib/jquery/dist/jquery.min.js',
|
||||||
'../static/lib/angular/angular.min.js',
|
'../static/lib/angular/angular.js',
|
||||||
'../static/lib/angular-route/angular-route.min.js',
|
'../static/lib/angular-route/angular-route.js',
|
||||||
'../static/lib/angular-resource/angular-resource.min.js',
|
'../static/lib/angular-resource/angular-resource.js',
|
||||||
'../static/lib/angular-cookies/angular-cookies.min.js',
|
'../static/lib/angular-cookies/angular-cookies.js',
|
||||||
'../static/lib/angular-sanitize/angular-sanitize.min.js',
|
'../static/lib/angular-sanitize/angular-sanitize.min.js',
|
||||||
'../static/lib/angular-md5/angular-md5.min.js',
|
'../static/lib/angular-md5/angular-md5.min.js',
|
||||||
'../static/lib/angular-codemirror/lib/AngularCodeMirror.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/angular-scheduler/lib/angular-scheduler.min.js',
|
||||||
'../static/lib/jqueryui/ui/minified/jquery-ui.min.js',
|
'../static/lib/jqueryui/ui/minified/jquery-ui.min.js',
|
||||||
'../static/lib/bootstrap/dist/js/bootstrap.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/select2/select2.min.js',
|
||||||
'../static/lib/js-yaml/js-yaml.min.js',
|
|
||||||
'../static/lib/jsonlint/lib/jsonlint.js',
|
'../static/lib/jsonlint/lib/jsonlint.js',
|
||||||
'../static/lib/codemirror/lib/codemirror.js',
|
'../static/lib/codemirror/lib/codemirror.js',
|
||||||
'../static/lib/codemirror/mode/javascript/javascript.js',
|
'../static/lib/codemirror/mode/javascript/javascript.js',
|
||||||
@@ -52,8 +51,13 @@ module.exports = function() {
|
|||||||
'../static/lib/lrInfiniteScroll/lrInfiniteScroll.js',
|
'../static/lib/lrInfiniteScroll/lrInfiniteScroll.js',
|
||||||
'../static/lib/ansible/*.js',
|
'../static/lib/ansible/*.js',
|
||||||
'../static/js/config.js',
|
'../static/js/config.js',
|
||||||
|
'../static/js/directives/dashboard-graphs.js',
|
||||||
'../static/js/*/*.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
|
// test results reporter to use
|
||||||
// possible values: 'dots', 'progress'
|
// possible values: 'dots', 'progress'
|
||||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||||
reporters: ['progress'],
|
reporters: ['dots', 'progress'],
|
||||||
|
|
||||||
|
client: {
|
||||||
|
mocha: {
|
||||||
|
ui: 'bdd'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
// web server port
|
// web server port
|
||||||
@@ -84,7 +94,7 @@ module.exports = function() {
|
|||||||
|
|
||||||
|
|
||||||
// enable / disable watching file and executing tests whenever any file changes
|
// enable / disable watching file and executing tests whenever any file changes
|
||||||
autoWatch: false,
|
autoWatch: true,
|
||||||
|
|
||||||
|
|
||||||
// start these browsers
|
// start these browsers
|
||||||
@@ -96,5 +106,5 @@ module.exports = function() {
|
|||||||
// if true, Karma captures browsers, runs the tests and exits
|
// if true, Karma captures browsers, runs the tests and exits
|
||||||
singleRun: false
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user