Implement UI support for manifest lists
This commit is contained in:
parent
276d0d571d
commit
c46b11bac1
14 changed files with 338 additions and 157 deletions
|
@ -39,7 +39,7 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
$scope.expandedView = false;
|
||||
$scope.labelCache = {};
|
||||
|
||||
$scope.imageVulnerabilities = {};
|
||||
$scope.manifestVulnerabilities = {};
|
||||
$scope.repoDelegationsInfo = null;
|
||||
|
||||
$scope.defcon1 = {};
|
||||
|
@ -251,76 +251,6 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
loadRepoSignatures();
|
||||
}, true);
|
||||
|
||||
$scope.loadImageVulnerabilities = function(image_id, imageData) {
|
||||
VulnerabilityService.loadImageVulnerabilities($scope.repository, image_id, function(resp) {
|
||||
imageData.loading = false;
|
||||
imageData.status = resp['status'];
|
||||
|
||||
if (imageData.status == 'scanned') {
|
||||
var vulnerabilities = [];
|
||||
var highest = {
|
||||
'Severity': 'Unknown',
|
||||
'Count': 0,
|
||||
'index': 100000
|
||||
};
|
||||
|
||||
VulnerabilityService.forEachVulnerability(resp, function(vuln) {
|
||||
if (VulnerabilityService.LEVELS[vuln.Severity].index == 0) {
|
||||
$scope.defcon1[vuln.Name] = vuln;
|
||||
$scope.hasDefcon1 = true;
|
||||
}
|
||||
|
||||
if (VulnerabilityService.LEVELS[vuln.Severity].index < highest.index) {
|
||||
highest = {
|
||||
'Priority': vuln.Severity,
|
||||
'Count': 1,
|
||||
'index': VulnerabilityService.LEVELS[vuln.Severity].index,
|
||||
'Color': VulnerabilityService.LEVELS[vuln.Severity].color
|
||||
}
|
||||
} else if (VulnerabilityService.LEVELS[vuln.Severity].index == highest.index) {
|
||||
highest['Count']++;
|
||||
}
|
||||
|
||||
vulnerabilities.push(vuln);
|
||||
});
|
||||
|
||||
imageData.hasFeatures = VulnerabilityService.hasFeatures(resp);
|
||||
imageData.hasVulnerabilities = !!vulnerabilities.length;
|
||||
imageData.vulnerabilities = vulnerabilities;
|
||||
imageData.highestVulnerability = highest;
|
||||
imageData.featuresInfo = VulnerabilityService.buildFeaturesInfo(null, resp);
|
||||
imageData.vulnerabilitiesInfo = VulnerabilityService.buildVulnerabilitiesInfo(null, resp);
|
||||
}
|
||||
}, function() {
|
||||
imageData.loading = false;
|
||||
imageData.hasError = true;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getTagVulnerabilities = function(tag) {
|
||||
if (!tag.manifest_digest) {
|
||||
return 'nodigest';
|
||||
}
|
||||
|
||||
return $scope.getImageVulnerabilities(tag.image_id);
|
||||
};
|
||||
|
||||
$scope.getImageVulnerabilities = function(image_id) {
|
||||
if (!$scope.repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.imageVulnerabilities[image_id]) {
|
||||
$scope.imageVulnerabilities[image_id] = {
|
||||
'loading': true
|
||||
};
|
||||
|
||||
$scope.loadImageVulnerabilities(image_id, $scope.imageVulnerabilities[image_id]);
|
||||
}
|
||||
|
||||
return $scope.imageVulnerabilities[image_id];
|
||||
};
|
||||
|
||||
$scope.clearSelectedTags = function() {
|
||||
$scope.checkedTags.setChecked([]);
|
||||
};
|
||||
|
@ -499,6 +429,27 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
$scope.handleLabelsChanged = function(manifest_digest) {
|
||||
delete $scope.labelCache[manifest_digest];
|
||||
};
|
||||
|
||||
$scope.manifestsOf = function(tag) {
|
||||
if (!tag.is_manifest_list || !tag.manifest) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!tag._mapped_manifests) {
|
||||
// Calculate once and cache to avoid angular digest cycles.
|
||||
tag._mapped_manifests = tag.manifest.manifests.map(function(manifest) {
|
||||
return {
|
||||
'raw': manifest,
|
||||
'os': manifest.platform.os,
|
||||
'size': manifest.size,
|
||||
'digest': manifest.digest,
|
||||
'description': `${manifest.platform.os} on ${manifest.platform.architecture}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return tag._mapped_manifests;
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
|
|
|
@ -39,7 +39,7 @@ angular.module('quay').directive('fetchTagDialog', function () {
|
|||
});
|
||||
}
|
||||
|
||||
if ($scope.repository) {
|
||||
if ($scope.repository && $scope.currentTag && !$scope.currentTag.is_manifest_list) {
|
||||
$scope.formats.push({
|
||||
'title': 'Squashed Docker Image',
|
||||
'icon': 'ci-squashed',
|
||||
|
@ -49,7 +49,7 @@ angular.module('quay').directive('fetchTagDialog', function () {
|
|||
});
|
||||
}
|
||||
|
||||
if (Features.ACI_CONVERSION) {
|
||||
if (Features.ACI_CONVERSION && $scope.currentTag && !$scope.currentTag.is_manifest_list) {
|
||||
$scope.formats.push({
|
||||
'title': 'rkt Fetch',
|
||||
'icon': 'rocket-icon',
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<div class="manifest-security-view-element">
|
||||
<span class="cor-loader-inline" ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).loading"></span>
|
||||
<span class="vuln-load-error" ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).hasError"
|
||||
data-title="The vulnerabilities for this tag could not be retrieved at the present time, try again later"
|
||||
bs-tooltip>
|
||||
<i class="fa fa-times-circle"></i>
|
||||
Could not load security information
|
||||
</span>
|
||||
|
||||
<span ng-if="!$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).loading">
|
||||
<!-- Queued -->
|
||||
<span class="scanning" ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).status == 'queued'"
|
||||
data-title="The image for this tag is queued to be scanned for vulnerabilities"
|
||||
bs-tooltip>
|
||||
<i class="fa fa-ellipsis-h"></i>
|
||||
Queued
|
||||
</span>
|
||||
|
||||
<!-- Scan Failed -->
|
||||
<span class="failed-scan" ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).status == 'failed'"
|
||||
data-title="The image for this tag could not be scanned for vulnerabilities"
|
||||
bs-tooltip>
|
||||
<span class="donut-chart" width="22" data="[{'index': 0, 'value': 1, 'color': '#eee'}]"></span>
|
||||
Unable to scan
|
||||
</span>
|
||||
|
||||
<!-- No Features -->
|
||||
<span class="failed-scan"
|
||||
ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).status == 'scanned' && !$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).hasFeatures"
|
||||
data-title="The image for this tag has an operating system or package manager unsupported by Quay Security Scanner"
|
||||
bs-tooltip
|
||||
bindonce>
|
||||
<span class="donut-chart" width="22" data="[{'index': 0, 'value': 1, 'color': '#eee'}]"></span>
|
||||
Unsupported
|
||||
</span>
|
||||
|
||||
<!-- Features and No Vulns -->
|
||||
<span class="no-vulns"
|
||||
ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).status == 'scanned' && $ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).hasFeatures && !$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).hasVulnerabilities"
|
||||
data-title="The image for this tag has no vulnerabilities as found in our database"
|
||||
bs-tooltip
|
||||
bindonce>
|
||||
<a bo-href-i="/repository/{{ $ctrl.repository.namespace }}/{{ $ctrl.repository.name }}/manifest/{{ tag.manifest_digest }}?tab=vulnerabilities">
|
||||
<span class="donut-chart" width="22" data="[{'index': 0, 'value': 1, 'color': '#2FC98E'}]"></span>
|
||||
Passed
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<!-- Vulns -->
|
||||
<span ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).status == 'scanned' && $ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).hasFeatures && $ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).hasVulnerabilities"
|
||||
ng-class="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).highestVulnerability.Priority"
|
||||
class="has-vulns" bindonce>
|
||||
|
||||
<a class="vuln-link" bo-href-i="/repository/{{ $ctrl.repository.namespace }}/{{ $ctrl.repository.name }}/manifest/{{ tag.manifest_digest }}?tab=vulnerabilities"
|
||||
data-title="This tag has {{ $ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).vulnerabilities.length }} vulnerabilities across {{ $ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).featuresInfo.brokenFeaturesCount }} packages"
|
||||
bs-tooltip>
|
||||
<!-- Donut -->
|
||||
<span class="donut-chart" min-percent="10" width="22" data="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).vulnerabilitiesInfo.severityBreakdown"></span>
|
||||
|
||||
<!-- Messaging -->
|
||||
<span class="highest-vuln">
|
||||
<span class="vulnerability-priority-view" hide-icon="true" priority="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).highestVulnerability.Priority">
|
||||
{{ $ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).highestVulnerability.Count }}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<span class="dot" ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).vulnerabilitiesInfo.fixable.length">·</span>
|
||||
<a class="vuln-link" bo-href-i="/repository/{{ $ctrl.repository.namespace }}/{{ $ctrl.repository.name }}/manifest/{{ tag.manifest_digest }}?tab=vulnerabilities&fixable=true" ng-if="$ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).vulnerabilitiesInfo.fixable.length">
|
||||
{{ $ctrl.getSecurityStatus($ctrl.repository, $ctrl.manifestDigest).vulnerabilitiesInfo.fixable.length }} fixable
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
|
@ -0,0 +1,76 @@
|
|||
import { Input, Component, Inject } from 'ng-metadata/core';
|
||||
import { Repository } from '../../../types/common.types';
|
||||
|
||||
|
||||
/**
|
||||
* A component that displays the security status of a manifest.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'manifest-security-view',
|
||||
templateUrl: '/static/js/directives/ui/manifest-security-view/manifest-security-view.component.html',
|
||||
})
|
||||
export class ManifestSecurityView {
|
||||
@Input('<') public repository: Repository;
|
||||
@Input('<') public manifestDigest: string;
|
||||
|
||||
private cachedSecurityStatus: Object = null;
|
||||
|
||||
constructor(@Inject('VulnerabilityService') private VulnerabilityService: any) {
|
||||
}
|
||||
|
||||
private getSecurityStatus(repository: Repository, manifestDigest: string): Object {
|
||||
if (repository == null || !manifestDigest) {
|
||||
return {'status': 'loading'};
|
||||
}
|
||||
|
||||
if (this.cachedSecurityStatus) {
|
||||
return this.cachedSecurityStatus;
|
||||
}
|
||||
|
||||
this.cachedSecurityStatus = {'status': 'loading'};
|
||||
this.loadManifestVulnerabilities(this.cachedSecurityStatus);
|
||||
return this.cachedSecurityStatus;
|
||||
}
|
||||
|
||||
private loadManifestVulnerabilities(securityStatus) {
|
||||
this.VulnerabilityService.loadManifestVulnerabilities(this.repository, this.manifestDigest, (resp) => {
|
||||
securityStatus.loading = false;
|
||||
securityStatus.status = resp['status'];
|
||||
|
||||
if (securityStatus.status == 'scanned') {
|
||||
var vulnerabilities = [];
|
||||
var highest = {
|
||||
'Priority': 'Unknown',
|
||||
'Count': 0,
|
||||
'index': 100000,
|
||||
'Color': 'gray',
|
||||
};
|
||||
|
||||
this.VulnerabilityService.forEachVulnerability(resp, function(vuln) {
|
||||
if (this.VulnerabilityService.LEVELS[vuln.Severity].index < highest.index) {
|
||||
highest = {
|
||||
'Priority': vuln.Severity,
|
||||
'Count': 1,
|
||||
'index': this.VulnerabilityService.LEVELS[vuln.Severity].index,
|
||||
'Color': this.VulnerabilityService.LEVELS[vuln.Severity].color
|
||||
}
|
||||
} else if (this.VulnerabilityService.LEVELS[vuln.Severity].index == highest.index) {
|
||||
highest['Count']++;
|
||||
}
|
||||
|
||||
vulnerabilities.push(vuln);
|
||||
});
|
||||
|
||||
securityStatus.hasFeatures = this.VulnerabilityService.hasFeatures(resp);
|
||||
securityStatus.hasVulnerabilities = !!vulnerabilities.length;
|
||||
securityStatus.vulnerabilities = vulnerabilities;
|
||||
securityStatus.highestVulnerability = highest;
|
||||
securityStatus.featuresInfo = this.VulnerabilityService.buildFeaturesInfo(null, resp);
|
||||
securityStatus.vulnerabilitiesInfo = this.VulnerabilityService.buildVulnerabilitiesInfo(null, resp);
|
||||
}
|
||||
}, function() {
|
||||
securityStatus.loading = false;
|
||||
securityStatus.hasError = true;
|
||||
});
|
||||
};
|
||||
}
|
Reference in a new issue