Dashboard refresh

This commit is contained in:
John Mitchell 2015-05-21 21:57:53 -04:00
parent f4e3126c43
commit 12af62f671
55 changed files with 1066 additions and 1205 deletions

View File

@ -210,4 +210,3 @@
} }
} }
} }

View File

@ -29,11 +29,7 @@ import {Authenticate} from 'tower/controllers/Authentication';
import {CredentialsAdd, CredentialsEdit, CredentialsList} from 'tower/controllers/Credentials'; import {CredentialsAdd, CredentialsEdit, CredentialsList} from 'tower/controllers/Credentials';
import {JobsListController} from 'tower/controllers/Jobs'; import {JobsListController} from 'tower/controllers/Jobs';
import {PortalController} from 'tower/controllers/Portal'; import {PortalController} from 'tower/controllers/Portal';
import dataServices from 'tower/services/_data-services';
import dashboardGraphs from 'tower/directives/_dashboard-graphs';
import systemTracking from 'tower/system-tracking/main'; import systemTracking from 'tower/system-tracking/main';
import routeExtensions from 'tower/shared/route-extensions/main'; import routeExtensions from 'tower/shared/route-extensions/main';
import breadcrumbs from 'tower/shared/breadcrumbs/main'; import breadcrumbs from 'tower/shared/breadcrumbs/main';
@ -41,6 +37,7 @@ import breadcrumbs from 'tower/shared/breadcrumbs/main';
import setupMenu from 'tower/setup-menu/main'; import setupMenu from 'tower/setup-menu/main';
import mainMenu from 'tower/main-menu/main'; import mainMenu from 'tower/main-menu/main';
import browserData from 'tower/browser-data/main'; import browserData from 'tower/browser-data/main';
import dashboard from 'tower/dashboard/main';
import {JobDetailController} from 'tower/controllers/JobDetail'; import {JobDetailController} from 'tower/controllers/JobDetail';
import {JobStdoutController} from 'tower/controllers/JobStdout'; import {JobStdoutController} from 'tower/controllers/JobStdout';
@ -79,14 +76,13 @@ var tower = angular.module('Tower', [
'ngSanitize', 'ngSanitize',
'ngCookies', 'ngCookies',
'RestServices', 'RestServices',
dataServices.name,
dashboardGraphs.name,
routeExtensions.name, routeExtensions.name,
browserData.name, browserData.name,
breadcrumbs.name, breadcrumbs.name,
systemTracking.name, systemTracking.name,
setupMenu.name, setupMenu.name,
mainMenu.name, mainMenu.name,
dashboard.name,
'AuthService', 'AuthService',
'Utilities', 'Utilities',
'LicenseHelper', 'LicenseHelper',
@ -149,8 +145,6 @@ var tower = angular.module('Tower', [
'AccessHelper', 'AccessHelper',
'SelectionHelper', 'SelectionHelper',
'HostGroupsFormDefinition', 'HostGroupsFormDefinition',
'DashboardCountsWidget',
'DashboardJobsWidget',
'PortalJobsWidget', 'PortalJobsWidget',
'StreamWidget', 'StreamWidget',
'JobsHelper', 'JobsHelper',
@ -863,10 +857,9 @@ var tower = angular.module('Tower', [
templateUrl: urlPrefix + 'partials/home.html', templateUrl: urlPrefix + 'partials/home.html',
controller: Home, controller: Home,
resolve: { resolve: {
graphData: ['$q', 'jobStatusGraphData', 'hostCountGraphData', 'FeaturesService', function($q, jobStatusGraphData, hostCountGraphData, FeaturesService) { graphData: ['$q', 'jobStatusGraphData', 'FeaturesService', function($q, jobStatusGraphData, FeaturesService) {
return $q.all({ return $q.all({
jobStatus: jobStatusGraphData.get("month", "all"), jobStatus: jobStatusGraphData.get("month", "all"),
hostCounts: hostCountGraphData.get(),
features: FeaturesService.get() features: FeaturesService.get()
}); });
}] }]

View File

@ -26,41 +26,31 @@
* *
*/ */
export function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, DashboardJobs, export function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait,
ClearScope, Stream, Rest, GetBasePath, ProcessErrors, $window, graphData){ ClearScope, Stream, Rest, GetBasePath, ProcessErrors, $window, graphData){
ClearScope('home'); ClearScope('home');
var borderStyles; var dataCount = 0;
if (!$routeParams.login) { if ($scope.removeDashboardDataLoadComplete) {
// If we're not logging in, start the Wait widget. Otherwise, it's already running. $scope.removeDashboardDataLoadComplete();
//Wait('start');
} }
$scope.removeDashboardDataLoadComplete = $scope.$on('dashboardDataLoadComplete', function () {
dataCount++;
if (dataCount === 3) {
Wait("stop");
dataCount = 0;
}
});
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) {
$scope.dashboardCountsData = data;
nv.dev=false;
borderStyles = {"border": "1px solid #A9A9A9",
"border-radius": "4px",
"padding": "5px",
"margin-bottom": "15px"};
$('.graph-container').css(borderStyles);
DashboardCounts({
scope: $scope,
target: 'dash-counts',
dashboard: data
});
// // chart.update();
$scope.graphData = graphData; $scope.graphData = graphData;
$scope.$emit('dashboardDataLoadComplete');
var cleanupJobListener = var cleanupJobListener =
$rootScope.$on('DataReceived:JobStatusGraph', function(e, data) { $rootScope.$on('DataReceived:JobStatusGraph', function(e, data) {
@ -70,16 +60,25 @@ export function Home($scope, $compile, $routeParams, $rootScope, $location, $log
$scope.$on('$destroy', function() { $scope.$on('$destroy', function() {
cleanupJobListener(); cleanupJobListener();
}); });
DashboardJobs({
scope: $scope,
target: 'dash-jobs-list',
dashboard: data
});
}); });
if ($scope.removeDashboardJobsListReady) {
$scope.removeDashboardJobsListReady();
}
$scope.removeDashboardJobsListReady = $scope.$on('dashboardJobsListReady', function (e, data) {
$scope.dashboardJobsListData = data;
$scope.$emit('dashboardDataLoadComplete');
});
if ($scope.removeDashboardJobTemplatesListReady) {
$scope.removeDashboardJobTemplatesListReady();
}
$scope.removeDashboardJobTemplatesListReady = $scope.$on('dashboardJobTemplatesListReady', function (e, data) {
$scope.dashboardJobTemplatesListData = data;
$scope.$emit('dashboardDataLoadComplete');
});
$scope.showActivity = function () { $scope.showActivity = function () {
Stream({ Stream({
scope: $scope scope: $scope
@ -97,13 +96,30 @@ export function Home($scope, $compile, $routeParams, $rootScope, $location, $log
.error(function (data, status) { .error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status });
}); });
Rest.setUrl(GetBasePath("jobs") + "?order_by=-finished&page_size=5&finished__isnull=false");
Rest.get()
.success(function (data) {
data = data.results;
$scope.$emit('dashboardJobsListReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
});
Rest.setUrl(GetBasePath("job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false");
Rest.get()
.success(function (data) {
data = data.results;
$scope.$emit('dashboardJobTemplatesListReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard job templates list: ' + status });
});
}; };
$scope.refresh(); $scope.refresh();
} }
Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'DashboardJobs', Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait',
'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', '$window', 'graphData' 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', '$window', 'graphData'
]; ];

View File

@ -0,0 +1,70 @@
/** @define DashboardCounts */
.DashboardCounts {
display: flex;
flex-direction: row;
align-items: stretch;
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
padding-top: 0;
margin-top: 0;
}
.DashboardCounts-buttonStyle {
text-align: center;
padding: 8px;
padding-bottom: 11px;
padding-left: 15px;
padding-right: 15px;
border-radius: 3px;
}
@media only screen and (max-width: 710px) {
.DashboardCounts {
margin-bottom: -15px;
}
.DashboardCounts-buttonStyle {
border: 1px solid #aaa;
margin-bottom: 15px;
width: 33%;
flex-basis: ~"calc(33% - 7px)";
}
}
.DashboardCounts-buttonStyle:hover {
background-color: #1778c3;
border-color: #1778c3;
.DashboardCounts-number,
.DashboardCounts-label {
color: #fff;
}
}
.DashboardCounts-buttonStyle.is-failure:hover {
background-color: #ff5850;
border-color: #ff5850;
.DashboardCounts-number,
.DashboardCounts-label {
color: #fff;
}
}
.DashboardCounts-number {
font-size: 30px;
line-height: 26px;
flex: 1;
}
.DashboardCounts-number.is-failure {
color: #ff5850;
}
.DashboardCounts-label {
flex: 1;
margin-bottom: 0px;
color: #000;
}

View File

@ -0,0 +1,73 @@
/* jshint unused: vars */
export default
[ '$rootScope',
function() {
return {
restrict: 'E',
scope: {
data: '='
},
replace: false,
templateUrl: '/static/js/dashboard/counts/dashboard-counts.partial.html',
link: function(scope, element, attrs) {
scope.$watch("data", function(data) {
if (data && data.hosts) {
createCounts(data);
}
});
function addFailureToCount(val) {
if (val.isFailureCount) {
// delete isFailureCount
if (val.number > 0) {
val.isFailure = true;
} else {
val.isFailure = false;
}
} else {
val.isFailure = false;
}
return val;
}
function createCounts(data) {
scope.counts = _.map([
{
url: "/#/home/hosts",
number: scope.data.hosts.total,
label: "Hosts"
},
{
url: "/#/home/hosts?has_active_failures=true",
number: scope.data.hosts.failed,
label: "Failed Hosts",
isFailureCount: true
},
{
url: "/#/inventories",
number: scope.data.inventories.total,
label: "Inventories",
},
{
url: "/#/inventories/?inventory_sources_with_failures",
number: scope.data.inventories.inventory_failed,
label: "Inventory Sync Failures",
isFailureCount: true
},
{
url: "/#/projects",
number: scope.data.projects.total,
label: "Projects"
},
{
url: "/#/projects/?status=failed",
number: scope.data.projects.failed,
label: "Projects Sync Failures",
isFailureCount: true
}
], function(val) { return addFailureToCount(val); });
}
}
};
}
];

View File

@ -0,0 +1,10 @@
<div class="DashboardCounts">
<a class="DashboardCounts-buttonStyle" ng-repeat="count in counts" ng-href="{{ count.url }}"
ng-class="{'is-failure': {{ count.isFailure }} }">
<div class="DashboardCounts-number"
ng-class="{'is-failure': {{ count.isFailure }} }">
{{ count.number }}
</div>
<h6 class="DashboardCounts-label" >{{ count.label }}</h6>
</a>
</div>

View File

@ -0,0 +1,5 @@
import dashboardCountsDirective from 'tower/dashboard/counts/dashboard-counts.directive';
export default
angular.module('DashboardCountModules', [])
.directive('dashboardCounts', dashboardCountsDirective);

View File

@ -0,0 +1,43 @@
/** @define Dashboard */
.Dashboard {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
}
.Dashboard-counts {
flex: initial;
width: 100%;
border: 1px solid #a9a9a9;
border-radius: 4px;
padding: 9px;
}
.Dashboard-graphs {
flex: initial;
width: 100%;
}
.Dashboard-list {
display: flex;
border: 1px solid #a9a9a9;
border-radius: 4px;
margin-top: 15px;
width: 50%;
padding: 15px;
flex-basis: ~"calc(50% - 7px)";
}
@media only screen and (max-width: 710px) {
.Dashboard-counts {
border: 0;
padding: 0;
}
.Dashboard-list {
flex: initial;
width: 100%;
}
}

View File

@ -0,0 +1,13 @@
/* jshint unused: vars */
export default
[ '$rootScope',
function() {
return {
restrict: 'E',
scope: true,
templateUrl: '/static/js/dashboard/dashboard.partial.html',
link: function(scope, element, attrs) {
}
};
}
];

View File

@ -0,0 +1,11 @@
<div class="Dashboard">
<dashboard-counts class="Dashboard-counts" data="dashboardData"></dashboard-counts>
<dashboard-graphs class="Dashboard-graphs"></dashboard-graphs>
<job-templates-list class="Dashboard-list
Dashboard-list--jobTemplates"
data="dashboardJobTemplatesListData">
</job-templates-list>
<jobs-list class="Dashboard-list Dashboard-list--jobs"
data="dashboardJobsListData">
</jobs-list>
</div>

View File

@ -0,0 +1,101 @@
/** @define DashboardGraphs */
.DashboardGraphs {
display: flex;
flex-direction: column;
margin-top: 15px;
border: solid 1px #a9a9a9;
border-radius: 4px;
}
.DashboardGraphs-tabSection {
flex: 1;
display: flex;
}
.DashboardGraphs-tab {
flex: 1;
padding: 10px;
border-right: solid 1px #a9a9a9;
border-bottom: solid 1px #a9a9a9;
text-align: center;
font-size: 20px;
background-color: #ccc;
color: #545454;
background-color: #EAEAEA;
}
.DashboardGraphs-tab--firstTab {
border-top-left-radius: 4px;
}
.DashboardGraphs-tab--lastTab {
border-top-right-radius: 4px;
border-right: 0;
}
.DashboardGraphs-tab:hover {
color: #000;
cursor: pointer;
}
.DashboardGraphs-tab.is-selected {
background-color: #fff;
color: #000;
border-bottom: 0;
}
.DashboardGraphs-tab.is-selected:hover {
cursor: default;
}
.DashboardGraphs-graphSection {
display: block;
flex: 1;
}
.DashboardGraphs-graphContainer {
width: 0%;
display: none;
}
.DashboardGraphs-graphContainer.is-selected {
width: 100%;
display: block;
}
.DashboardGraphs-graph {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
margin-bottom: 19px;
padding: 20px;
}
.DashboardGraphs-graphToolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 6px;
}
.DashboardGraphs-filterDropdown {
flex: initial;
font-size: 12px;
padding-right: 15px;
}
.DashboardGraphs-filterDropdownItems {
position: fixed;
left: initial;
top: initial;
box-shadow: none;
}
.DashboardGraphs-filterDropdownItems--period {
margin-left: -42px;
}
.DashboardGraphs-filterDropdownItems--jobType {
margin-left: -84px;
}

View File

@ -0,0 +1,31 @@
/* jshint unused: vars */
export default
[ '$rootScope',
function() {
return {
restrict: 'E',
scope: true,
templateUrl: '/static/js/dashboard/graphs/dashboard-graphs.partial.html',
link: function(scope, element, attrs) {
function clearGraphs() {
scope.jobStatusSelected = false;
scope.hostStatusSelected = false;
}
scope.toggleGraphStatus = function (graphType) {
clearGraphs();
if (graphType === "jobStatus") {
scope.jobStatusSelected = true;
} else if (graphType === "hostStatus") {
scope.hostStatusSelected = true;
}
scope.$broadcast("resizeGraphs");
};
// initially toggle jobStatus graph
clearGraphs();
scope.toggleGraphStatus("jobStatus");
}
};
}
];

View File

@ -0,0 +1,32 @@
<div class="DashboardGraphs">
<div class="DashboardGraphs-tabSection">
<div class="DashboardGraphs-tab DashboardGraphs-tab--firstTab"
ng-click="toggleGraphStatus('jobStatus')"
ng-class="{'is-selected': jobStatusSelected }">
Job Status
</div>
<div class="DashboardGraphs-tab DashboardGraphs-tab--lastTab"
ng-click="toggleGraphStatus('hostStatus')"
ng-class="{'is-selected': hostStatusSelected }">
Host Status
</div>
</div>
<div class="DashboardGraphs-graphSection">
<div class="DashboardGraphs-graphContainer" auto-size-module
graph-type="jobsStatus"
ng-class="{'is-selected': jobStatusSelected }">
<job-status-graph class="DashboardGraphs-graph
DashboardGraphs-graph--jobStatusGraph"
data="graphData.jobStatus" period="month" job-type="all">
</job-status-graph>
</div>
<div class="DashboardGraphs-graphContainer" auto-size-module
graph-type="hostStatus"
ng-class="{'is-selected': hostStatusSelected }">
<host-status-graph class="DashboardGraphs-graph
DashboardGraphs-graph--hostStatusGraph"
data="dashboardData">
</host-status-graph>
</div>
</div>
</div>

View File

@ -53,37 +53,35 @@ export default function() {
// Calling chartModel.update() at the end instructs nv to process our changes. // Calling chartModel.update() at the end instructs nv to process our changes.
// //
return function adjustGraphSize(chartModel, element) { return function adjustGraphSize(chartModel, element) {
var parentHeight = element.parent().parent().height(); if (chartModel) {
var toolbarHeight = element.find('.toolbar').height(); var margins = chartModel.margin();
var container = element.find('svg').parent(); var graph = d3.select(element.find('svg')[0]);
var margins = chartModel.margin(); var width = parseInt(graph.style('width')) - margins.left - margins.right;
// var height = parseInt(graph.style('height')) - margins.top - margins.bottom;
// console.log(height);
var height = 200;
var newHeight = parentHeight - toolbarHeight - margins.bottom; chartModel.xRange([0, width]);
chartModel.yRange([height, 0]);
$(container).height(newHeight); chartModel.xAxis.ticks(Math.max(width / 75, 2));
chartModel.yAxis.ticks(Math.max(height / 50, 2));
var graph = d3.select(element.find('svg')[0]); if (height < 160) {
var width = parseInt(graph.style('width')) - margins.left - margins.right; graph.select('.y.nv-axis').select('.domain').style('display', 'none');
var height = parseInt(graph.style('height')) - margins.top - margins.bottom; graph.select('.y.nv-axis').select('.domain').style('display', 'initial');
}
chartModel.xRange([0, width]); graph.select('.x.nv-axis')
chartModel.yRange([height, 0]); .attr('transform', 'translate(0, ' + height + ')')
.call(chartModel.xAxis);
chartModel.xAxis.ticks(Math.max(width / 75, 2)); graph.selectAll('.line')
chartModel.yAxis.ticks(Math.max(height / 50, 2)); .attr('d', chartModel.lines);
if (height < 160) { if (chartModel.update) {
graph.select('.y.nv-axis').select('.domain').style('display', 'none'); chartModel.update();
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();
}; };
} }

View File

@ -9,16 +9,18 @@ function AutoSizeModule($window) {
// fit into a single a page; assumes there are 2 rows // fit into a single a page; assumes there are 2 rows
// of modules, with the available height being offset // of modules, with the available height being offset
// by the navbar & the count summaries module // by the navbar & the count summaries module
return function(scope, element) { return function(scope, element, attrs) {
function adjustSizeInitially() {
adjustSize();
}
function adjustSize() { function adjustSize() {
var winHeight = $($window).height(), if (attrs.graphType === "hostStatus") {
available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; if (element.parent().width() > 596) {
element.height(available_height/2); element.height(596);
} else {
element.height(element.parent().width());
}
} else {
element.height(320);
}
} }
$($window).resize(adjustSize); $($window).resize(adjustSize);
@ -30,8 +32,8 @@ function AutoSizeModule($window) {
// This makes sure count-container div is loaded // This makes sure count-container div is loaded
// by controllers/Home.js before we use it // by controllers/Home.js before we use it
// to determine the available window height // to determine the available window height
scope.$on('dashboardReady', function() { scope.$on('resizeGraphs', function() {
adjustSizeInitially(); adjustSize();
}); });
}; };

View File

@ -0,0 +1,6 @@
import AutoSize from 'tower/dashboard/graphs/graph-helpers/auto-size.directive';
import AdjustGraphSize from 'tower/dashboard/graphs/graph-helpers/adjust-graph-size.service';
export default angular.module('DashboardGraphHelpers', [])
.directive('autoSizeModule', AutoSize)
.service('adjustGraphSize', AdjustGraphSize);

View File

@ -1,14 +1,15 @@
export default export default
[ '$compile', [ '$compile',
'$window', '$window',
HostStatusGraph 'adjustGraphSize',
HostStatusGraph,
]; ];
function HostStatusGraph($compile, $window) { function HostStatusGraph($compile, $window, adjustGraphSize) {
return { return {
restrict: 'E', restrict: 'E',
link: link, link: link,
templateUrl: '/static/partials/host_status_graph.html' templateUrl: '/static/js/dashboard/graphs/host-status/host_status_graph.partial.html'
}; };
function link(scope, element, attr) { function link(scope, element, attr) {
@ -20,12 +21,10 @@ function HostStatusGraph($compile, $window) {
} }
}); });
function adjustGraphSize() { function adjustHostGraphSize() {
if (angular.isUndefined(host_pie_chart)) { if (angular.isUndefined(host_pie_chart)) {
return; return;
} }
var parentHeight = element.parent().parent().height(); var parentHeight = element.parent().parent().height();
var toolbarHeight = element.find('.toolbar').height(); var toolbarHeight = element.find('.toolbar').height();
var container = element.find('svg').parent(); var container = element.find('svg').parent();
@ -38,10 +37,12 @@ function HostStatusGraph($compile, $window) {
host_pie_chart.update(); host_pie_chart.update();
} }
angular.element($window).on('resize', adjustGraphSize); angular.element($window).on('resize', adjustHostGraphSize);
$(".DashboardGraphs-graph--hostStatusGraph").resize(adjustHostGraphSize);
element.on('$destroy', function() { element.on('$destroy', function() {
angular.element($window).off('resize', adjustGraphSize); angular.element($window).off('resize', adjustHostGraphSize);
$(".DashboardGraphs-graph--hostStatusGraph").removeResize(adjustHostGraphSize);
}); });
function createGraph(data) { function createGraph(data) {
@ -58,7 +59,7 @@ function HostStatusGraph($compile, $window) {
]; ];
host_pie_chart = nv.models.pieChart() host_pie_chart = nv.models.pieChart()
.margin({top: 5, right: 75, bottom: 25, left: 85}) .margin({bottom: 15})
.x(function(d) { return d.label; }) .x(function(d) { return d.label; })
.y(function(d) { return d.value; }) .y(function(d) { return d.value; })
.showLabels(true) .showLabels(true)
@ -101,6 +102,7 @@ function HostStatusGraph($compile, $window) {
element.find('svg').replaceWith(notFoundContainer); element.find('svg').replaceWith(notFoundContainer);
} }
} }
} }
} }

View File

@ -0,0 +1,3 @@
<div class="graph">
<svg width="100%" height="100%" preserveAspectRatio="xMinYMin"></svg>
</div>

View File

@ -0,0 +1,5 @@
import HostStatusGraphDirective from 'tower/dashboard/graphs/host-status/host-status-graph.directive';
import DashboardGraphHelpers from 'tower/dashboard/graphs/graph-helpers/main';
export default angular.module('HostStatusGraph', [DashboardGraphHelpers.name])
.directive('hostStatusGraph', HostStatusGraphDirective);

View File

@ -15,7 +15,7 @@ function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustG
scope: { scope: {
data: '=' data: '='
}, },
templateUrl: '/static/partials/job_status_graph.html', templateUrl: '/static/js/dashboard/graphs/job-status/job_status_graph.partial.html',
link: link link: link
}; };
@ -38,10 +38,10 @@ function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustG
scope.period = period; scope.period = period;
scope.jobType = jobType; scope.jobType = jobType;
}); });
} }
function createGraph(period, jobtype, data){ function createGraph(period, jobtype, data){
scope.period = period; scope.period = period;
scope.jobType = jobtype; scope.jobType = jobtype;
@ -73,7 +73,6 @@ function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustG
}); });
job_status_chart 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; }) .x(function(d,i) { return i; })
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
.showLegend(true) //Show the legend, allowing users to turn on/off line series. .showLegend(true) //Show the legend, allowing users to turn on/off line series.
@ -120,17 +119,23 @@ function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustG
recreateGraph(period, job_type); recreateGraph(period, job_type);
}); });
job_status_chart.legend.margin({top: 1, right:0, left:24, bottom: 0});
adjustGraphSize(job_status_chart, element); adjustGraphSize(job_status_chart, element);
} }
function onResize() { function onResize() {
adjustGraphSize(job_status_chart, element); adjustGraphSize(job_status_chart, element);
} }
angular.element($window).on('resize', onResize); angular.element($window).on('resize', onResize);
$(".DashboardGraphs-graph--jobStatusGraph").resize(onResize);
element.on('$destroy', function() { element.on('$destroy', function() {
angular.element($window).off('resize', onResize); angular.element($window).off('resize', onResize);
$(".DashboardGraphs-graph--jobStatusGraph").removeResize(onResize);
}); });
if (scope.removeGraphDataReady) { if (scope.removeGraphDataReady) {

View File

@ -39,12 +39,12 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) {
setupWatcher: function(period, jobType) { setupWatcher: function(period, jobType) {
this.destroyWatcher = this.destroyWatcher =
$rootScope.$on('JobStatusChange-home', function() { $rootScope.$on('JobStatusChange-home', function() {
getData(period, jobType).then(function(result) { getData(period, jobType).then(function(result) {
$rootScope. $rootScope.
$broadcast('DataReceived:JobStatusGraph', $broadcast('DataReceived:JobStatusGraph',
result); result);
return result; return result;
}); });
}); });
}, },
get: function(period, jobType) { get: function(period, jobType) {

View File

@ -0,0 +1,49 @@
<div class="DashboardGraphs-graphToolbar">
<div class="DashboardGraphs-filterDropdown">
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 DashboardGraphs-filterDropdownItems
DashboardGraphs-filterDropdownItems--period" 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="DashboardGraphs-filterDropdown">
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 DashboardGraphs-filterDropdownItems
DashboardGraphs-filterDropdownItems--jobType" 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>

View File

@ -0,0 +1,8 @@
import JobStatusGraphDirective from 'tower/dashboard/graphs/job-status/job-status-graph.directive';
import JobStatusGraphService from 'tower/dashboard/graphs/job-status/job-status-graph.service';
import DashboardGraphHelpers from 'tower/dashboard/graphs/graph-helpers/main';
import ApiLoader from 'tower/shared/api-loader';
export default angular.module('JobStatusGraph', [DashboardGraphHelpers.name, ApiLoader.name])
.directive('jobStatusGraph', JobStatusGraphDirective)
.service('jobStatusGraphData', JobStatusGraphService);

View File

@ -0,0 +1,7 @@
import hostStatus from 'tower/dashboard/graphs/host-status/main';
import jobStatus from 'tower/dashboard/graphs/job-status/main';
import dashboardGraphsDirective from 'tower/dashboard/graphs/dashboard-graphs.directive';
export default
angular.module('DashboardGraphModules', [hostStatus.name, jobStatus.name])
.directive('dashboardGraphs', dashboardGraphsDirective);

View File

@ -0,0 +1,124 @@
/** @define DashboardJobTemplates */
.DashboardJobTemplates {
flex: 1;
display: flex;
flex-direction: column;
}
.DashboardJobTemplates--noJobTemplates {
color: #8d8d8d;
}
.DashboardJobTemplates-header {
flex: initial;
margin-top: 0;
font-size: 20px;
}
.DashboardJobTemplates-container {
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
padding-bottom: 15px;
}
.DashboardJobTemplates-item {
flex: 1;
display: flex;
flex-wrap: wrap;
padding-bottom: 7px;
padding-top: 5px;
border-bottom: 1px solid #a9a9a9;
align-items: center;
}
.DashboardJobTemplates-item--snapRows {
flex: initial;
}
.DashboardJobTemplates-item:last-of-type {
border-bottom: 0px;
}
.DashboardJobTemplates-seeMore {
padding-top: 11px;
padding-bottom: 11px;
width: ~"calc(100% + 32px)";
margin-left: -16px;
margin-bottom: -16px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
color: #fff;
font-size: 17px;
text-align: center;
background-color: #1778c3;
}
.DashboardJobTemplates-seeMore:hover {
font-weight: 600;
color: #fff;
}
.DashboardJobTemplates-smartStatus {
flex: initial;
width: 88px;
margin-left: 10px;
}
.DashboardJobTemplates-name {
flex: 1;
}
.DashboardJobTemplates-name:hover {
font-weight: 700;
color: #2a6496;
cursor: pointer;
}
.DashboardJobTemplates-launch {
font-size: 25px;
height: 28px;
width: 27px;
margin-left: 1px;
color: #1778c3;
margin-right: 13px;
flex: initial;
}
.DashboardJobTemplates-launch:hover {
margin-left: 0px;
width: 28px;
font-size: 28px;
color: #2a6496;
cursor: pointer;
}
@media only screen and (max-width: 710px) {
.DashboardJobTemplates-item {
padding-bottom: 9px;
padding-top: 7px;
}
.DashboardJobTemplates-name {
font-size:19px;
border-left: 1px solid #a9a9a9;
padding-left: 14px;
}
.DashboardJobTemplates-launch {
font-size: 35px;
height: 35px;
width: 30px;
margin-left: 10px;
margin-right: 16px;
}
.DashboardJobTemplates-launch:hover {
margin-left: 9px;
width: 31px;
font-size: 38px;
}
}

View File

@ -0,0 +1,47 @@
/* jshint unused: vars */
export default
[ "PlaybookRun",
function JobTemplatesList(PlaybookRun) {
return {
restrict: 'E',
link: link,
scope: {
data: '='
},
templateUrl: '/static/js/dashboard/lists/job-templates/job-templates-list.partial.html'
};
function link(scope, element, attr) {
scope.$watch("data", function(data) {
if (data) {
if (data.length > 0) {
createList(data);
scope.noJobTemplates = false;
} else {
scope.noJobTemplates = true;
}
}
});
function createList(list) {
// smartStatus?, launchUrl, editUrl, name
scope.job_templates = _.map(list, function(job_template){ return {
recent_jobs: job_template.summary_fields.recent_jobs,
launch_url: job_template.url,
edit_url: job_template.url.replace('api/v1', '#'),
name: job_template.name,
id: job_template.id
}; });
scope.snapRows = (list.length < 4);
}
scope.isSuccessful = function (status) {
return (status === "successful");
};
scope.launchJobTemplate = function(jobTemplateId){
PlaybookRun({ scope: scope, id: jobTemplateId });
};
}
}];

View File

@ -0,0 +1,28 @@
<div class="DashboardJobTemplates" ng-hide="noJobTemplates">
<h3 class="DashboardJobTemplates-header">
Recently Used Job Templates
</h3>
<div class="DashboardJobTemplates-container">
<div class="DashboardJobTemplates-item"
ng-class="{'DashboardJobTemplates-item--snapRows': {{ snapRows }}}"
ng-repeat="job_template in job_templates">
<i class="fa fa-rocket
DashboardJobTemplates-launch"
ng-click="launchJobTemplate(job_template.id)"></i>
<a href="#/job_templates/{{ job_template.id }}" class="DashboardJobTemplates-name">
{{ job_template.name }}
</a>
<aw-smart-status jobs="job_template.recent_jobs" class="DashboardJobTemplates-smartStatus"></aw-smart-status>
</div>
</div>
<a href="/#/job_templates" class="DashboardJobTemplates-seeMore">
See all job templates
</a>
</div>
<div class="DashboardJobTemplates DashboardJobTemplates--noJobTemplates" ng-show="noJobTemplates">
<h3 class="DashboardJobTemplates-header">
Recent Job Runs
</h3>
<p>It doesn't seem like you have used any job templates.<br />
You can create a job template <a href="#/job_templates/add">here</a>.</p>
</div>

View File

@ -0,0 +1,6 @@
import JobTemplatesListDirective from 'tower/dashboard/lists/job-templates/job-templates-list.directive';
import systemStatus from 'tower/smart-status/main';
import jobSubmissionHelper from 'tower/helpers/JobSubmission';
export default angular.module('JobTemplatesList', [systemStatus.name, jobSubmissionHelper.name])
.directive('jobTemplatesList', JobTemplatesListDirective);

View File

@ -0,0 +1,113 @@
/** @define DashboardJobs */
.DashboardJobs {
flex: 1;
display: flex;
flex-direction: column;
}
.DashboardJobs--noJobs {
color: #8d8d8d;
}
.DashboardJobs-header {
flex: initial;
margin-top: 0;
font-size: 20px;
}
.DashboardJobs-container {
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
padding-bottom: 15px;
}
.DashboardJobs-item {
flex: 1;
display: flex;
flex-wrap: wrap;
padding-bottom: 0;
padding-top: 0;
border-bottom: 1px solid #a9a9a9;
align-items: center;
}
.DashboardJobs-item--snapRows {
flex: initial;
}
.DashboardJobs-itemLink {
flex: 2;
display: flex;
padding-bottom: 9px;
padding-top: 7px;
align-items: center;
}
.DashboardJobs-status {
flex: initial;
height: 18px;
width: 23px;
font-size: 18px;
margin-bottom: -3px;
}
.DashboardJobs-nameContainer {
flex: 1;
margin-left: 10px;
}
.DashboardJobs-time {
flex: initial;
text-align: right;
margin-bottom: 0px;
margin-left: 10px;
color: #000;
}
.DashboardJobs-itemLink:hover {
.DashboardJobs-nameContainer,
.DashboardJobs-time {
font-weight: 600;
}
.DashboardJobs-status--success {
color: #5DF370;
}
.DashboardJobs-status--failed {
color: #FF1105;
}
}
.DashboardJobs-item:last-of-type {
border-bottom: 0px;
}
.DashboardJobs-seeMore {
padding-top: 11px;
padding-bottom: 11px;
width: ~"calc(100% + 32px)";
margin-left: -16px;
margin-bottom: -16px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
color: #fff;
font-size: 17px;
text-align: center;
background-color: #1778c3;
}
.DashboardJobs-seeMore:hover {
font-weight: 600;
color: #fff;
}
@media only screen and (max-width: 710px) {
.DashboardJobs-nameContainer {
font-size: 19px;
}
}

View File

@ -0,0 +1,42 @@
/* jshint unused: vars */
export default
[function JobsList() {
return {
restrict: 'E',
link: link,
scope: {
data: '='
},
templateUrl: '/static/js/dashboard/lists/jobs/jobs-list.partial.html'
};
function link(scope, element, attr) {
scope.$watch("data", function(data) {
if (data) {
if (data.length > 0) {
createList(data);
scope.noJobs = false;
} else {
scope.noJobs = true;
}
}
});
function createList(list) {
// detailsUrl, status, name, time
scope.jobs = _.map(list, function(job){
return {
detailsUrl: job.url.replace("api/v1", "#"),
status: job.status,
name: job.name,
time: moment(job.finished).fromNow()
}; });
scope.snapRows = (list.length < 4);
}
scope.isSuccessful = function (status) {
return (status === "successful");
};
}
}];

View File

@ -0,0 +1,35 @@
<div class="DashboardJobs" ng-hide="noJobs">
<h3 class="DashboardJobs-header">
Recent Job Runs
</h3>
<div class="DashboardJobs-container">
<div class="DashboardJobs-item"
ng-class="{'DashboardJobs-item--snapRows': {{ snapRows }}}"
ng-repeat="job in jobs">
<a ng-href="{{ job.detailsUrl }}"
class="DashboardJobs-itemLink">
<i class="fa
DashboardJobs-status"
ng-class="{'DashboardJobs-status--success icon-job-successful': isSuccessful(job.status),
'DashboardJobs-status--failed icon-job-failed': !isSuccessful(job.status)}">
</i>
<span class="DashboardJobs-nameContainer">
{{job.name}}
</span>
<div class="DashboardJobs-time">
{{ job.time }}
</div>
</a>
</div>
</div>
<a href="/#/jobs" class="DashboardJobs-seeMore">
See all job runs
</a>
</div>
<div class="DashboardJobs DashboardJobs--noJobs" ng-show="noJobs">
<h3 class="DashboardJobs-header">
Recent Job Runs
</h3>
<p>It doesn't seem like you have any recent job runs.</p>
</div>

View File

@ -0,0 +1,4 @@
import JobsListDirective from 'tower/dashboard/lists/jobs/jobs-list.directive';
export default angular.module('JobsList', [])
.directive('jobsList', JobsListDirective);

View File

@ -0,0 +1,5 @@
import jobTemplates from 'tower/dashboard/lists/job-templates/main';
import jobs from 'tower/dashboard/lists/jobs/main';
export default
angular.module('DashboardListsModules', [jobTemplates.name, jobs.name]);

View File

@ -0,0 +1,8 @@
import dashboardCounts from 'tower/dashboard/counts/main';
import dashboardGraphs from 'tower/dashboard/graphs/main';
import dashboardLists from 'tower/dashboard/lists/main';
import dashboardDirective from 'tower/dashboard/dashboard.directive';
export default
angular.module('dashboard', [dashboardCounts.name, dashboardGraphs.name, dashboardLists.name])
.directive('dashboard', dashboardDirective);

View File

@ -1,12 +0,0 @@
import JobStatusGraph from 'tower/directives/job-status-graph';
import HostCountGraph from 'tower/directives/host-count-graph';
import HostStatusGraph from 'tower/directives/host-status-graph';
import AutoSizeModule from 'tower/directives/auto-size-module';
import AdjustGraphSize from 'tower/services/adjust-graph-size';
export default angular.module('DashboardGraphs', [])
.directive('jobStatusGraph', JobStatusGraph)
.directive('hostCountGraph', HostCountGraph)
.directive('hostStatusGraph', HostStatusGraph)
.directive('autoSizeModule', AutoSizeModule)
.service('adjustGraphSize', AdjustGraphSize);

View File

@ -1,124 +0,0 @@
export default
[ 'adjustGraphSize',
'$window',
HostCountGraph
];
function HostCountGraph(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', onResize);
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!
.duration(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;
}
}
}

View File

@ -744,7 +744,7 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
launch_url, launch_url,
html; html;
scope.job_template_id = id; scope.job_template_id = id;
if (base === 'job_templates' || base === 'portal' || base === 'inventories') { if (base === 'job_templates' || base === 'portal' || base === 'inventories' || base === 'home') {
url = GetBasePath('job_templates') + id + '/launch/'; url = GetBasePath('job_templates') + id + '/launch/';
} }
else { else {
@ -777,7 +777,8 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
} }
scope.removePlaybookLaunchFinished = scope.$on('PlaybookLaunchFinished', function(e, data) { scope.removePlaybookLaunchFinished = scope.$on('PlaybookLaunchFinished', function(e, data) {
var job = data.job || data.system_job; var job = data.job || data.system_job;
if((scope.portalMode===false || scope.$parent.portalMode===false ) && Empty(data.system_job)){ if((scope.portalMode===false || scope.$parent.portalMode===false ) && Empty(data.system_job) ||
(base === 'home')){
$location.path('/jobs/' + job); $location.path('/jobs/' + job);
} }

View File

@ -1,7 +0,0 @@
import JobStatusGraphData from 'tower/services/job-status-graph-data';
import HostCountGraphData from 'tower/services/host-count-graph-data';
export default
angular.module('DataServices', ['ApiLoader'])
.service('jobStatusGraphData', JobStatusGraphData)
.service('hostCountGraphData', HostCountGraphData);

View File

@ -1,47 +0,0 @@
export default
[ "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 $q.reject(response);
});
}
};
}

View File

@ -6,14 +6,15 @@ export default [ function() {
}, },
restrict: 'E', restrict: 'E',
link: function (scope, element){ link: function (scope, element){
scope.formatter = function(sparklines, options, point){ scope.formatter = function(sparklines, options, point){
var status = options.userOptions.tooltipValueLookups.status[point.offset]; var status = options.userOptions.tooltipValueLookups.status[point.offset];
//capitalize first letter //capitalize first letter
status = status.charAt(0).toUpperCase() + status.slice(1); if (status) {
return "<div class=\"smart-status-tooltip\">Job ID: " + status = status.charAt(0).toUpperCase() + status.slice(1);
options.userOptions.tooltipValueLookups.jobs[point.offset] + return "<div class=\"smart-status-tooltip\">Job ID: " +
"<br>Status: <span style=\"color: " + point.color + "\">&#9679;</span>"+status+"</div>" ; options.userOptions.tooltipValueLookups.jobs[point.offset] +
"<br>Status: <span style=\"color: " + point.color + "\">&#9679;</span>"+status+"</div>" ;
}
}; };
element.sparkline(scope.sparkArray, { element.sparkline(scope.sparkArray, {

View File

@ -1,12 +1,6 @@
import "tower/widgets/DashboardCounts";
import "tower/widgets/DashboardJobs";
import "tower/widgets/HostGraph";
import "tower/widgets/HostPieChart";
import "tower/widgets/InventorySyncStatus"; import "tower/widgets/InventorySyncStatus";
import "tower/widgets/JobStatus"; import "tower/widgets/JobStatus";
import "tower/widgets/JobStatusGraph";
import "tower/widgets/ObjectCount"; import "tower/widgets/ObjectCount";
import "tower/widgets/PortalJobs"; import "tower/widgets/PortalJobs";
import "tower/widgets/SCMSyncStatus"; import "tower/widgets/SCMSyncStatus";
import "tower/widgets/Stream"; import "tower/widgets/Stream";

View File

@ -1,60 +0,0 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc overview
* @name widgets
* @description Various widgets, including widgets on the dashboard
/**
* @ngdoc function
* @name widgets.function:DashboardCounts
* @description
* The dashboard widget with stats across the top
*
*/
angular.module('DashboardCountsWidget', ['RestServices', 'Utilities'])
.factory('DashboardCounts', ['$rootScope', '$compile', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
function ($rootScope, $compile) {
return function (params) {
var scope = params.scope,
target = params.target,
dashboard = params.dashboard,
html, element;
html = "<div class=\"container\" >\n";
html = "<div id=\"count-container\" class=\"count-container row\">\n";
html += "<div class=\"h2 col-xs-4 col-sm-2 text-center\"><a href=\"/#/home/hosts\">"+ dashboard.hosts.total+"</a><br><h6>Hosts</h6></div>\n";
html += "<div class=\"h2 col-xs-4 col-sm-2 text-center\"><a href=\"/#/home/hosts/?has_active_failures=true\" id=\"failed-hosts\">"+dashboard.hosts.failed+"</a><br><h6>Failed Hosts</h6></div>\n";
html += "<div class=\"h2 col-xs-4 col-sm-2 text-center\"><a href=\"/#/inventories\">"+dashboard.inventories.total+"</a><br><h6>Inventories</h6></div>\n";
html += "<div class=\"h2 col-xs-4 col-sm-2 text-center\"><a href=\"/#/inventories/?inventory_sources_with_failures\" id=\"failed-inventories\">"+dashboard.inventories.inventory_failed+"</a><br><h6>Inventory Sync Failures</h6></div>\n";
html += "<div class=\"h2 col-xs-4 col-sm-2 text-center\"><a href=\"/#/projects\">"+dashboard.projects.total+"</a><br><h6>Projects</h6></div>\n";
html += "<div class=\"h2 col-xs-4 col-sm-2 text-center\"><a href=\"/#/projects/?status=failed\" id=\"failed-projects\">"+dashboard.projects.failed+"</a><br><h6>Project Sync Failures</h6></div>\n";
// html += "<div class=\"h2 col-xs-4 col-sm-2 text-center\"><a href=/#/users>"+dashboard.users.total+"</a></div>\n";
html += "</div>\n";
html += "</div>\n";
element = angular.element(document.getElementById(target));
element.html(html);
$compile(element)(scope);
if(dashboard.hosts.failed>0 ){
$('#failed-hosts').replaceWith("<a style=\"color: #ff5850\" href=\"/#/home/hosts/?has_active_failures=true\" id=\"failed-hosts\">"+dashboard.hosts.failed+"</a>");
}
if(dashboard.inventories.inventory_failed>0 ){
$('#failed-inventories').replaceWith("<a style=\"color: #ff5850\" href=/#/inventories/?inventory_sources_with_failures id=\"failed-inventories\">"+dashboard.inventories.inventory_failed+"</a>");
}
if(dashboard.projects.failed>0 ){
$('#failed-projects').replaceWith("<a style=\"color: #ff5850\" href=\"/#/projects/?status=failed\" id=\"failed-projects\">"+dashboard.projects.failed+"</a>");
}
scope.$emit('WidgetLoaded');
};
}
]);

View File

@ -1,183 +0,0 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name widgets.function:DashboardJobs
* @description
*
*/
angular.module('DashboardJobsWidget', ['RestServices', 'Utilities'])
.factory('DashboardJobs', ['$rootScope', '$compile', 'LoadSchedulesScope', 'LoadJobsScope', 'JobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath',
function ($rootScope, $compile, LoadSchedulesScope, LoadJobsScope, JobsList, ScheduledJobsList, GetChoices, GetBasePath) {
return function (params) {
var scope = params.scope,
target = params.target,
choicesCount = 0,
listCount = 0,
jobs_scope = scope.$new(true),
scheduled_scope = scope.$new(true),
max_rows,
html, e;
html = '';
html += "<div class=\"dashboard-jobs-list-container\">\n";
html += "<ul id=\"job_status_tabs\" class=\"nav nav-tabs\">\n";
html += "<li class=\"active\"><a id=\"active_jobs_link\" ng-click=\"toggleTab($event, 'active_jobs_link', 'job_status_tabs')\"\n";
html += " href=\"#active-jobs-tab\" data-toggle=\"tab\">Jobs</a></li>\n";
html += "<li><a id=\"scheduled_jobs_link\" ng-click=\"toggleTab($event, 'scheduled_jobs_link', 'job_status_tabs')\"\n";
html += "href=\"#scheduled-jobs-tab\" data-toggle=\"tab\">Schedule</a></li>\n";
html += "</ul>\n";
html += "<div id=\"dashboard-tab-content\" class=\"tab-content \">\n";
html += "<div class=\"tab-pane active\" id=\"active-jobs-tab\">\n";
html += "<div class=\"row search-row\">\n";
html += "<div class=\"col-lg-6 col-md-6\" id=\"active-jobs-search-container\"></div>\n";
html += "</div>\n"; //row
html += "<div class=\"job-list\" id=\"active-jobs-container\">\n";
html += "<div id=\"active-jobs\" class=\"job-list-target\"></div>\n";
html += "</div>\n"; //list
html += "</div>\n"; //active-jobs-tab
html += "<div class=\"tab-pane\" id=\"scheduled-jobs-tab\"></div>\n";
html += "</div>\n"; // jobs-list-container
html += "</div>\n";
e = angular.element(document.getElementById(target));
e.html(html);
$compile(e)(scope);
$rootScope.$on('JobStatusChange-home', function() {
jobs_scope.refreshJobs();
});
if (scope.removeListLoaded) {
scope.removeListLoaded();
}
scope.removeListLoaded = scope.$on('listLoaded', function() {
listCount++;
if (listCount === 1) {
//api_complete = true;
scope.$emit('WidgetLoaded', "dashboard_jobs", jobs_scope, scheduled_scope);
}
});
// After all choices are ready, load up the lists and populate the page
if (scope.removeBuildJobsList) {
scope.removeBuildJobsList();
}
scope.removeBuildJobsList = scope.$on('buildJobsList', function() {
if (JobsList.fields.type) {
JobsList.fields.type.searchOptions = scope.type_choices;
}
LoadJobsScope({
parent_scope: scope,
scope: jobs_scope,
list: JobsList,
id: 'active-jobs',
url: GetBasePath('unified_jobs') + '?status__in=pending,running,completed,failed,successful,error,canceled',
pageSize: max_rows,
spinner: false
});
LoadSchedulesScope({
parent_scope: scope,
scope: scheduled_scope,
list: ScheduledJobsList,
id: 'scheduled-jobs-tab',
url: GetBasePath('schedules') + '?next_run__isnull=false',
pageSize: max_rows,
spinner: false
});
$(window).resize(_.debounce(function() {
resizeDashboardJobsWidget();
}, 500));
});
if (scope.removeChoicesReady) {
scope.removeChoicesReady();
}
scope.removeChoicesReady = scope.$on('choicesReady', function() {
choicesCount++;
if (choicesCount === 2) {
setDashboardJobsHeight();
scope.$emit('buildJobsList');
}
});
GetChoices({
scope: scope,
url: GetBasePath('unified_jobs'),
field: 'status',
variable: 'status_choices',
callback: 'choicesReady'
});
GetChoices({
scope: scope,
url: GetBasePath('unified_jobs'),
field: 'type',
variable: 'type_choices',
callback: 'choicesReady'
});
// Set the height of each container and calc max number of rows containers can hold
function setDashboardJobsHeight() {
var docw = $(window).width(),
box_height, available_height, search_row, page_row, height, header, row_height;
available_height = Math.floor(($(window).height() - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120)/2);
$('.dashboard-jobs-list-container').height(available_height);
search_row = Math.max($('.search-row:eq(0)').outerHeight(), 50);
page_row = Math.max($('.page-row:eq(0)').outerHeight(), 33);
header = Math.max($('#completed_jobs_table thead').height(), 41);
height = Math.floor(available_height) - header - page_row - search_row -30 ;
// if (docw < 765 && docw >= 493) {
// row_height = 27;
// }
if (docw < 480) {
row_height = 87;
}
else if (docw < 767) {
row_height = 44;
}
else if (docw < 926) {
row_height = 87;
}
else if (docw < 1200) {
row_height = 44;
}
else if (docw < 1415) {
row_height = 55;
}
else {
row_height = 44;
}
max_rows = Math.floor(height / row_height);
if (max_rows < 5){
box_height = header+page_row + search_row + 40 + (5 * row_height);
if (docw < 1140) {
box_height += 40;
}
$('.dashboard-jobs-list-container').height(box_height);
max_rows = 5;
}
}
// Set container height and return the number of allowed rows
function resizeDashboardJobsWidget() {
setDashboardJobsHeight();
jobs_scope[JobsList.iterator + '_page_size'] = max_rows;
jobs_scope.changePageSize(JobsList.name, JobsList.iterator, false);
scheduled_scope[ScheduledJobsList.iterator + '_page_size'] = max_rows;
scheduled_scope.changePageSize(ScheduledJobsList.name, ScheduledJobsList.iterator, false);
}
};
}
]);

View File

@ -1,180 +0,0 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name widgets.function:HostGraph
* @description
*
*/
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;
},
});
//});
});
};
}
]);

View File

@ -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
*
*/
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": "#60D66F",
"value" : dashboard.hosts.total
} ,
{
"label": "Failed",
"color" : "#ff5850",
"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(['#60D66F', '#ff5850']);
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');
}
};
}
]);

View File

@ -1,219 +0,0 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name widgets.function:JobStatusGraph
* @description
*/
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": "#60D66F",
"key": "Successful",
"values": data.jobs.successful
},
{
"key" : "Failed" ,
"color" : "#ff5850",
"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;
},
});
});
};
}
]);

View File

@ -1964,3 +1964,15 @@ tr td button i {
} }
} }
.nvtooltip {
border-radius: 4px;
}
.nvtooltip td.value {
padding-right: 0px;
}
.nvd3 g.nv-groups path.nv-line {
stroke-width: 3px;
}

View File

@ -30,4 +30,4 @@
"_source": "git://github.com/sdecima/javascript-detect-element-resize.git", "_source": "git://github.com/sdecima/javascript-detect-element-resize.git",
"_target": "~0.5.3", "_target": "~0.5.3",
"_originalSource": "javascript-detect-element-resize" "_originalSource": "javascript-detect-element-resize"
} }

View File

@ -10,9 +10,9 @@
(function ( $ ) { (function ( $ ) {
var attachEvent = document.attachEvent, var attachEvent = document.attachEvent,
stylesCreated = false; stylesCreated = false;
var jQuery_resize = $.fn.resize; var jQuery_resize = $.fn.resize;
$.fn.resize = function(callback) { $.fn.resize = function(callback) {
return this.each(function() { return this.each(function() {
if(this == window) if(this == window)
@ -27,14 +27,14 @@
removeResizeListener(this, callback); removeResizeListener(this, callback);
}); });
} }
if (!attachEvent) { if (!attachEvent) {
var requestFrame = (function(){ var requestFrame = (function(){
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
function(fn){ return window.setTimeout(fn, 20); }; function(fn){ return window.setTimeout(fn, 20); };
return function(fn){ return raf(fn); }; return function(fn){ return raf(fn); };
})(); })();
var cancelFrame = (function(){ var cancelFrame = (function(){
var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame ||
window.clearTimeout; window.clearTimeout;
@ -58,7 +58,7 @@
return element.offsetWidth != element.__resizeLast__.width || return element.offsetWidth != element.__resizeLast__.width ||
element.offsetHeight != element.__resizeLast__.height; element.offsetHeight != element.__resizeLast__.height;
} }
function scrollListener(e){ function scrollListener(e){
var element = this; var element = this;
resetTriggers(this); resetTriggers(this);
@ -73,7 +73,7 @@
} }
}); });
}; };
/* Detect CSS Animations support to detect element display/re-attach */ /* Detect CSS Animations support to detect element display/re-attach */
var animation = false, var animation = false,
animationstring = 'animation', animationstring = 'animation',
@ -84,8 +84,8 @@
pfx = ''; pfx = '';
{ {
var elm = document.createElement('fakeelement'); var elm = document.createElement('fakeelement');
if( elm.style.animationName !== undefined ) { animation = true; } if( elm.style.animationName !== undefined ) { animation = true; }
if( animation === false ) { if( animation === false ) {
for( var i = 0; i < domPrefixes.length; i++ ) { for( var i = 0; i < domPrefixes.length; i++ ) {
if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) { if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) {
@ -99,12 +99,12 @@
} }
} }
} }
var animationName = 'resizeanim'; var animationName = 'resizeanim';
var animationKeyframes = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { opacity: 0; } to { opacity: 0; } } '; var animationKeyframes = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { opacity: 0; } to { opacity: 0; } } ';
var animationStyle = keyframeprefix + 'animation: 1ms ' + animationName + '; '; var animationStyle = keyframeprefix + 'animation: 1ms ' + animationName + '; ';
} }
function createStyles() { function createStyles() {
if (!stylesCreated) { if (!stylesCreated) {
//opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360 //opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360
@ -113,7 +113,7 @@
'.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: \" \"; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }', '.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: \" \"; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }',
head = document.head || document.getElementsByTagName('head')[0], head = document.head || document.getElementsByTagName('head')[0],
style = document.createElement('style'); style = document.createElement('style');
style.type = 'text/css'; style.type = 'text/css';
if (style.styleSheet) { if (style.styleSheet) {
style.styleSheet.cssText = css; style.styleSheet.cssText = css;
@ -125,7 +125,7 @@
stylesCreated = true; stylesCreated = true;
} }
} }
window.addResizeListener = function(element, fn){ window.addResizeListener = function(element, fn){
if (attachEvent) element.attachEvent('onresize', fn); if (attachEvent) element.attachEvent('onresize', fn);
else { else {
@ -140,7 +140,7 @@
element.appendChild(element.__resizeTriggers__); element.appendChild(element.__resizeTriggers__);
resetTriggers(element); resetTriggers(element);
element.addEventListener('scroll', scrollListener, true); element.addEventListener('scroll', scrollListener, true);
/* Listen for a css animation to detect element display/re-attach */ /* Listen for a css animation to detect element display/re-attach */
animationstartevent && element.__resizeTriggers__.addEventListener(animationstartevent, function(e) { animationstartevent && element.__resizeTriggers__.addEventListener(animationstartevent, function(e) {
if(e.animationName == animationName) if(e.animationName == animationName)
@ -150,7 +150,7 @@
element.__resizeListeners__.push(fn); element.__resizeListeners__.push(fn);
} }
}; };
window.removeResizeListener = function(element, fn){ window.removeResizeListener = function(element, fn){
if (attachEvent) element.detachEvent('onresize', fn); if (attachEvent) element.detachEvent('onresize', fn);
else { else {
@ -161,4 +161,4 @@
} }
} }
} }
}( jQuery )); }( jQuery ));

View File

@ -11354,4 +11354,4 @@ nv.models.stackedAreaChart = function() {
}; };
nv.version = "1.7.1"; nv.version = "1.7.1";
})(); })();

View File

@ -1,58 +1,35 @@
<div class="tab-pane" id="home"> <div class="tab-pane" id="home">
<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">
<div id="home-list-actions" class="list-actions pull-right"> <div id="home-list-actions" class="list-actions pull-right">
<button <button
toolbar-button toolbar-button
mode="all" mode="all"
aw-tool-tip="Refresh the page" aw-tool-tip="Refresh the page"
ng-click="refresh()" ng-click="refresh()"
ng-show="socketStatus == 'error'" ng-show="socketStatus == 'error'"
icon-name="refresh" icon-name="refresh"
toolbar="true"> toolbar="true">
</button> </button>
<button <button
toolbar-button toolbar-button
mode="all" mode="all"
ng-click="showActivity()" ng-click="showActivity()"
aw-tool-tip="View Activity Stream" aw-tool-tip="View Activity Stream"
icon-name="stream" icon-name="stream"
toolbar="true" toolbar="true"
aw-feature="activity_streams"> aw-feature="activity_streams">
</button> </button>
</div>
<!-- <button type="button" class="btn btn-xs btn-primary ng-hide" ng-click="refreshJobs()" id="refresh_btn" aw-tool-tip="Refresh the page" data-placement="top" ng-show="socketStatus == 'error'" data-original-title="" title=""><i class="fa fa-refresh fa-lg"></i> </button></div> --> </div>
</div>
</div>
<div class="row">
<div id="dash-counts" class="col-sm-12 col-xs-12"></div>
</div>
<div class="row">
<div class="left-side col-lg-6 col-md-12">
<div id="dash-job-status-graph" auto-size-module class="graph-container">
<job-status-graph data="graphData.jobStatus" period="month" job-type="all"></job-status-graph>
</div>
</div> </div>
<div class="right-side col-lg-6 col-md-12">
<div id="dash-host-status-graph" auto-size-module class="graph-container">
<host-status-graph data="dashboardData"></host-status-graph>
</div>
</div>
</div>
<div class="row">
<div id="dash-jobs-list" class="left-side col-lg-6 col-md-12"></div>
<div class="right-side col-lg-6 col-md-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 ng-include="'/static/partials/schedule_dialog.html'"></div> <dashboard></dashboard>
</div>
<div>
<!-- <div ng-include="'/static/partials/schedule_dialog.html'"></div>
<div ng-include="'/static/partials/logviewer.html'"></div> <div ng-include="'/static/partials/logviewer.html'"></div>
<div id="host-modal-dialog" style="display: none;" class="dialog-content"></div> <div id="host-modal-dialog" style="display: none;" class="dialog-content"></div> -->

View File

@ -1,12 +0,0 @@
<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>

View File

@ -1,12 +0,0 @@
<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>

View File

@ -1,39 +0,0 @@
<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>

View File

@ -1,12 +1,17 @@
Alias /munin /var/www/html/munin/
<Directory /var/www/html/munin/> Alias /munin /var/cache/munin/www
<Directory /var/cache/munin/www>
Order Allow,Deny Order Allow,Deny
Allow from all Allow from all
Options FollowSymLinks
AuthUserFile /var/lib/awx/.munin_htpasswd AuthUserFile /var/lib/awx/.munin_htpasswd
AuthName "Munin" AuthName "Munin"
AuthType Basic AuthType Basic
require valid-user require valid-user
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault M310
</IfModule>
</Directory> </Directory>
ScriptAlias /munin-cgi/munin-cgi-graph /var/www/cgi-bin/munin-cgi-graph