WIP: UI for QuaySec
This commit is contained in:
parent
75dfec7875
commit
8c144397e9
5 changed files with 177 additions and 1 deletions
|
@ -85,6 +85,42 @@
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-panel-tags-element .fa-flag {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-panel-tags-element .vuln-name {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-panel-tags-element .vuln-description {
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-panel-tags-element .fa-flag.None {
|
||||||
|
color: #00CA00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-panel-tags-element .fa-flag.Medium {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-panel-tags-element .fa-flag.High {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flickerAnimation { /* flame pulses */
|
||||||
|
0% { opacity:1; }
|
||||||
|
50% { opacity:0; }
|
||||||
|
100% { opacity:1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-panel-tags-element .fa-flag.Critical {
|
||||||
|
color: red;
|
||||||
|
opacity:1;
|
||||||
|
animation: flickerAnimation 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.repo-panel-tags-element .tag-span {
|
.repo-panel-tags-element .tag-span {
|
||||||
|
|
|
@ -86,6 +86,12 @@
|
||||||
style="min-width: 62px;">
|
style="min-width: 62px;">
|
||||||
<a href="javascript:void(0)" ng-click="orderBy('size')">Size</a>
|
<a href="javascript:void(0)" ng-click="orderBy('size')">Size</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td ng-class="tablePredicateClass('vuln_level', options.predicate, options.reverse)"
|
||||||
|
style="width: 60px;">
|
||||||
|
<a href="javascript:void(0)" ng-click="orderBy('vuln_level')">
|
||||||
|
<i class="fa fa-flag"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td class="hidden-xs"
|
<td class="hidden-xs"
|
||||||
ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)"
|
ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)"
|
||||||
colspan="{{ imageTracks.length + 1 }}"
|
colspan="{{ imageTracks.length + 1 }}"
|
||||||
|
@ -108,6 +114,42 @@
|
||||||
<span bo-if="!tag.last_modified">Unknown</span>
|
<span bo-if="!tag.last_modified">Unknown</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden-xs" bo-text="tag.size | bytes"></td>
|
<td class="hidden-xs" bo-text="tag.size | bytes"></td>
|
||||||
|
<td>
|
||||||
|
<span class="cor-loader-inline" ng-if="getTagVulnerabilities(tag).loading"></span>
|
||||||
|
<span ng-if="!getTagVulnerabilities(tag).loading">
|
||||||
|
<i class="fa fa-flag-o" ng-if="!getTagVulnerabilities(tag).indexed"
|
||||||
|
data-title="Image is currently being checked for vulnerabilities"
|
||||||
|
bs-tooltip>
|
||||||
|
</i>
|
||||||
|
<i class="fa fa-flag None"
|
||||||
|
ng-if="getTagVulnerabilities(tag).indexed && !getTagVulnerabilities(tag).hasVulnerabilities"
|
||||||
|
data-title="Image has no vulnerabilities"
|
||||||
|
bs-tooltip>
|
||||||
|
</i>
|
||||||
|
<div class="dropdown" style="text-align: left;"
|
||||||
|
ng-if="getTagVulnerabilities(tag).indexed && getTagVulnerabilities(tag).hasVulnerabilities">
|
||||||
|
<i class="fa fa-flag"
|
||||||
|
data-title="Image has vulnerabilities"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
ng-class="getTagVulnerabilities(tag).highestVulnerability.Priority"
|
||||||
|
bs-tooltip>
|
||||||
|
</i>
|
||||||
|
<ul class="dropdown-menu pull-right">
|
||||||
|
<li ng-repeat="vuln in getTagVulnerabilities(tag).vulnerabilities">
|
||||||
|
<a href="{{ vuln.Link }}" target="_new">
|
||||||
|
<div class="vuln-name">
|
||||||
|
<i class="fa fa-flag" bo-class="vuln.Priority"></i>
|
||||||
|
{{ vuln.ID }}
|
||||||
|
</div>
|
||||||
|
<div class="vuln-description">
|
||||||
|
{{ vuln.Description }}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td class="hidden-xs image-id-col">
|
<td class="hidden-xs image-id-col">
|
||||||
<span class="image-link" repository="repository" image-id="tag.image_id"></span>
|
<span class="image-link" repository="repository" image-id="tag.image_id"></span>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -18,7 +18,7 @@ angular.module('quay').directive('repoPanelTags', function () {
|
||||||
|
|
||||||
'getImages': '&getImages'
|
'getImages': '&getImages'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element, $filter, $location, ApiService, UIService) {
|
controller: function($scope, $element, $filter, $location, ApiService, UIService, VulnerabilityService) {
|
||||||
var orderBy = $filter('orderBy');
|
var orderBy = $filter('orderBy');
|
||||||
|
|
||||||
$scope.checkedTags = UIService.createCheckStateController([], 'name');
|
$scope.checkedTags = UIService.createCheckStateController([], 'name');
|
||||||
|
@ -35,6 +35,7 @@ angular.module('quay').directive('repoPanelTags', function () {
|
||||||
$scope.tagActionHandler = null;
|
$scope.tagActionHandler = null;
|
||||||
$scope.showingHistory = false;
|
$scope.showingHistory = false;
|
||||||
$scope.tagsPerPage = 50;
|
$scope.tagsPerPage = 50;
|
||||||
|
$scope.tagVulnerabilities = {};
|
||||||
|
|
||||||
var setTagState = function() {
|
var setTagState = function() {
|
||||||
if (!$scope.repository || !$scope.selectedTags) { return; }
|
if (!$scope.repository || !$scope.selectedTags) { return; }
|
||||||
|
@ -149,6 +150,53 @@ angular.module('quay').directive('repoPanelTags', function () {
|
||||||
setTagState();
|
setTagState();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$scope.loadTagVulnerabilities = function(tag, tagData) {
|
||||||
|
var params = {
|
||||||
|
'tag': tag.name,
|
||||||
|
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.getRepoTagVulnerabilities(null, params).then(function(resp) {
|
||||||
|
tagData.indexed = resp.security_indexed;
|
||||||
|
tagData.loading = false;
|
||||||
|
|
||||||
|
if (resp.security_indexed) {
|
||||||
|
tagData.hasVulnerabilities = !!resp.data.Vulnerabilities.length;
|
||||||
|
tagData.vulnerabilities = resp.data.Vulnerabilities;
|
||||||
|
|
||||||
|
var highest = null;
|
||||||
|
resp.data.Vulnerabilities.forEach(function(v) {
|
||||||
|
if (highest == null ||
|
||||||
|
VulnerabilityService.LEVELS[v.Priority].index < VulnerabilityService.LEVELS[highest.Priority].index) {
|
||||||
|
highest = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tagData.highestVulnerability = highest;
|
||||||
|
}
|
||||||
|
}, function() {
|
||||||
|
tagData.loading = false;
|
||||||
|
tagData.hasError = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getTagVulnerabilities = function(tag) {
|
||||||
|
if (!$scope.repository) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagName = tag.name;
|
||||||
|
if (!$scope.tagVulnerabilities[tagName]) {
|
||||||
|
$scope.tagVulnerabilities[tagName] = {
|
||||||
|
'loading': true
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.loadTagVulnerabilities(tag, $scope.tagVulnerabilities[tagName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scope.tagVulnerabilities[tagName];
|
||||||
|
};
|
||||||
|
|
||||||
$scope.clearSelectedTags = function() {
|
$scope.clearSelectedTags = function() {
|
||||||
$scope.checkedTags.setChecked([]);
|
$scope.checkedTags.setChecked([]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,6 +40,19 @@
|
||||||
loadImage();
|
loadImage();
|
||||||
loadRepository();
|
loadRepository();
|
||||||
|
|
||||||
|
$scope.downloadPackages = function() {
|
||||||
|
if ($scope.packagesResource) { return; }
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
'repository': namespace + '/' + name,
|
||||||
|
'imageid': imageid
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.packagesResource = ApiService.getRepoImagePackagesAsResource(params).get(function(packages) {
|
||||||
|
$scope.packages = packages;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.downloadChanges = function() {
|
$scope.downloadChanges = function() {
|
||||||
if ($scope.changesResource) { return; }
|
if ($scope.changesResource) { return; }
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
tab-init="downloadChanges()">
|
tab-init="downloadChanges()">
|
||||||
<i class="fa fa-code-fork"></i>
|
<i class="fa fa-code-fork"></i>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="cor-tab" tab-title="Packages" tab-target="#packages"
|
||||||
|
tab-init="downloadPackages()">
|
||||||
|
<i class="fa ci-package"></i>
|
||||||
|
</span>
|
||||||
</div> <!-- /cor-tabs -->
|
</div> <!-- /cor-tabs -->
|
||||||
|
|
||||||
<div class="cor-tab-content">
|
<div class="cor-tab-content">
|
||||||
|
@ -53,6 +57,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Packages -->
|
||||||
|
<div id="packages" class="tab-pane">
|
||||||
|
<div class="resource-view" resource="packagesResource" error-message="'Could not load image packages'">
|
||||||
|
<h3>Image Packages</h3>
|
||||||
|
<div class="empty" ng-if="!packages.security_indexed">
|
||||||
|
<div class="empty-primary-msg">This image has not been indexed yet</div>
|
||||||
|
<div class="empty-secondary-msg">
|
||||||
|
Please try again in a few minutes.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="empty" ng-if="packages.security_indexed && !packages.data.InstalledPackages.length">
|
||||||
|
<div class="empty-primary-msg">This image contains no recognized packages</div>
|
||||||
|
<div class="empty-secondary-msg">
|
||||||
|
Quay currently indexes Debian, Red Hat and Ubuntu packages.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table" ng-if="packages.security_indexed && packages.data.InstalledPackages.length">
|
||||||
|
<thead>
|
||||||
|
<th>Package Name</th>
|
||||||
|
<th>Package Version</th>
|
||||||
|
<th>OS</th>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tr ng-repeat="package in packages.data.InstalledPackages | orderBy:'Name'">
|
||||||
|
<td>{{ package.Name }}</td>
|
||||||
|
<td>{{ package.Version }}</td>
|
||||||
|
<td>{{ package.OS }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue