1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 01:21:21 +03:00

Merge pull request #6447 from jaredevantabor/insights

Insights UI Integration
This commit is contained in:
Jared Tabor 2017-06-05 14:12:39 -07:00 committed by GitHub
commit 641099f941
28 changed files with 663 additions and 64 deletions

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 146 166"
style="enable-background:new 0 0 146 166;"
xml:space="preserve"
sodipodi:docname="i_critical.svg"
inkscape:version="0.92.1 r"><metadata
id="metadata4612"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs4610" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1055"
id="namedview4608"
showgrid="false"
inkscape:zoom="1.4216867"
inkscape:cx="73"
inkscape:cy="83"
inkscape:window-x="0"
inkscape:window-y="1050"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style4593">
.st0{fill:#808080;}
.st1{fill:#CC0000;}
</style><g
id="g4605"><path
class="st1"
d="M114.6,143.3H31c-7.2,0-13-4.8-13-10.6l0,0c0-5.9,5.9-10.6,13-10.6h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,138.5,121.8,143.3,114.6,143.3z"
id="path4597" /><path
class="st1"
d="M114.6,110.2H31c-7.2,0-13-4.8-13-10.6l0,0C18,93.7,23.9,89,31,89h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,105.4,121.8,110.2,114.6,110.2z"
id="path4599" /><path
class="st1"
d="M115,77.1H31.4c-7.2,0-13-4.8-13-10.6l0,0c0-5.9,5.9-10.6,13-10.6H115c7.2,0,13,4.8,13,10.6l0,0 C128,72.3,122.1,77.1,115,77.1z"
id="path4601" /><path
class="st1"
d="M114.6,44H31c-7.2,0-13-4.8-13-10.6l0,0c0-5.9,5.9-10.7,13-10.7h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,39.2,121.8,44,114.6,44z"
id="path4603" /></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 146 166"
style="enable-background:new 0 0 146 166;"
xml:space="preserve"
sodipodi:docname="i_high.svg"
inkscape:version="0.92.1 r"><metadata
id="metadata6016"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6014" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1055"
id="namedview6012"
showgrid="false"
inkscape:zoom="1.4216867"
inkscape:cx="73"
inkscape:cy="83"
inkscape:window-x="0"
inkscape:window-y="1050"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style5999">
.st0{fill:#808080;}
.st1{fill:#F39800;}
.st2{fill:#CCCCCC;}
</style><path
class="st1"
d="M114.6,143.3H31c-7.2,0-13-4.8-13-10.6l0,0c0-5.9,5.9-10.6,13-10.6h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,138.5,121.8,143.3,114.6,143.3z"
id="path6003" /><path
class="st1"
d="M114.6,110.2H31c-7.2,0-13-4.8-13-10.6l0,0C18,93.7,23.9,89,31,89h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,105.4,121.8,110.2,114.6,110.2z"
id="path6005" /><path
class="st1"
d="M115,77.1H31.4c-7.2,0-13-4.8-13-10.6l0,0c0-5.9,5.9-10.6,13-10.6H115c7.2,0,13,4.8,13,10.6l0,0 C128,72.3,122.1,77.1,115,77.1z"
id="path6007" /><path
class="st2"
d="M99.6,40.2H46c-4.6,0-8.4-3.1-8.4-6.8l0,0c0-3.8,3.8-6.9,8.4-6.9h53.7c4.6,0,8.4,3.1,8.4,6.8l0,0 C108.1,37.1,104.3,40.2,99.6,40.2z"
id="path6009" /></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 146 166"
style="enable-background:new 0 0 146 166;"
xml:space="preserve"
sodipodi:docname="i_low.svg"
inkscape:version="0.92.1 r"><metadata
id="metadata6693"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6691" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1055"
id="namedview6689"
showgrid="false"
inkscape:zoom="1.4216867"
inkscape:cx="73"
inkscape:cy="83"
inkscape:window-x="0"
inkscape:window-y="1050"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style6674">
.st0{fill:#808080;}
.st1{fill:#94D400;}
.st2{fill:#CCCCCC;}
</style><g
id="g6678" /><path
class="st1"
d="M114.6,143.3H31c-7.2,0-13-4.8-13-10.6l0,0c0-5.9,5.9-10.6,13-10.6h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,138.5,121.8,143.3,114.6,143.3z"
id="path6680" /><path
class="st2"
d="M99.6,40.2H46c-4.6,0-8.4-3.1-8.4-6.8l0,0c0-3.8,3.8-6.9,8.4-6.9h53.7c4.6,0,8.4,3.1,8.4,6.8l0,0 C108.1,37.1,104.3,40.2,99.6,40.2z"
id="path6682" /><path
class="st2"
d="M99.6,73.3H46c-4.6,0-8.4-3.1-8.4-6.8l0,0c0-3.8,3.8-6.9,8.4-6.9h53.7c4.6,0,8.4,3.1,8.4,6.8l0,0 C108.1,70.2,104.3,73.3,99.6,73.3z"
id="path6684" /><path
class="st2"
d="M99.6,106.4H46c-4.6,0-8.4-3.1-8.4-6.8l0,0c0-3.8,3.8-6.9,8.4-6.9h53.7c4.6,0,8.4,3.1,8.4,6.8l0,0 C108.1,103.3,104.3,106.4,99.6,106.4z"
id="path6686" /></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 146 166"
style="enable-background:new 0 0 146 166;"
xml:space="preserve"
sodipodi:docname="i_med.svg"
inkscape:version="0.92.1 r"><metadata
id="metadata7574"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs7572" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="834"
inkscape:window-height="480"
id="namedview7570"
showgrid="false"
inkscape:zoom="1.4216867"
inkscape:cx="73"
inkscape:cy="83"
inkscape:window-x="0"
inkscape:window-y="1050"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style7555">
.st0{fill:#808080;}
.st1{fill:#DEC800;}
.st2{fill:#CCCCCC;}
</style><g
id="g7559" /><path
class="st1"
d="M114.6,143.3H31c-7.2,0-13-4.8-13-10.6l0,0c0-5.9,5.9-10.6,13-10.6h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,138.5,121.8,143.3,114.6,143.3z"
id="path7561" /><path
class="st1"
d="M114.6,110.2H31c-7.2,0-13-4.8-13-10.6l0,0C18,93.7,23.9,89,31,89h83.6c7.2,0,13,4.8,13,10.6l0,0 C127.7,105.4,121.8,110.2,114.6,110.2z"
id="path7563" /><path
class="st2"
d="M99.6,40.2H46c-4.6,0-8.4-3.1-8.4-6.8l0,0c0-3.8,3.8-6.9,8.4-6.9h53.7c4.6,0,8.4,3.1,8.4,6.8l0,0 C108.1,37.1,104.3,40.2,99.6,40.2z"
id="path7565" /><path
class="st2"
d="M99.6,73.3H46c-4.6,0-8.4-3.1-8.4-6.8l0,0c0-3.8,3.8-6.9,8.4-6.9h53.7c4.6,0,8.4,3.1,8.4,6.8l0,0 C108.1,70.2,104.3,73.3,99.6,73.3z"
id="path7567" /></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -125,7 +125,8 @@ function(i18n) {
awToolTip: i18n._('Please save before viewing Insights'),
dataPlacement: 'top',
title: i18n._('Insights'),
skipGenerator: true
skipGenerator: true,
ngIf: 'host.insights_system_id!==null'
}
}
};

View File

@ -74,6 +74,9 @@
</td>
<td class="List-actionsContainer">
<div class="List-actionButtonCell List-tableCell">
<button id="insights-action" class="List-actionButton " data-placement="top" ng-click="goToInsights(host.id)" aw-tool-tip="View Insights Data" ng-show="host.insights_system_id">
<i class="fa fa-info"></i>
</button>
<button id="edit-action" class="List-actionButton " ng-class="{'List-editButton--selected' : $stateParams['host_id'] == host.id}" data-placement="top" ng-click="editHost(host.id)" aw-tool-tip="Edit host" ng-show="host.summary_fields.user_capabilities.edit">
<i class="fa fa-pencil"></i>
</button>

View File

@ -65,6 +65,9 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
$scope.editHost = function(id){
$state.go('hosts.edit', {host_id: id});
};
$scope.goToInsights = function(id){
$state.go('hosts.edit.insights', {host_id:id});
};
$scope.deleteHost = function(id, name){
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the host below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(name) + '</div>';
var action = function(){

View File

@ -1,5 +1,12 @@
@import "../../shared/branding/colors.default.less";
.InsightsLastCheck{
display: flex;
justify-content: flex-end;
padding-bottom: 20px;
align-items: baseline;
}
.InsightsNav{
width: 100%;
display: flex;
@ -8,7 +15,6 @@
flex-wrap: wrap;
font-size: 14px;
font-weight: bold;
}
.InsightsNav-rightSide{
@ -16,7 +22,8 @@
display: flex;
flex: 1 0 auto;
flex-wrap: wrap;
padding: 10px 0px 10px 0px
max-width: 100%;
padding-left: 10px;
}
.InsightsNav-leftSide{
@ -26,6 +33,45 @@
justify-content: flex-end;
flex-wrap: wrap;
max-width: 100%;
padding-right: 10px;
}
.InsightsNav-badgeTitle{
color: #707070;
font-size: 14px;
margin-right: 10px;
font-weight: normal;
text-transform: uppercase;
margin-left: 10px;
}
.InsightsIcon{
height: 30px;
width:30px;
}
.InsightsIcon-warning{
color:@default-warning;
padding-right: 7px;
}
.InsightsNav-anchor{
display:flex;
align-items: center;
cursor:pointer;
height: 40px;
padding-right:10px;
}
.InsightsNav-anchor.is-currentFilter{
padding-top: 5px;
border-bottom: 5px solid @menu-link-btm-hov;
}
.InsightsNav-anchor:hover{
background-color: @menu-link-bg-hov;
padding-top: 5px;
border-bottom: 5px solid @menu-link-btm-hov;
}
.InsightsNav-totalIssues{
@ -42,7 +88,7 @@
}
.InsightsNav-mediumIssues{
background-color: @default-succ;
background-color: @insights-yellow;
}
.InsightsNav-lowIssues{
@ -52,6 +98,29 @@
.InsightsNav-solvableBadge{
background-color: @b7grey;
}
.InsightsNav-solvableBadge:last-of-type{
margin-right: 20px;
.InsightsRow{
margin-top:10px;
}
.InsightsRow-title{
display: flex;
align-items: center;
}
.InsightsRow-description{
font-size:14px;
font-weight: bold;
padding-left: 5px;
}
.InsightsRow-category{
margin-left: 10px;
}
.InsightsRow-body{
padding-left: 35px;
}
.InsightsRow-plan{
padding-left: 35px;
}

View File

@ -4,13 +4,71 @@
* All Rights Reserved
*************************************************/
export default [
function () {
export default [ 'InsightsData', '$scope', 'moment', '$state', 'resourceData',
function (data, $scope, moment, $state, resourceData) {
function init() {
// $scope.insights
$scope.reports = data.reports;
$scope.reports_dataset = data;
$scope.currentFilter = "total";
$scope.solvable_count = _.filter($scope.reports_dataset.reports, (report) => {return report.maintenance_actions.length > 0;}).length;
$scope.not_solvable_count = _.filter($scope.reports_dataset.reports, (report) => {return report.maintenance_actions.length === 0; }).length;
$scope.critical_count = 0 || _.filter($scope.reports_dataset.reports, (report) => {return report.rule.severity === "CRITICAL"; }).length;
$scope.high_count = _.filter($scope.reports_dataset.reports, (report) => {return report.rule.severity === "ERROR"; }).length;
$scope.med_count = _.filter($scope.reports_dataset.reports, (report) => {return report.rule.severity === "WARN"; }).length;
$scope.low_count = _.filter($scope.reports_dataset.reports, (report) => {return report.rule.severity === "INFO"; }).length;
let a = moment(), b = moment($scope.reports_dataset.last_check_in);
$scope.last_check_in = a.diff(b, 'hours');
$scope.inventory = resourceData.data;
$scope.insights_credential = resourceData.data.summary_fields.insights_credential.id;
}
init();
$scope.filter = function(filter){
$scope.currentFilter = filter;
if(filter === "total"){
$scope.reports = $scope.reports_dataset.reports;
}
if(filter === "solvable"){
$scope.reports = _.filter($scope.reports_dataset.reports, function(report){
return (report.maintenance_actions.length > 0);
});
}
if(filter === "not_solvable"){
$scope.reports = _.filter($scope.reports_dataset.reports, function(report){
return (report.maintenance_actions.length === 0);
});
}
if(filter === "critical"){
$scope.reports = _.filter($scope.reports_dataset.reports, function(report){
return (report.rule.severity === 'CRITICAL');
});
}
if(filter === "high"){
$scope.reports = _.filter($scope.reports_dataset.reports, function(report){
return (report.rule.severity === 'ERROR');
});
}
if(filter === "medium"){
$scope.reports = _.filter($scope.reports_dataset.reports, function(report){
return (report.rule.severity === 'WARN');
});
}
if(filter === "low"){
$scope.reports = _.filter($scope.reports_dataset.reports, function(report){
return (report.rule.severity === 'INFO');
});
}
};
$scope.viewDataInInsights = function(){
window.open(`https://access.redhat.com/insights/inventory?machine=${$scope.$parent.host.insights_system_id}`, '_blank');
};
$scope.remediateInventory = function(inv_id, inv_name, insights_credential){
$state.go('templates.addJobTemplate', {inventory_id: inv_id, inventory_name:inv_name, credential_id: insights_credential});
};
$scope.formCancel = function(){
$state.go('inventories', null, {reload: true});
};
}];

View File

@ -1,20 +1,81 @@
<div class="InsightsLastCheck"
ng-show="isCheckingIn=false">
<i class="fa icon-job-failed InsightsIcon-warning"></i>
<translate>This machine has not checked in with Insights in {{last_check_in}} hours</translate>
</div>
<div class="InsightsNav">
<div class="InsightsNav-rightSide">
<div class="JobResults-badgeTitle">Total Issues</div>
<span class="badge List-titleBadge InsightsNav-totalIssues">4</span>
<div class="JobResults-badgeTitle">Critical</div>
<span class="badge List-titleBadge InsightsNav-criticalIssues">1</span>
<div class="JobResults-badgeTitle">High</div>
<span class="badge List-titleBadge InsightsNav-highIssues">1</span>
<div class="JobResults-badgeTitle">Medium</div>
<span class="badge List-titleBadge InsightsNav-mediumIssues">1</span>
<div class="JobResults-badgeTitle">Low</div>
<span class="badge List-titleBadge InsightsNav-lowIssues">1</span>
<div class="InsightsNav-anchor" ng-click="filter('total')"
ng-class="{'is-currentFilter' : (currentFilter === 'total')}">
<div class="InsightsNav-badgeTitle"><translate>Total Issues</translate></div>
<span class="badge List-titleBadge InsightsNav-totalIssues">{{reports_dataset.reports.length}}</span>
</div>
<div class="InsightsNav-anchor" ng-click="filter('critical')"
ng-class="{'is-currentFilter' : (currentFilter === 'critical')}"
ng-show="critical_count>0">
<div class="InsightsNav-badgeTitle"><translate>Critical</translate></div>
<span class="badge List-titleBadge InsightsNav-criticalIssues">{{critical_count}}</span>
</div>
<div class="InsightsNav-anchor" ng-click="filter('high')"
ng-class="{'is-currentFilter' : (currentFilter === 'high')}"
ng-show="high_count>0">
<div class="InsightsNav-badgeTitle"><translate>High</translate></div>
<span class="badge List-titleBadge InsightsNav-highIssues">{{high_count}}</span>
</div>
<div class="InsightsNav-anchor" ng-click="filter('medium')"
ng-class="{'is-currentFilter' : (currentFilter === 'medium')}"
ng-show="med_count>0">
<div class="InsightsNav-badgeTitle"><translate>Medium</translate></div>
<span class="badge List-titleBadge InsightsNav-mediumIssues">{{med_count}}</span>
</div>
<div class="InsightsNav-anchor" ng-click="filter('low')"
ng-class="{'is-currentFilter' : (currentFilter === 'low')}"
ng-show="low_count>0">
<div class="InsightsNav-badgeTitle"><translate>Low</translate></div>
<span class="badge List-titleBadge InsightsNav-lowIssues">{{low_count}}</span>
</div>
</div>
<div class="InsightsNav-leftSide">
<div class="JobResults-badgeTitle">Solvable With Playbook</div>
<span class="badge List-titleBadge InsightsNav-solvableBadge">4</span>
<div class="JobResults-badgeTitle">Not Solvable With Playbook</div>
<span class="badge List-titleBadge InsightsNav-solvableBadge">1</span>
<div class="InsightsNav-anchor" ng-click="filter('solvable')"
ng-class="{'is-currentFilter' : (currentFilter === 'solvable')}"
ng-show="solvable_count>0">
<div class="InsightsNav-badgeTitle"><translate>Solvable With Playbook</translate></div>
<span class="badge List-titleBadge InsightsNav-solvableBadge">{{solvable_count}}</span>
</div>
<div class="InsightsNav-anchor" ng-click="filter('not_solvable')"
ng-class="{'is-currentFilter' : (currentFilter === 'not_solvable')}"
ng-show="not_solvable_count>0">
<div class="InsightsNav-badgeTitle"><translate>Not Solvable With Playbook</translate></div>
<span class="badge List-titleBadge InsightsNav-solvableBadge">{{not_solvable_count}}</span>
</div>
</div>
</div>
<div class="InsightsBody">
<div class="InsightsRow" ng-repeat="report in reports">
<div class="InsightsRow-title">
<img class="InsightsIcon" src="/static/assets/i_severity_critical.svg" ng-show="report.rule.severity === 'CRITICAL'"
aw-tool-tip="Critical Risk" data-placement="right" data-original-title="" title="">
<img class="InsightsIcon" src="/static/assets/i_severity_high.svg" ng-show="report.rule.severity === 'ERROR'"
aw-tool-tip="High Risk" data-placement="top" data-original-title="" title="">
<img class="InsightsIcon" src="/static/assets/i_severity_med.svg" ng-show="report.rule.severity === 'WARN'"
aw-tool-tip="Medium Risk" data-placement="top">
<img class="InsightsIcon" src="/static/assets/i_severity_low.svg" ng-show="report.rule.severity === 'INFO'"
aw-tool-tip="Low Risk" data-placement="top">
<div class="InsightsRow-description"><translate>ISSUE: {{report.rule.description}}</translate></div>
<span class="Form-title--is_superuser">{{report.rule.category}}</span>
</div>
<div class="InsightsRow-body">{{report.rule.summary}}</div>
<div ng-repeat="plan in report.maintenance_actions">
<div class="InsightsRow-plan" ng-bind-html="plan | planFilter"></div>
</div>
</div>
</div>
<div class="buttons Form-buttons">
<button type="button" class="btn btn-sm Form-primaryButton" ng-click="viewDataInInsights()"> <i class="fa fa-sign-out"></i> VIEW DATA IN INSIGHTS</button>
<button type="button" class="btn btn-sm Form-primaryButton" ng-click="remediateInventory(inventory.id, inventory.name, insights_credential)"> REMEDIATE INVENTORY</button>
<button type="button" class="btn btn-sm Form-cancelButton" ng-click="formCancel()"> Close</button>
</div>

View File

@ -13,15 +13,21 @@ export default {
}
},
resolve: {
Facts: ['$stateParams', 'GetBasePath', 'Rest',
function($stateParams, GetBasePath, Rest) {
let ansibleFactsUrl = GetBasePath('hosts') + $stateParams.host_id + '/ansible_facts';
Rest.setUrl(ansibleFactsUrl);
InsightsData: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors',
(Rest, $stateParams, GetBasePath, ProcessErrors) => {
var path = `${GetBasePath('hosts')}${$stateParams.host_id}/insights`;
Rest.setUrl(path);
return Rest.get()
.success(function(data) {
return data;
.then(function(data) {
return (data.data.insights_content);
}).catch(function(response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get insights info. GET returned status: ' +
response.status
});
});
}
]
],
}
};

View File

@ -5,7 +5,9 @@
*************************************************/
import controller from './insights.controller';
import planFilter from './plan-filter';
export default
angular.module('insightsDashboard', [])
.filter('planFilter', planFilter)
.controller('InsightsController', controller);

View File

@ -0,0 +1,16 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default function(){
return function(plan) {
if(plan === null || plan === undefined){
return "PLAN: Not Available <a href='https://access.redhat.com/insights/info/' target='_blank'>CREATE A NEW PLAN IN INSIGHTS</a>";
} else {
let name = (plan.maintenance_plan.name === null) ? "Unnamed Plan" : plan.maintenance_plan.name;
return `<a href="https://access.redhat.com/insights/planner/${plan.maintenance_plan.maintenance_id}" target="_blank">${name} (${plan.maintenance_plan.maintenance_id})</a>`;
}
};
}

View File

@ -86,6 +86,9 @@ export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
$scope.editHost = function(id){
$state.go('inventories.edit.hosts.edit', {host_id: id});
};
$scope.goToInsights = function(id){
$state.go('inventories.edit.hosts.edit.insights', {host_id:id});
};
$scope.deleteHost = function(id, name){
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the host below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(name) + '</div>';
var action = function(){

View File

@ -125,7 +125,8 @@ function(i18n) {
awToolTip: i18n._('Please save before viewing Insights'),
dataPlacement: 'top',
title: i18n._('Insights'),
skipGenerator: true
skipGenerator: true,
ngIf: 'host.insights_system_id!==null'
}
}
};

View File

@ -50,6 +50,13 @@ export default {
fieldActions: {
columnClass: 'col-lg-6 col-md-4 col-sm-4 col-xs-5 text-right',
insights: {
ngClick: "goToInsights(host.id)",
icon: 'fa-info',
awToolTip: 'View Insights Data',
dataPlacement: 'top',
ngShow: 'host.insights_system_id'
},
copy: {
mode: 'all',
ngClick: "copyMoveHost(host.id)",

View File

@ -29,6 +29,7 @@ function InventoriesEdit($scope, $location,
$scope = angular.extend($scope, inventoryData);
$scope.credential_name = (inventoryData.summary_fields.insights_credential && inventoryData.summary_fields.insights_credential.name) ? inventoryData.summary_fields.insights_credential.name : null;
$scope.organization_name = inventoryData.summary_fields.organization.name;
$scope.inventory_variables = inventoryData.variables === null || inventoryData.variables === '' ? '---' : ParseVariableString(inventoryData.variables);
$scope.parseType = 'yaml';
@ -90,6 +91,10 @@ function InventoriesEdit($scope, $location,
$state.go('inventories');
};
$scope.remediateInventory = function(inv_id, inv_name, insights_credential){
$state.go('templates.addJobTemplate', {inventory_id: inv_id, inventory_name:inv_name, credential_id: insights_credential});
};
}
export default ['$scope', '$location',

View File

@ -68,6 +68,17 @@ function(i18n, InventoryCompletedJobsList) {
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg',
awLookupWhen: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg'
},
insights_credential: {
label: i18n._('Insights Credential'),
type: 'lookup',
list: 'CredentialList',
basePath: 'credentials',
sourceModel: 'credential',
sourceField: 'name',
search: {
credential_type: 13 //insights
}
},
inventory_variables: {
realName: 'variables',
label: i18n._('Variables'),
@ -177,6 +188,14 @@ function(i18n, InventoryCompletedJobsList) {
skipGenerator: true
},
completed_jobs: completed_jobs_object
},
relatedButtons: {
remediate_inventory: {
ngClick: 'remediateInventory(id, name, insights_credential)',
ngShow: 'insights_credential!==null',
label: i18n._('Remediate Inventory'),
class: 'Form-primaryButton'
}
}
};}];

View File

@ -7,9 +7,10 @@
export default ['$scope', '$location', '$stateParams', 'GenerateForm',
'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath',
'GetProjectPath', 'GetChoices', 'Wait', '$state', 'CreateSelect2', 'i18n',
'CredentialTypes',
function($scope, $location, $stateParams, GenerateForm, ProjectsForm, Rest,
Alert, ProcessErrors, GetBasePath, GetProjectPath, GetChoices, Wait, $state,
CreateSelect2, i18n) {
CreateSelect2, i18n, CredentialTypes) {
var form = ProjectsForm(),
base = $location.path().replace(/^\//, '').split('/')[0],
@ -121,6 +122,7 @@ export default ['$scope', '$location', '$stateParams', 'GenerateForm',
if ($scope.scm_type.value) {
switch ($scope.scm_type.value) {
case 'git':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' +
i18n._('Example URLs for GIT SCM include:') +
'</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
@ -130,11 +132,13 @@ export default ['$scope', '$location', '$stateParams', 'GenerateForm',
'SSH. GIT read only protocol (git://) does not use username or password information.'), '<strong>', '</strong>');
break;
case 'svn':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' + i18n._('Example URLs for Subversion SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
'<li>svn+ssh://servername.example.com/path</li></ul>';
break;
case 'hg':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' + i18n._('Example URLs for Mercurial SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://bitbucket.org/username/project</li><li>ssh://hg@bitbucket.org/username/project</li>' +
'<li>ssh://server.example.com/path</li></ul>' +
@ -142,8 +146,15 @@ export default ['$scope', '$location', '$stateParams', 'GenerateForm',
'Do not put the username and key in the URL. ' +
'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '<strong>', '</strong>');
break;
case 'insights':
$scope.pathRequired = false;
$scope.scmRequired = false;
$scope.credRequired = true;
$scope.credentialLabel = "Credential";
break;
default:
$scope.urlPopover = '<p> ' + i18n._('URL popover text');
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p> ' + i18n._('URL popover text') + '</p>';
}
}
@ -151,5 +162,20 @@ export default ['$scope', '$location', '$stateParams', 'GenerateForm',
$scope.formCancel = function() {
$state.go('projects');
};
$scope.lookupCredential = function(){
// Perform a lookup on the credential_type. Git, Mercurial, and Subversion
// all use SCM as their credential type.
let credType = _.filter(CredentialTypes, function(credType){
return ($scope.scm_type.value !== "insights" && credType.kind === "scm" ||
$scope.scm_type.value === "insights" && credType.kind === "insights");
});
$state.go('.credential', {
credential_search: {
credential_type: credType[0].id,
page_size: '5',
page: '1'
}
});
};
}
];

View File

@ -8,11 +8,11 @@ export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest',
'Alert', 'ProcessErrors', 'GenerateForm', 'Prompt', 'ClearScope',
'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty',
'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification',
'i18n',
'i18n', 'CredentialTypes',
function($scope, $rootScope, $stateParams, ProjectsForm, Rest, Alert,
ProcessErrors, GenerateForm, Prompt, ClearScope, GetBasePath,
GetProjectPath, Authorization, GetChoices, Empty, Wait, ProjectUpdate,
$state, CreateSelect2, ToggleNotification, i18n) {
$state, CreateSelect2, ToggleNotification, i18n, CredentialTypes) {
ClearScope('htmlTemplate');
@ -254,6 +254,7 @@ export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest',
if ($scope.scm_type.value) {
switch ($scope.scm_type.value) {
case 'git':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' + i18n._('Example URLs for GIT SCM include:') + '</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
'<li>git@github.com:ansible/ansible.git</li><li>git://servername.example.com/ansible.git</li></ul>' +
'<p>' + i18n.sprintf(i18n._('%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
@ -261,11 +262,13 @@ export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest',
'SSH. GIT read only protocol (git://) does not use username or password information.'), '<strong>', '</strong>');
break;
case 'svn':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' + i18n._('Example URLs for Subversion SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
'<li>svn+ssh://servername.example.com/path</li></ul>';
break;
case 'hg':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' + i18n._('Example URLs for Mercurial SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://bitbucket.org/username/project</li><li>ssh://hg@bitbucket.org/username/project</li>' +
'<li>ssh://server.example.com/path</li></ul>' +
@ -273,12 +276,35 @@ export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest',
'Do not put the username and key in the URL. ' +
'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '<strong>', '</strong>');
break;
case 'insights':
$scope.pathRequired = false;
$scope.scmRequired = false;
$scope.credRequired = true;
$scope.credentialLabel = "Credential";
break;
default:
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p> ' + i18n._('URL popover text');
}
}
};
$scope.lookupCredential = function(){
// Perform a lookup on the credential_type. Git, Mercurial, and Subversion
// all use SCM as their credential type.
let credType = _.filter(CredentialTypes, function(credType){
return ($scope.scm_type.value !== "insights" && credType.kind === "scm" ||
$scope.scm_type.value === "insights" && credType.kind === "insights");
});
$state.go('.credential', {
credential_search: {
credential_type: credType[0].id,
page_size: '5',
page: '1'
}
});
};
$scope.SCMUpdate = function() {
if ($scope.project_obj.scm_type === "Manual" || Empty($scope.project_obj.scm_type)) {
// ignore

View File

@ -28,7 +28,24 @@ angular.module('Projects', [revisions.name])
.config(['$stateProvider', 'stateDefinitionsProvider',
function($stateProvider, stateDefinitionsProvider) {
let stateDefinitions = stateDefinitionsProvider.$get();
var projectResolve = {
CredentialTypes: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors',
(Rest, $stateParams, GetBasePath, ProcessErrors) => {
var path = GetBasePath('credential_types');
Rest.setUrl(path);
return Rest.get()
.then(function(data) {
return (data.data.results);
}).catch(function(response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get credential tpyes. GET returned status: ' +
response.status
});
});
}
]
};
// lazily generate a tree of substates which will replace this node in ui-router's stateRegistry
// see: stateDefinition.factory for usage documentation
$stateProvider.state({
@ -55,6 +72,10 @@ angular.module('Projects', [revisions.name])
},
ncyBreadcrumb: {
label: N_('PROJECTS')
},
resolve: {
add: projectResolve,
edit: projectResolve
}
})
});

View File

@ -131,9 +131,10 @@ export default ['i18n', 'NotificationsList', function(i18n, NotificationsList) {
basePath: 'credentials',
list: 'CredentialList',
// apply a default search filter to show only scm credentials
search: {
kind: 'scm'
},
// search: {
// kind: 'scm'
// },
ngClick: 'lookupCredential()',
autopopulateLookup: false,
awRequiredWhen: {
reqExpression: "credRequired",

View File

@ -23,6 +23,7 @@
@egrey: #EEEEEE;
@cgrey: #CCCCCC;
@f7grey: #F7F7F7;
@insights-yellow: #dedc4f;
@default-warning: #F0AD4E;

View File

@ -1507,6 +1507,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
collection = this.form.related[itm];
html += `<div id="${itm}_tab" `+
`class="Form-tab" `;
html += (this.form.related[itm].ngIf) ? ` ng-if="${this.form.related[itm].ngIf}" ` : "";
html += (this.form.related[itm].ngClick) ? `ng-click="` + this.form.related[itm].ngClick + `" ` : `ng-click="$state.go('${this.form.stateTree}.edit.${itm}')" `;
if (collection.awToolTip && collection.awToolTipTabEnabledInEditMode === true) {
html += `aw-tool-tip="${collection.awToolTip}" ` +

View File

@ -191,6 +191,9 @@ angular.module('GeneratorHelpers', [systemStatus.name])
case 'copy':
icon = "fa-copy";
break;
case 'insights':
icon = "fa-info";
break;
case 'cancel':
icon = "fa-minus-circle";
break;

View File

@ -9,13 +9,13 @@
'$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'Wait',
'Empty', 'ToJSON', 'CallbackHelpInit', 'GetChoices', '$state',
'CreateSelect2', '$q', 'i18n',
'CreateSelect2', '$q', 'i18n', 'Inventory', 'Project',
function(
$filter, $scope,
$stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
ProcessErrors, ClearScope, GetBasePath, md5Setup, ParseTypeChange, Wait,
Empty, ToJSON, CallbackHelpInit, GetChoices,
$state, CreateSelect2, $q, i18n
$state, CreateSelect2, $q, i18n, Inventory, Project
) {
Rest.setUrl(GetBasePath('job_templates'));
@ -80,24 +80,6 @@
}
$scope.job_type = $scope.job_type_options[form.fields.job_type.default];
// if you're getting to the form from the scan job section on inventories,
// set the job type select to be scan
if ($stateParams.inventory_id) {
// This means that the job template form was accessed via inventory prop's
// This also means the job is a scan job.
$scope.job_type.value = 'scan';
$scope.jobTypeChange();
$scope.inventory = $stateParams.inventory_id;
Rest.setUrl(GetBasePath('inventory') + $stateParams.inventory_id + '/');
Rest.get()
.success(function (data) {
$scope.inventory_name = data.name;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to lookup inventory: ' + data.id + '. GET returned status: ' + status });
});
}
CreateSelect2({
element:'#job_template_job_type',
multiple: false
@ -254,6 +236,17 @@
}
};
if(Inventory){
$scope.inventory = Inventory.inventory_id;
$scope.inventory_name = Inventory.inventory_name;
}
if(Project){
$scope.project = Project.id;
$scope.project_name = Project.name;
selectPlaybook('force_load');
checkSCMStatus();
}
// Register a watcher on project_name
if ($scope.selectPlaybookUnregister) {
$scope.selectPlaybookUnregister();

View File

@ -77,10 +77,7 @@ export default ['$scope', '$rootScope',
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
});
});
$scope.addJobTemplate = function() {
$state.go('jobTemplates.add');
};
$scope.editJobTemplate = function(template) {
if(template) {
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {

View File

@ -52,11 +52,41 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesA
addJobTemplate = stateDefinitions.generateTree({
name: 'templates.addJobTemplate',
url: '/add_job_template',
url: '/add_job_template?inventory_id&inventory_name&credential_id',
modes: ['add'],
form: 'JobTemplateForm',
controllers: {
add: 'JobTemplateAdd'
},
resolve: {
add: {
Inventory: ['$stateParams',
function($stateParams){
if($stateParams.inventory_id){
let obj = {};
obj.inventory_id = Number($stateParams.inventory_id);
obj.inventory_name = $stateParams.inventory_name;
return obj;
}
}],
Project: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors',
function($stateParams, Rest, GetBasePath, ProcessErrors){
if($stateParams.credential_id){
let path = `${GetBasePath('projects')}?credential__id=${Number($stateParams.credential_id)}`;
Rest.setUrl(path);
return Rest.get().
then(function(data){
return data.data.results[0];
}).catch(function(response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get project info. GET returned status: ' +
response.status
});
});
}
}]
}
}
});