Merge pull request #858 from coreos-inc/vulnerability-tool-updateui

New Quay Sec UI and fix some small bugs
This commit is contained in:
josephschorr 2015-11-11 18:17:58 -05:00
commit 6970b0685e
13 changed files with 307 additions and 115 deletions

View file

@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
def _call_security_api(relative_url, *args, **kwargs): def _call_security_api(relative_url, *args, **kwargs):
""" Issues an HTTP call to the sec API at the given relative URL. """ """ Issues an HTTP call to the sec API at the given relative URL. """
try: try:
response = secscan_api.call(relative_url, body=None, *args, **kwargs) response = secscan_api.call(relative_url, None, *args, **kwargs)
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
raise DownstreamIssue(payload=dict(message='API call timed out')) raise DownstreamIssue(payload=dict(message='API call timed out'))
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
@ -40,32 +40,32 @@ def _call_security_api(relative_url, *args, **kwargs):
@show_if(features.SECURITY_SCANNER) @show_if(features.SECURITY_SCANNER)
@resource('/v1/repository/<repopath:repository>/tag/<tag>/vulnerabilities') @resource('/v1/repository/<repopath:repository>/image/<imageid>/vulnerabilities')
@path_param('repository', 'The full path of the repository. e.g. namespace/name') @path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('tag', 'The name of the tag') @path_param('imageid', 'The image ID')
class RepositoryTagVulnerabilities(RepositoryParamResource): class RepositoryImageVulnerabilities(RepositoryParamResource):
""" Operations for managing the vulnerabilities in a repository tag. """ """ Operations for managing the vulnerabilities in a repository image. """
@require_repo_read @require_repo_read
@nickname('getRepoTagVulnerabilities') @nickname('getRepoImageVulnerabilities')
@parse_args @parse_args
@query_param('minimumPriority', 'Minimum vulnerability priority', type=str, @query_param('minimumPriority', 'Minimum vulnerability priority', type=str,
default='Low') default='Low')
def get(self, args, namespace, repository, tag): def get(self, args, namespace, repository, imageid):
""" Fetches the vulnerabilities (if any) for a repository tag. """ """ Fetches the vulnerabilities (if any) for a repository tag. """
try: repo_image = model.image.get_repo_image(namespace, repository, imageid)
tag_image = model.tag.get_tag_image(namespace, repository, tag) if repo_image is None:
except model.DataModelException:
raise NotFound() raise NotFound()
if not tag_image.security_indexed: if not repo_image.security_indexed:
logger.debug('Image %s for tag %s under repository %s/%s not security indexed', logger.debug('Image %s under repository %s/%s not security indexed',
tag_image.docker_image_id, tag, namespace, repository) repo_image.docker_image_id, namespace, repository)
return { return {
'security_indexed': False 'security_indexed': False
} }
data = _call_security_api('layers/%s/vulnerabilities', tag_image.docker_image_id, layer_id = '%s.%s' % (repo_image.docker_image_id, repo_image.storage.uuid)
data = _call_security_api('layers/%s/vulnerabilities', layer_id,
minimumPriority=args.minimumPriority) minimumPriority=args.minimumPriority)
return { return {
@ -94,7 +94,8 @@ class RepositoryImagePackages(RepositoryParamResource):
'security_indexed': False 'security_indexed': False
} }
data = _call_security_api('layers/%s/packages/diff', repo_image.docker_image_id) layer_id = '%s.%s' % (repo_image.docker_image_id, repo_image.storage.uuid)
data = _call_security_api('layers/%s/packages', layer_id)
return { return {
'security_indexed': True, 'security_indexed': True,

View file

@ -21,5 +21,5 @@ def secscan_notification():
if not layer_ids: if not layer_ids:
return make_response('Okay') return make_response('Okay')
secscan_notification_queue.put(data['Name'], json.dumps(data)) secscan_notification_queue.put(['notification', data['Name']], json.dumps(data))
return make_response('Okay') return make_response('Okay')

View file

@ -85,46 +85,34 @@
margin-right: 2px; margin-right: 2px;
} }
.repo-panel-tags-element .fa-flag { .repo-panel-tags-element .security-scan-col span {
cursor: pointer; cursor: pointer;
} }
.repo-panel-tags-element .vuln-name { .repo-panel-tags-element .security-scan-col i.fa {
margin-right: 4px;
} }
.repo-panel-tags-element .vuln-description { .repo-panel-tags-element .security-scan-col .scanning {
color: #aaa; color: #9B9B9B;
font-size: 10px; font-size: 12px;
white-space: normal;
} }
.repo-panel-tags-element .fa-flag.None { .repo-panel-tags-element .security-scan-col .no-vulns a {
color: #00CA00; color: #2FC98E;
} }
.repo-panel-tags-element .fa-flag.Medium { .repo-panel-tags-element .security-scan-col .vuln-link,
color: orange; .repo-panel-tags-element .security-scan-col .vuln-link span {
text-decoration: none !important
} }
.repo-panel-tags-element .fa-flag.High { .repo-panel-tags-element .security-scan-col .has-vulns.Critical .highest-vuln,
color: red; .repo-panel-tags-element .security-scan-col .has-vulns.Defcon1 .highest-vuln {
} }
.repo-panel-tags-element .vuln-dropdown ul { .repo-panel-tags-element .other-vulns {
min-width: 400px; color: black;
}
@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) {

View file

@ -15,4 +15,35 @@
margin-right: 10px; margin-right: 10px;
margin-bottom: 10px; margin-bottom: 10px;
color: #ccc; color: #ccc;
}
.filter-box.floating {
float: right;
min-width: 300px;
margin-top: 0px;
position: relative;
}
.filter-box.floating .filter-message {
position: absolute;
left: -200px;
top: 7px;
}
@media (max-width: 767px) {
.filter-box.floating {
float: none;
width: 100%;
display: block;
margin-top: 10px;
}
.filter-box.floating .form-control {
max-width: 100%;
}
.filter-box.floating .filter-message {
display: none;
}
} }

View file

@ -0,0 +1,19 @@
.vulnerability-priority-view-element i.fa {
margin-right: 4px;
}
.vulnerability-priority-view-element.Unknown,
.vulnerability-priority-view-element.Low,
.vulnerability-priority-view-element.Negligable {
color: #9B9B9B;
}
.vulnerability-priority-view-element.Medium {
color: #FCA657;
}
.vulnerability-priority-view-element.High,
.vulnerability-priority-view-element.Critical,
.vulnerability-priority-view-element.Defcon1 {
color: #D64456;
}

View file

@ -23,3 +23,23 @@
.image-view .co-tab-content h3 { .image-view .co-tab-content h3 {
margin-bottom: 20px; margin-bottom: 20px;
} }
.image-view .fa-bug {
margin-right: 4px;
}
.image-view .co-filter-box {
float: right;
min-width: 300px;
margin-bottom: 10px;
}
.image-view .co-filter-box .current-filtered {
display: inline-block;
margin-right: 10px;
color: #999;
}
.image-view .co-filter-box input {
display: inline-block;
}

View file

@ -81,17 +81,17 @@
style="min-width: 120px;"> style="min-width: 120px;">
<a href="javascript:void(0)" ng-click="orderBy('last_modified_datetime')">Last Modified</a> <a href="javascript:void(0)" ng-click="orderBy('last_modified_datetime')">Last Modified</a>
</td> </td>
<td class="hidden-xs"
ng-class="tablePredicateClass('security_scanned', options.predicate, options.reverse)"
style="min-width: 120px;"
quay-require="['SECURITY_SCANNER']">
Security Scan
</td>
<td class="hidden-xs" <td class="hidden-xs"
ng-class="tablePredicateClass('size', options.predicate, options.reverse)" ng-class="tablePredicateClass('size', options.predicate, options.reverse)"
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 }}"
@ -113,43 +113,51 @@
<span am-time-ago="tag.last_modified" bo-if="tag.last_modified"></span> <span am-time-ago="tag.last_modified" bo-if="tag.last_modified"></span>
<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 quay-require="['SECURITY_SCANNER']" class="security-scan-col">
<td>
<span class="cor-loader-inline" ng-if="getTagVulnerabilities(tag).loading"></span> <span class="cor-loader-inline" ng-if="getTagVulnerabilities(tag).loading"></span>
<span ng-if="!getTagVulnerabilities(tag).loading"> <span ng-if="!getTagVulnerabilities(tag).loading">
<i class="fa fa-flag-o" ng-if="!getTagVulnerabilities(tag).indexed" <!-- Scanning -->
data-title="Image is currently being checked for vulnerabilities" <span class="scanning" ng-if="!getTagVulnerabilities(tag).security_indexed"
bs-tooltip> data-title="The image for this tag is queued to be scanned for vulnerabilities"
</i> bs-tooltip>Queued for scan</span>
<i class="fa fa-flag None"
ng-if="getTagVulnerabilities(tag).indexed && !getTagVulnerabilities(tag).hasVulnerabilities" <!-- No Vulns -->
data-title="Image has no vulnerabilities" <span class="no-vulns"
bs-tooltip> ng-if="getTagVulnerabilities(tag).security_indexed && !getTagVulnerabilities(tag).hasVulnerabilities"
</i> data-title="The image for this tag has no vulnerabilities as found in our database"
<div class="dropdown vuln-dropdown" style="text-align: left;" bs-tooltip
ng-if="getTagVulnerabilities(tag).indexed && getTagVulnerabilities(tag).hasVulnerabilities"> bindonce>
<i class="fa fa-flag" <a bo-href-i="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ tag.image_id }}?tab=security">
data-title="Image has vulnerabilities" <i class="fa fa-check-circle"></i>
data-toggle="dropdown" Passed
ng-class="getTagVulnerabilities(tag).highestVulnerability.Priority" </a>
bs-tooltip> </span>
</i>
<ul class="dropdown-menu pull-right"> <!-- Vulns -->
<li ng-repeat="vuln in getTagVulnerabilities(tag).vulnerabilities"> <span ng-if="getTagVulnerabilities(tag).security_indexed && getTagVulnerabilities(tag).hasVulnerabilities"
<a href="{{ vuln.Link }}" target="_new"> ng-class="getTagVulnerabilities(tag).highestVulnerability.Priority"
<div class="vuln-name"> class="has-vulns" bindonce>
<i class="fa fa-flag" bo-class="vuln.Priority"></i> <a class="vuln-link" bo-href-i="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ tag.image_id }}?tab=security"
{{ vuln.ID }} data-title="The image for this tag has {{ getTagVulnerabilities(tag).highestVulnerability.Count }} {{ getTagVulnerabilities(tag).highestVulnerability.Priority }} level vulnerabilities"
</div> bs-tooltip>
<div class="vuln-description"> <span class="highest-vuln">
{{ vuln.Description }} <span class="vulnerability-priority-view" priority="getTagVulnerabilities(tag).highestVulnerability.Priority">
</div> {{ getTagVulnerabilities(tag).highestVulnerability.Count }}
</a> </span>
</li> </span>
</ul>
</div> <span ng-if="getTagVulnerabilities(tag).vulnerabilities.length - getTagVulnerabilities(tag).highestVulnerability.Count > 0"
class="other-vulns">
+ {{ getTagVulnerabilities(tag).vulnerabilities.length - getTagVulnerabilities(tag).highestVulnerability.Count }} others
</span>
</a>
<a bo-href-i="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ tag.image_id }}?tab=security" style="display: inline-block; margin-left: 6px;">
More Info
</a>
</span>
</span> </span>
</td> </td>
<td class="hidden-xs" bo-text="tag.size | bytes"></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>

View file

@ -0,0 +1,5 @@
<span class="vulnerability-priority-view-element" ng-class="priority">
<i class="fa fa-exclamation-triangle"></i>
<span ng-transclude/>
{{ priority }}
</span>

View file

@ -34,8 +34,8 @@ angular.module('quay').directive('repoPanelTags', function () {
$scope.tagHistory = {}; $scope.tagHistory = {};
$scope.tagActionHandler = null; $scope.tagActionHandler = null;
$scope.showingHistory = false; $scope.showingHistory = false;
$scope.tagsPerPage = 50; $scope.tagsPerPage = 25;
$scope.tagVulnerabilities = {}; $scope.imageVulnerabilities = {};
var setTagState = function() { var setTagState = function() {
if (!$scope.repository || !$scope.selectedTags) { return; } if (!$scope.repository || !$scope.selectedTags) { return; }
@ -57,7 +57,7 @@ angular.module('quay').directive('repoPanelTags', function () {
allTags.push(tagInfo); allTags.push(tagInfo);
if (!$scope.options.tagFilter || tag.indexOf($scope.options.tagFilter) >= 0 || if (!$scope.options.tagFilter || tagfOf($scope.options.tagFilter) >= 0 ||
tagInfo.image_id.indexOf($scope.options.tagFilter) >= 0) { tagInfo.image_id.indexOf($scope.options.tagFilter) >= 0) {
tags.push(tagInfo); tags.push(tagInfo);
} }
@ -150,51 +150,66 @@ angular.module('quay').directive('repoPanelTags', function () {
setTagState(); setTagState();
}); });
$scope.loadTagVulnerabilities = function(tag, tagData) { $scope.loadImageVulnerabilities = function(image_id, imageData) {
var params = { var params = {
'tag': tag.name, 'imageid': image_id,
'repository': $scope.repository.namespace + '/' + $scope.repository.name, 'repository': $scope.repository.namespace + '/' + $scope.repository.name,
}; };
ApiService.getRepoTagVulnerabilities(null, params).then(function(resp) { ApiService.getRepoImageVulnerabilities(null, params).then(function(resp) {
tagData.indexed = resp.security_indexed; imageData.security_indexed = resp.security_indexed;
tagData.loading = false; imageData.loading = false;
if (resp.security_indexed) { if (imageData.security_indexed) {
tagData.hasVulnerabilities = !!resp.data.Vulnerabilities.length; var vulnerabilities = resp.data.Vulnerabilities;
tagData.vulnerabilities = resp.data.Vulnerabilities;
imageData.hasVulnerabilities = !!vulnerabilities.length;
imageData.vulnerabilities = vulnerabilities;
var highest = {
'Priority': 'Unknown',
'Count': 0,
'index': 100000
};
var highest = null;
resp.data.Vulnerabilities.forEach(function(v) { resp.data.Vulnerabilities.forEach(function(v) {
if (highest == null || if (VulnerabilityService.LEVELS[v.Priority].index < highest.index) {
VulnerabilityService.LEVELS[v.Priority].index < VulnerabilityService.LEVELS[highest.Priority].index) { highest = {
highest = v; 'Priority': v.Priority,
'Count': 1,
'index': VulnerabilityService.LEVELS[v.Priority].index
}
} else if (VulnerabilityService.LEVELS[v.Priority].index == highest.index) {
highest['Count']++;
} }
}); });
tagData.highestVulnerability = highest; imageData.highestVulnerability = highest;
} }
}, function() { }, function() {
tagData.loading = false; imageData.loading = false;
tagData.hasError = true; imageData.hasError = true;
}); });
}; };
$scope.getTagVulnerabilities = function(tag) { $scope.getTagVulnerabilities = function(tag) {
return $scope.getImageVulnerabilities(tag.image_id);
};
$scope.getImageVulnerabilities = function(image_id) {
if (!$scope.repository) { if (!$scope.repository) {
return return
} }
var tagName = tag.name; if (!$scope.imageVulnerabilities[image_id]) {
if (!$scope.tagVulnerabilities[tagName]) { $scope.imageVulnerabilities[image_id] = {
$scope.tagVulnerabilities[tagName] = {
'loading': true 'loading': true
}; };
$scope.loadTagVulnerabilities(tag, $scope.tagVulnerabilities[tagName]); $scope.loadImageVulnerabilities(image_id, $scope.imageVulnerabilities[image_id]);
} }
return $scope.tagVulnerabilities[tagName]; return $scope.imageVulnerabilities[image_id];
}; };
$scope.clearSelectedTags = function() { $scope.clearSelectedTags = function() {

View file

@ -0,0 +1,18 @@
/**
* An element which displays a priority triangle for vulnerabilities.
*/
angular.module('quay').directive('vulnerabilityPriorityView', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/vulnerability-priority-view.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'priority': '=priority'
},
controller: function($scope, $element) {
}
};
return directiveDefinitionObject;
});

View file

@ -10,11 +10,16 @@
}) })
}]); }]);
function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService) { function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService, VulnerabilityService, Features) {
var namespace = $routeParams.namespace; var namespace = $routeParams.namespace;
var name = $routeParams.name; var name = $routeParams.name;
var imageid = $routeParams.image; var imageid = $routeParams.image;
$scope.options = {
'vulnFilter': '',
'packageFilter': ''
};
var loadImage = function() { var loadImage = function() {
var params = { var params = {
'repository': namespace + '/' + name, 'repository': namespace + '/' + name,
@ -41,7 +46,7 @@
loadRepository(); loadRepository();
$scope.downloadPackages = function() { $scope.downloadPackages = function() {
if ($scope.packagesResource) { return; } if (!Features.SECURITY_SCANNER || $scope.packagesResource) { return; }
var params = { var params = {
'repository': namespace + '/' + name, 'repository': namespace + '/' + name,
@ -53,6 +58,26 @@
}); });
}; };
$scope.loadImageVulnerabilities = function() {
if (!Features.SECURITY_SCANNER || $scope.vulnerabilitiesResource) { return; }
var params = {
'repository': namespace + '/' + name,
'imageid': imageid
};
$scope.vulnerabilitiesResource = ApiService.getRepoImageVulnerabilitiesAsResource(params).get(function(resp) {
$scope.vulerabilityInfo = resp;
$scope.vulnerabilities = [];
resp.data.Vulnerabilities.forEach(function(vuln) {
vuln_copy = jQuery.extend({}, vuln);
vuln_copy['index'] = VulnerabilityService.LEVELS[vuln['Priority']]['index'];
$scope.vulnerabilities.push(vuln_copy);
});
});
};
$scope.downloadChanges = function() { $scope.downloadChanges = function() {
if ($scope.changesResource) { return; } if ($scope.changesResource) { return; }

View file

@ -25,8 +25,14 @@
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="Security Scan" tab-target="#security"
tab-init="loadImageVulnerabilities()"
quay-show="Features.SECURITY_SCANNER">
<i class="fa fa-bug"></i>
</span>
<span class="cor-tab" tab-title="Packages" tab-target="#packages" <span class="cor-tab" tab-title="Packages" tab-target="#packages"
tab-init="downloadPackages()"> tab-init="downloadPackages()"
quay-show="Features.SECURITY_SCANNER">
<i class="fa ci-package"></i> <i class="fa ci-package"></i>
</span> </span>
</div> <!-- /cor-tabs --> </div> <!-- /cor-tabs -->
@ -58,9 +64,57 @@
</div> </div>
</div> </div>
<!-- Security -->
<div id="security" class="tab-pane" quay-require="['SECURITY_SCANNER']">
<div class="resource-view" resource="vulnerabilitiesResource" error-message="'Could not load security information for image'">
<div class="filter-box floating" collection="vulnerabilities" filter-model="options.vulnFilter" filter-name="Vulnerabilities" ng-if="vulerabilityInfo.security_indexed && vulnerabilities.length"></div>
<h3>Image Security</h3>
<div class="empty" ng-if="!vulerabilityInfo.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="vulerabilityInfo.security_indexed && !vulnerabilities.length">
<div class="empty-primary-msg">This image contains no recognized security vulnerabilities</div>
<div class="empty-secondary-msg">
Quay currently indexes Debian, Red Hat and Ubuntu packages.
</div>
</div>
<div ng-if="vulerabilityInfo.security_indexed && vulnerabilities.length">
<table class="co-table">
<thead>
<td style="width: 200px;">Vulnerability</td>
<td style="width: 200px;">Priority</td>
<td>Description</td>
</thead>
<tr ng-repeat="vulnerability in vulnerabilities | filter:options.vulnFilter | orderBy:'index'">
<td><a href="{{ vulnerability.Link }}" target="_blank">{{ vulnerability.ID }}</a></td>
<td>
<span class="vulnerability-priority-view" priority="vulnerability.Priority"></span>
<td>{{ vulnerability.Description }}</td>
</tr>
</table>
<div class="empty" ng-if="(vulnerabilities | filter:options.vulnFilter).length == 0"
style="margin-top: 20px;">
<div class="empty-primary-msg">No matching vulnerabilities found</div>
<div class="empty-secondary-msg">
Please adjust your filter above.
</div>
</div>
</div>
</div>
</div>
<!-- Packages --> <!-- Packages -->
<div id="packages" class="tab-pane"> <div id="packages" class="tab-pane" quay-require="['SECURITY_SCANNER']">
<div class="resource-view" resource="packagesResource" error-message="'Could not load image packages'"> <div class="resource-view" resource="packagesResource" error-message="'Could not load image packages'">
<div class="filter-box floating" collection="packages.data.Packages" filter-model="options.packageFilter" filter-name="Packages" ng-if="packages.security_indexed && packages.data.Packages.length"></div>
<h3>Image Packages</h3> <h3>Image Packages</h3>
<div class="empty" ng-if="!packages.security_indexed"> <div class="empty" ng-if="!packages.security_indexed">
<div class="empty-primary-msg">This image has not been indexed yet</div> <div class="empty-primary-msg">This image has not been indexed yet</div>
@ -75,19 +129,27 @@
</div> </div>
</div> </div>
<table class="table" ng-if="packages.security_indexed && packages.data.Packages.length"> <table class="co-table" ng-if="packages.security_indexed && packages.data.Packages.length">
<thead> <thead>
<th>Package Name</th> <td>Package Name</td>
<th>Package Version</th> <td>Package Version</td>
<th>OS</th> <td>OS</td>
</thead> </thead>
<tr ng-repeat="package in packages.data.Packages | orderBy:'Name'"> <tr ng-repeat="package in packages.data.Packages | filter:options.packageFilter | orderBy:'Name'">
<td>{{ package.Name }}</td> <td>{{ package.Name }}</td>
<td>{{ package.Version }}</td> <td>{{ package.Version }}</td>
<td>{{ package.OS }}</td> <td>{{ package.OS }}</td>
</tr> </tr>
</table> </table>
<div class="empty" ng-if="(packages.data.Packages | filter:options.packageFilter).length == 0"
style="margin-top: 20px;">
<div class="empty-primary-msg">No matching packages found</div>
<div class="empty-secondary-msg">
Please adjust your filter above.
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -256,7 +256,7 @@ class SecurityWorker(Worker):
# callback code, etc. # callback code, etc.
try: try:
logger.debug('Loading vulnerabilities for layer %s', img['image_id']) logger.debug('Loading vulnerabilities for layer %s', img['image_id'])
response = secscan_api.call('layers/%s/vulnerabilities', request['ID']) response = secscan_api.call('layers/%s/vulnerabilities', None, request['ID'])
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
logger.debug('Timeout when calling Sec') logger.debug('Timeout when calling Sec')
continue continue