diff --git a/src/Bootstrap/dist/css/bootstrap-theme.css b/src/Bootstrap/dist/css/bootstrap-theme.css
index 1ab92c7f02..d318722783 100644
--- a/src/Bootstrap/dist/css/bootstrap-theme.css
+++ b/src/Bootstrap/dist/css/bootstrap-theme.css
@@ -613,6 +613,32 @@ img.reserved-indicator-icon {
margin-top: 75px;
margin-bottom: 0px;
}
+.package-warning {
+ padding-right: 8px;
+ padding-left: 8px;
+ border-radius: 2px;
+ margin-right: 5px;
+ color: #323130;
+}
+.package-warning--vulnerable {
+ padding-right: 8px;
+ padding-left: 8px;
+ border-radius: 2px;
+ margin-right: 5px;
+ color: #323130;
+ background-color: #fed9cc;
+}
+.package-warning--vulnerable i {
+ color: #d83b01;
+}
+.package-warning--deprecated {
+ padding-right: 8px;
+ padding-left: 8px;
+ border-radius: 2px;
+ margin-right: 5px;
+ color: #323130;
+ background-color: #fff4ce;
+}
.multi-select-dropdown {
position: relative;
display: block;
@@ -1147,6 +1173,9 @@ p.frameworktableinfo-text {
padding-left: 10px;
border-left: 1px solid #dbdbdb;
}
+.page-api-keys .api-key-details .package-list li {
+ overflow-y: visible;
+}
.page-api-keys .api-key-details:not(:first-child) {
border-top: 1px solid #dbdbdb;
}
@@ -1467,9 +1496,15 @@ p.frameworktableinfo-text {
white-space: nowrap;
text-overflow: ellipsis;
}
+.page-package-details .owner-list li .profile-icon {
+ margin: 1.5px;
+}
+.page-package-details .owner-list li .username {
+ margin-left: 6.5px;
+}
.page-package-details .owner-list img {
- margin-right: 8px;
border-radius: 5px;
+ margin: 2px;
}
.page-package-details .report-link i {
color: #BE0151;
diff --git a/src/Bootstrap/dist/js/bootstrap.js b/src/Bootstrap/dist/js/bootstrap.js
index 5645023431..c7cc091728 100644
--- a/src/Bootstrap/dist/js/bootstrap.js
+++ b/src/Bootstrap/dist/js/bootstrap.js
@@ -585,6 +585,8 @@ if (typeof jQuery === 'undefined') {
toggle: true
}
+ Collapse.ARIA_EXPANDED_ALLOWED_ROLES = ['application', 'button', 'checkbox', 'combobox', 'gridcell', 'link', 'listbox', 'menuitem', 'row', 'rowheader', 'tab', 'treeitem']
+
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
@@ -615,7 +617,11 @@ if (typeof jQuery === 'undefined') {
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
- .attr('aria-expanded', true)
+
+ // the aria-expanded attribute is only allowed when the element has an allowed role
+ if (Collapse.ARIA_EXPANDED_ALLOWED_ROLES.includes(this.$element.attr('role'))) {
+ this.$element.attr('aria-expanded', true)
+ }
this.$trigger
.removeClass('collapsed')
@@ -655,7 +661,11 @@ if (typeof jQuery === 'undefined') {
this.$element
.addClass('collapsing')
.removeClass('collapse in')
- .attr('aria-expanded', false)
+
+ // the aria-expanded attribute is only allowed when the element has an allowed role
+ if (Collapse.ARIA_EXPANDED_ALLOWED_ROLES.includes(this.$element.attr('role'))) {
+ this.$element.attr('aria-expanded', false)
+ }
this.$trigger
.addClass('collapsed')
@@ -696,7 +706,10 @@ if (typeof jQuery === 'undefined') {
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in')
- $element.attr('aria-expanded', isOpen)
+ if (Collapse.ARIA_EXPANDED_ALLOWED_ROLES.includes(this.$element.attr('role'))) {
+ $element.attr('aria-expanded', isOpen)
+ }
+
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen)
@@ -2345,14 +2358,12 @@ if (typeof jQuery === 'undefined') {
.end()
.find('[data-toggle="tab"]')
.attr('tabindex', "-1")
- .attr('aria-expanded', false)
.attr('aria-selected', false)
element
.addClass('active')
.find('[data-toggle="tab"]')
.attr('tabindex', "0")
- .attr('aria-expanded', true)
.attr('aria-selected', true)
if (transition) {
diff --git a/src/Bootstrap/js/collapse.js b/src/Bootstrap/js/collapse.js
index a1a5ca49b6..fcb28893ed 100644
--- a/src/Bootstrap/js/collapse.js
+++ b/src/Bootstrap/js/collapse.js
@@ -38,6 +38,8 @@
toggle: true
}
+ Collapse.ARIA_EXPANDED_ALLOWED_ROLES = ['application', 'button', 'checkbox', 'combobox', 'gridcell', 'link', 'listbox', 'menuitem', 'row', 'rowheader', 'tab', 'treeitem']
+
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
@@ -68,7 +70,11 @@
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
- .attr('aria-expanded', true)
+
+ // the aria-expanded attribute is only allowed when the element has an allowed role
+ if (Collapse.ARIA_EXPANDED_ALLOWED_ROLES.includes(this.$element.attr('role'))) {
+ this.$element.attr('aria-expanded', true)
+ }
this.$trigger
.removeClass('collapsed')
@@ -108,7 +114,11 @@
this.$element
.addClass('collapsing')
.removeClass('collapse in')
- .attr('aria-expanded', false)
+
+ // the aria-expanded attribute is only allowed when the element has an allowed role
+ if (Collapse.ARIA_EXPANDED_ALLOWED_ROLES.includes(this.$element.attr('role'))) {
+ this.$element.attr('aria-expanded', false)
+ }
this.$trigger
.addClass('collapsed')
@@ -149,7 +159,10 @@
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in')
- $element.attr('aria-expanded', isOpen)
+ if (Collapse.ARIA_EXPANDED_ALLOWED_ROLES.includes(this.$element.attr('role'))) {
+ $element.attr('aria-expanded', isOpen)
+ }
+
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen)
diff --git a/src/Bootstrap/js/tab.js b/src/Bootstrap/js/tab.js
index 436cf3b724..3f288e62aa 100644
--- a/src/Bootstrap/js/tab.js
+++ b/src/Bootstrap/js/tab.js
@@ -77,14 +77,12 @@
.end()
.find('[data-toggle="tab"]')
.attr('tabindex', "-1")
- .attr('aria-expanded', false)
.attr('aria-selected', false)
element
.addClass('active')
.find('[data-toggle="tab"]')
.attr('tabindex', "0")
- .attr('aria-expanded', true)
.attr('aria-selected', true)
if (transition) {
diff --git a/src/Bootstrap/less/theme/common-list-packages.less b/src/Bootstrap/less/theme/common-list-packages.less
index 523193ab79..75d25c23cd 100644
--- a/src/Bootstrap/less/theme/common-list-packages.less
+++ b/src/Bootstrap/less/theme/common-list-packages.less
@@ -58,3 +58,30 @@
margin-top: 75px;
margin-bottom: 0px;
}
+
+@severe-warning-background-color: rgb(254, 217, 204);
+@severe-warning-icon-color: rgb(216, 59, 1);
+@warning-background-color: rgb(255, 244, 206);
+@package-warning-color: rgb(50, 49, 48);
+@badge-border-radius: 2px;
+
+.package-warning {
+ padding-right: 8px;
+ padding-left: 8px;
+ border-radius: @badge-border-radius;
+ margin-right: 5px;
+ color: @package-warning-color
+}
+
+.package-warning--vulnerable {
+ .package-warning;
+ background-color: @severe-warning-background-color;
+ i {
+ color: @severe-warning-icon-color;
+ }
+}
+
+.package-warning--deprecated {
+ .package-warning;
+ background-color: @warning-background-color;
+}
\ No newline at end of file
diff --git a/src/Bootstrap/less/theme/page-api-keys.less b/src/Bootstrap/less/theme/page-api-keys.less
index 4d05591dc6..02a48590e1 100644
--- a/src/Bootstrap/less/theme/page-api-keys.less
+++ b/src/Bootstrap/less/theme/page-api-keys.less
@@ -132,6 +132,11 @@
border-left: 1px solid @gray-lighter;
}
}
+ .package-list {
+ li {
+ overflow-y: visible;
+ }
+ }
}
.api-key-details:not(:first-child)
diff --git a/src/Bootstrap/less/theme/page-display-package.less b/src/Bootstrap/less/theme/page-display-package.less
index 6f6565dcd2..41a20b22f7 100644
--- a/src/Bootstrap/less/theme/page-display-package.less
+++ b/src/Bootstrap/less/theme/page-display-package.less
@@ -248,11 +248,19 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
+
+ .profile-icon {
+ margin: 1.5px;
+ }
+
+ .username{
+ margin-left: 6.5px;
+ }
}
img {
- margin-right: 8px;
border-radius: 5px;
+ margin: 2px;
}
}
diff --git a/src/NuGetGallery/App_Code/ViewHelpers.cshtml b/src/NuGetGallery/App_Code/ViewHelpers.cshtml
index b15da2a79a..3bacba6c96 100644
--- a/src/NuGetGallery/App_Code/ViewHelpers.cshtml
+++ b/src/NuGetGallery/App_Code/ViewHelpers.cshtml
@@ -577,7 +577,7 @@ var hlp = new AccordionHelper(name, formModelStatePrefix, expanded, page);
if (!disabled)
{
-
@content(MvcHtmlString.Empty)
diff --git a/src/NuGetGallery/App_Data/Files/Content/Trusted-Image-Domains.json b/src/NuGetGallery/App_Data/Files/Content/Trusted-Image-Domains.json
index de5ca2d786..acaf635f9d 100644
--- a/src/NuGetGallery/App_Data/Files/Content/Trusted-Image-Domains.json
+++ b/src/NuGetGallery/App_Data/Files/Content/Trusted-Image-Domains.json
@@ -34,6 +34,7 @@
"sonarcloud.io",
"travis-ci.com",
"travis-ci.org",
+ "wakatime.com",
"avatars.githubusercontent.com",
"raw.github.com",
"raw.githubusercontent.com",
diff --git a/src/NuGetGallery/Helpers/SearchResponseHelper.cs b/src/NuGetGallery/Helpers/SearchResponseHelper.cs
new file mode 100644
index 0000000000..d91d8723ef
--- /dev/null
+++ b/src/NuGetGallery/Helpers/SearchResponseHelper.cs
@@ -0,0 +1,86 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json.Linq;
+using NuGet.Services.Entities;
+
+namespace NuGetGallery.Helpers
+{
+ public static class SearchResponseHelper
+ {
+ public static ICollection
GetDeprecationsOrNull(JToken docDeprecation)
+ {
+ PackageDeprecation deprecation = null;
+ if (docDeprecation != null)
+ {
+ var docReasons = docDeprecation.Value("Reasons");
+ if (docReasons != null && docReasons.HasValues)
+ {
+ PackageDeprecationStatus status = PackageDeprecationStatus.NotDeprecated;
+ foreach (var reason in docReasons)
+ {
+ if (Enum.TryParse(reason.Value(), out var pdStatus))
+ {
+ status |= pdStatus;
+ }
+ }
+
+ var docAlternatePackage = docDeprecation["AlternatePackage"];
+ Package alternatePackage = null;
+ if (docAlternatePackage != null)
+ {
+ var range = docAlternatePackage.Value("Range");
+ var id = docAlternatePackage.Value("Id");
+ if (!string.IsNullOrEmpty(range) && !string.IsNullOrEmpty(id))
+ {
+ var version = string.Empty;
+ var commaIndex = range.IndexOf(",");
+ if (range.StartsWith("[") && commaIndex > 0)
+ {
+ var startIndex = 1;
+ version = range.Substring(startIndex, commaIndex - startIndex);
+ }
+
+ alternatePackage = new Package()
+ {
+ Id = id,
+ Version = version
+ };
+ }
+ }
+
+ deprecation = new PackageDeprecation()
+ {
+ CustomMessage = docDeprecation.Value("Message"),
+ Status = status,
+ AlternatePackage = alternatePackage
+ };
+ }
+ }
+
+ return deprecation == null ? null : new List() { deprecation };
+ }
+
+ public static ICollection GetVulnerabilities(JArray docVulnerabilities)
+ {
+ var vulnerabilities = new List();
+ if (docVulnerabilities != null)
+ {
+ vulnerabilities = docVulnerabilities.Select(v => new VulnerablePackageVersionRange()
+ {
+ Vulnerability = new PackageVulnerability()
+ {
+ AdvisoryUrl = v.Value("AdvisoryUrl"),
+ Severity = (PackageVulnerabilitySeverity)v.Value("Severity")
+ }
+ })
+ .ToList();
+ }
+
+ return vulnerabilities;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs
index adbcf78989..780e0d0a54 100644
--- a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs
+++ b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs
@@ -6,6 +6,7 @@
using System.Linq;
using NuGet.Services.Entities;
using NuGet.Versioning;
+using NuGetGallery.Helpers;
namespace NuGetGallery
{
@@ -217,60 +218,11 @@ private DisplayPackageViewModel SetupCommon(
viewModel.MaxVulnerabilitySeverity = default;
}
- viewModel.PackageWarningIconTitle =
- GetWarningIconTitle(viewModel.Version, deprecation, maxVulnerabilitySeverity);
+ viewModel.PackageWarningIconTitle = WarningTitleHelper.GetWarningIconTitle(viewModel.Version, deprecation, maxVulnerabilitySeverity);
return viewModel;
}
- private static string GetWarningIconTitle(
- string version,
- PackageDeprecation deprecation,
- PackageVulnerabilitySeverity? maxVulnerabilitySeverity)
- {
- // We want a tooltip title for the warning icon, which concatenates deprecation and vulnerability information cleanly
- var deprecationTitle = "";
- if (deprecation != null)
- {
- deprecationTitle = version;
- var isLegacy = deprecation.Status.HasFlag(PackageDeprecationStatus.Legacy);
- var hasCriticalBugs = deprecation.Status.HasFlag(PackageDeprecationStatus.CriticalBugs);
- if (hasCriticalBugs)
- {
- if (isLegacy)
- {
- deprecationTitle += " is deprecated because it's legacy and has critical bugs";
- }
- else
- {
- deprecationTitle += " is deprecated because it has critical bugs";
- }
- }
- else if (isLegacy)
- {
- deprecationTitle += " is deprecated because it's legacy and no longer maintained";
- }
- else
- {
- deprecationTitle += " is deprecated";
- }
- }
-
- if (maxVulnerabilitySeverity.HasValue)
- {
- var severity = Enum.GetName(typeof(PackageVulnerabilitySeverity), maxVulnerabilitySeverity)?.ToLowerInvariant() ?? "unknown";
- var vulnerabilitiesTitle = $"{version} has at least one vulnerability with {severity} severity.";
-
- return string.IsNullOrEmpty(deprecationTitle)
- ? vulnerabilitiesTitle
- : $"{deprecationTitle}; {vulnerabilitiesTitle}";
- }
-
- return string.IsNullOrEmpty(deprecationTitle)
- ? string.Empty
- : $"{deprecationTitle}.";
- }
-
private static string GetPushedBy(Package package, User currentUser, Dictionary pushedByCache)
{
var userPushedBy = package.User;
diff --git a/src/NuGetGallery/Helpers/ViewModelExtensions/ListPackageItemViewModelFactory.cs b/src/NuGetGallery/Helpers/ViewModelExtensions/ListPackageItemViewModelFactory.cs
index 98dcb9ee74..5236dfdfbb 100644
--- a/src/NuGetGallery/Helpers/ViewModelExtensions/ListPackageItemViewModelFactory.cs
+++ b/src/NuGetGallery/Helpers/ViewModelExtensions/ListPackageItemViewModelFactory.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NuGet.Services.Entities;
+using NuGetGallery.Helpers;
namespace NuGetGallery
{
@@ -39,6 +40,19 @@ private ListPackageItemViewModel SetupInternal(ListPackageItemViewModel viewMode
viewModel.MinClientVersion = package.MinClientVersion;
viewModel.Owners = package.PackageRegistration?.Owners?.Select(GetBasicUserViewModel).ToList();
viewModel.IsVerified = package.PackageRegistration?.IsVerified;
+ viewModel.IsDeprecated = package.Deprecations?.Count > 0;
+ viewModel.IsVulnerable = package.VulnerablePackageRanges?.Count > 0;
+
+ if (viewModel.IsDeprecated)
+ {
+ viewModel.DeprecationTitle = WarningTitleHelper.GetDeprecationTitle(package.Version, package.Deprecations.First().Status);
+ }
+
+ if (viewModel.IsVulnerable)
+ {
+ var maxVulnerabilitySeverity = package.VulnerablePackageRanges.Max(vpr => vpr.Vulnerability.Severity);
+ viewModel.VulnerabilityTitle = WarningTitleHelper.GetVulnerabilityTitle(package.Version, maxVulnerabilitySeverity);
+ }
viewModel.CanDisplayPrivateMetadata = CanPerformAction(currentUser, package, ActionsRequiringPermissions.DisplayPrivatePackageMetadata);
viewModel.CanEdit = CanPerformAction(currentUser, package, ActionsRequiringPermissions.EditPackage);
@@ -68,9 +82,10 @@ private static bool CanPerformAction(User currentUser, Package package, ActionRe
private static BasicUserViewModel GetBasicUserViewModel(User user)
{
- return new BasicUserViewModel {
- Username = user.Username,
- EmailAddress = user.EmailAddress,
+ return new BasicUserViewModel
+ {
+ Username = user.Username,
+ EmailAddress = user.EmailAddress,
IsOrganization = user is Organization,
IsLocked = user.IsLocked
};
diff --git a/src/NuGetGallery/Helpers/WarningTitleHelper.cs b/src/NuGetGallery/Helpers/WarningTitleHelper.cs
new file mode 100644
index 0000000000..7381d3cf11
--- /dev/null
+++ b/src/NuGetGallery/Helpers/WarningTitleHelper.cs
@@ -0,0 +1,69 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using NuGet.Services.Entities;
+
+namespace NuGetGallery.Helpers
+{
+ public static class WarningTitleHelper
+ {
+ public static string GetWarningIconTitle(
+ string version,
+ PackageDeprecation deprecation,
+ PackageVulnerabilitySeverity? maxVulnerabilitySeverity)
+ {
+ // We want a tooltip title for the warning icon, which concatenates deprecation and vulnerability information cleanly
+ var deprecationTitle = "";
+ if (deprecation != null)
+ {
+ deprecationTitle = GetDeprecationTitle(version, deprecation.Status);
+ }
+
+ if (maxVulnerabilitySeverity.HasValue)
+ {
+ var vulnerabilitiesTitle = GetVulnerabilityTitle(version, maxVulnerabilitySeverity.Value);
+ return string.IsNullOrEmpty(deprecationTitle)
+ ? vulnerabilitiesTitle
+ : $"{deprecationTitle.TrimEnd('.')}; {vulnerabilitiesTitle}";
+ }
+
+ return string.IsNullOrEmpty(deprecationTitle) ? string.Empty : deprecationTitle;
+ }
+
+ public static string GetVulnerabilityTitle(string version, PackageVulnerabilitySeverity maxVulnerabilitySeverity)
+ {
+ var severity = Enum.GetName(typeof(PackageVulnerabilitySeverity), maxVulnerabilitySeverity)?.ToLowerInvariant() ?? "unknown";
+ return $"{version} has at least one vulnerability with {severity} severity.";
+ }
+
+ public static string GetDeprecationTitle(string version, PackageDeprecationStatus status)
+ {
+ var deprecationTitle = version;
+ var isLegacy = status.HasFlag(PackageDeprecationStatus.Legacy);
+ var hasCriticalBugs = status.HasFlag(PackageDeprecationStatus.CriticalBugs);
+
+ if (hasCriticalBugs)
+ {
+ if (isLegacy)
+ {
+ deprecationTitle += " is deprecated because it is no longer maintained and has critical bugs";
+ }
+ else
+ {
+ deprecationTitle += " is deprecated because it has critical bugs";
+ }
+ }
+ else if (isLegacy)
+ {
+ deprecationTitle += " is deprecated because it is no longer maintained";
+ }
+ else
+ {
+ deprecationTitle += " is deprecated";
+ }
+
+ return $"{deprecationTitle}.";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs b/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs
index 52f715f45a..2a7e6b67ed 100644
--- a/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs
+++ b/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs
@@ -10,6 +10,7 @@
using NuGet.Services.Entities;
using NuGetGallery.Configuration;
using NuGetGallery.Diagnostics;
+using NuGetGallery.Helpers;
using NuGetGallery.Infrastructure.Lucene;
namespace NuGetGallery.Infrastructure.Search
@@ -25,7 +26,7 @@ public class ExternalSearchService : ISearchService, IIndexingService, IRawSearc
public string IndexPath
{
- get { return string.Empty ; }
+ get { return string.Empty; }
}
public bool IsLocal
@@ -82,7 +83,7 @@ private async Task SearchCore(SearchFilter filter, bool raw)
if (content == null)
{
results = new SearchResults(0, null, Enumerable.Empty().AsQueryable());
- }
+ }
else if (filter.CountOnly || content.TotalHits == 0)
{
results = new SearchResults(content.TotalHits, content.IndexTimestamp);
@@ -175,11 +176,11 @@ internal static Package ReadPackage(JObject doc, string semVerLevel)
doc.Value("Dependencies")
.Cast()
.Select(obj => new PackageDependency()
- {
- Id = obj.Value("Id"),
- VersionSpec = obj.Value("VersionSpec"),
- TargetFramework = obj.Value("TargetFramework")
- })
+ {
+ Id = obj.Value("Id"),
+ VersionSpec = obj.Value("VersionSpec"),
+ TargetFramework = obj.Value("TargetFramework")
+ })
.ToArray();
var frameworks =
@@ -189,8 +190,10 @@ internal static Package ReadPackage(JObject doc, string semVerLevel)
var reg = doc["PackageRegistration"];
PackageRegistration registration = null;
- if(reg != null) {
- registration = new PackageRegistration() {
+ if (reg != null)
+ {
+ registration = new PackageRegistration()
+ {
Id = reg.Value("Id"),
Owners = reg.Value("Owners")
.Select(v => new User { Username = v.Value() })
@@ -205,6 +208,12 @@ internal static Package ReadPackage(JObject doc, string semVerLevel)
var isLatestStable = doc.Value("IsLatestStable");
var semVer2 = SemVerLevelKey.ForSemVerLevel(semVerLevel) == SemVerLevelKey.SemVer2;
+ var docDeprecation = doc["Deprecation"];
+ var deprecations = SearchResponseHelper.GetDeprecationsOrNull(docDeprecation);
+
+ var docVulnerabilities = doc.Value("Vulnerabilities");
+ var vulnerabilities = SearchResponseHelper.GetVulnerabilities(docVulnerabilities);
+
return new Package
{
Copyright = doc.Value("Copyright"),
@@ -243,7 +252,9 @@ internal static Package ReadPackage(JObject doc, string semVerLevel)
LicenseNames = doc.Value("LicenseNames"),
LicenseReportUrl = doc.Value("LicenseReportUrl"),
HideLicenseReport = doc.Value("HideLicenseReport"),
- Listed = doc.Value("Listed")
+ Listed = doc.Value("Listed"),
+ Deprecations = deprecations,
+ VulnerablePackageRanges = vulnerabilities
};
}
diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj
index 2f11378fe9..13deba5cc5 100644
--- a/src/NuGetGallery/NuGetGallery.csproj
+++ b/src/NuGetGallery/NuGetGallery.csproj
@@ -227,6 +227,7 @@
+
@@ -235,6 +236,7 @@
+
@@ -1444,7 +1446,6 @@
-
diff --git a/src/NuGetGallery/Scripts/gallery/async-file-upload.js b/src/NuGetGallery/Scripts/gallery/async-file-upload.js
index 62687e86c2..3d003461cd 100644
--- a/src/NuGetGallery/Scripts/gallery/async-file-upload.js
+++ b/src/NuGetGallery/Scripts/gallery/async-file-upload.js
@@ -193,8 +193,8 @@
break;
case "error":
// IIS returns 404.13 (NotFound) when maxAllowedContentLength limit is exceeded.
- if (fullResponse === "Not Found") {
- displayErrors(["The package file exceeds the size limit. Please try again."]);
+ if (fullResponse === "Not Found" || fullResponse === "Request Entity Too Large") {
+ displayErrors(["The package file exceeds the size limit of 250 MB. Please reduce the package size and try again."]);
}
else {
displayErrors(model.responseJSON);
diff --git a/src/NuGetGallery/Scripts/gallery/common.js b/src/NuGetGallery/Scripts/gallery/common.js
index cfed8a112e..10e973ffc7 100644
--- a/src/NuGetGallery/Scripts/gallery/common.js
+++ b/src/NuGetGallery/Scripts/gallery/common.js
@@ -461,13 +461,40 @@
};
nuget.setPopovers = function () {
- var popoverElement = $(this);
- var popoverElementDom = this;
+ setPopoversInternal(this, rightWithVerticalFallback);
+ }
+
+ function rightWithVerticalFallback(popoverElement, ownerElement) {
+ // Both numbers below are in CSS pixels.
+ const MinSpaceOnRight = 150;
+ const MinSpaceOnTop = 100;
+
+ const ownerBoundingBox = ownerElement.getBoundingClientRect();
+ const spaceOnRight = window.innerWidth - ownerBoundingBox.right;
+ if (spaceOnRight > MinSpaceOnRight) {
+ return 'right';
+ }
+ const spaceOnTop = ownerBoundingBox.top;
+ if (spaceOnTop > MinSpaceOnTop) {
+ return 'top';
+ }
+
+ return 'bottom';
+ }
+
+ function setPopoversInternal(element, placement) {
+ var popoverElement = $(element);
+ var popoverElementDom = element;
var originalLabel = popoverElementDom.ariaLabel;
var popoverHideTimeMS = 2000;
var popoverFadeTimeMS = 200;
- popoverElement.popover({ trigger: 'hover' });
+ var popoverOptions = { trigger: 'hover', container: 'body' };
+ if (placement) {
+ popoverOptions.placement = placement;
+ }
+
+ popoverElement.popover(popoverOptions);
popoverElement.click(popoverShowAndHide);
popoverElement.focus(popoverShowAndHide);
popoverElement.keyup(function (event) {
diff --git a/src/NuGetGallery/Scripts/gallery/page-list-packages.js b/src/NuGetGallery/Scripts/gallery/page-list-packages.js
index c3e682f196..c0f5f756f0 100644
--- a/src/NuGetGallery/Scripts/gallery/page-list-packages.js
+++ b/src/NuGetGallery/Scripts/gallery/page-list-packages.js
@@ -2,6 +2,16 @@ $(function() {
'use strict';
$(".reserved-indicator").each(window.nuget.setPopovers);
+ $(".package-warning--vulnerable").each(window.nuget.setPopovers);
+ $(".package-warning--deprecated").each(window.nuget.setPopovers);
+
+ const storage = window['localStorage'];
+ const focusResultsColumnKey = 'focus_results_column';
+
+ if (storage && storage.getItem(focusResultsColumnKey)) {
+ storage.removeItem(focusResultsColumnKey);
+ document.getElementById('results-column').focus({ preventScroll: true });
+ }
const searchForm = document.forms.search;
const allFrameworks = document.querySelectorAll('.framework');
@@ -69,6 +79,11 @@ $(function() {
function submitSearchForm() {
constructFilterParameter(searchForm.frameworks, allFrameworks);
constructFilterParameter(searchForm.tfms, allTfms);
+
+ if (storage) {
+ storage.setItem(focusResultsColumnKey, true);
+ }
+
searchForm.submit();
}
diff --git a/src/NuGetGallery/ViewModels/ListPackageItemViewModel.cs b/src/NuGetGallery/ViewModels/ListPackageItemViewModel.cs
index aa32eaef5d..04d8701398 100644
--- a/src/NuGetGallery/ViewModels/ListPackageItemViewModel.cs
+++ b/src/NuGetGallery/ViewModels/ListPackageItemViewModel.cs
@@ -58,6 +58,9 @@ public bool UseVersion
public bool CanDeleteSymbolsPackage { get; set; }
public bool CanDeprecate { get; set; }
+ public string VulnerabilityTitle { get; set; }
+ public string DeprecationTitle { get; set; }
+
public void SetShortDescriptionFrom(string fullDescription)
{
ShortDescription = fullDescription.TruncateAtWordBoundary(_descriptionLengthLimit, _omissionString, out var wasTruncated);
diff --git a/src/NuGetGallery/ViewModels/PackageViewModel.cs b/src/NuGetGallery/ViewModels/PackageViewModel.cs
index 3e6c250a9e..bd801630bb 100644
--- a/src/NuGetGallery/ViewModels/PackageViewModel.cs
+++ b/src/NuGetGallery/ViewModels/PackageViewModel.cs
@@ -30,6 +30,7 @@ public class PackageViewModel : IPackageVersionModel
public string FullVersion { get; set; }
public PackageStatusSummary PackageStatusSummary { get; set; }
public bool IsVulnerable { get; set; }
+ public bool IsDeprecated { get; set; }
public bool IsCurrent(IPackageVersionModel current)
{
diff --git a/src/NuGetGallery/Views/Organizations/_OrganizationAccountManageMembers.cshtml b/src/NuGetGallery/Views/Organizations/_OrganizationAccountManageMembers.cshtml
index f3a4bdcddc..b061f3334e 100644
--- a/src/NuGetGallery/Views/Organizations/_OrganizationAccountManageMembers.cshtml
+++ b/src/NuGetGallery/Views/Organizations/_OrganizationAccountManageMembers.cshtml
@@ -40,7 +40,7 @@
-
+
@@ -72,8 +72,8 @@
@if (Model.CanManageMemberships)
{
-