From 14206ac477d0f07566d5e8125dc52cbd7f474ca2 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 20 Aug 2022 12:54:24 +0100 Subject: [PATCH] catalog impl --- .gitignore | 5 +- contrib/testinstnuget/NuGet.Config | 8 ++ contrib/testinstnuget/call.lines | 2 + contrib/testinstnuget/urls.adoc | 4 + contrib/upgrade-isn-isnd.sh | 8 +- src/isnd/Controllers/Packages/WebViews.cs | 13 +-- src/isnd/Data/Catalog/AlternatePackage.cs | 11 +++ src/isnd/Data/Catalog/CatalogEntry.cs | 82 +++++++++++++++++++ src/isnd/Data/Catalog/DependencyGroup.cs | 6 ++ src/isnd/Data/Catalog/Deprecation.cs | 14 ++++ .../PackageRegistrationIndexViewModel.cs} | 10 +-- src/isnd/Data/Catalog/PageRef.cs | 6 +- src/isnd/Data/Catalog/RegistratioinLeave.cs | 36 ++++++++ src/isnd/Data/Catalog/RegistrationPages.cs | 34 ++++++++ src/isnd/Data/Catalog/Vulnerabilitie.cs | 9 ++ .../Data/Packages/Catalog/CatalogIndex.cs | 1 + src/isnd/Data/Packages/Package.cs | 20 +++++ src/isnd/Data/Packages/PackageVersion.cs | 1 + src/isnd/Interfaces/IPackageManager.cs | 3 +- src/isnd/Services/PackageManager.cs | 28 ++----- .../PackageRegistrationViewModel.cs | 45 ++++++++++ src/isnd/Views/PackageVersion/Index.cshtml | 2 +- src/isnd/Views/Packages/Index.cshtml | 6 +- src/isnd/Views/Shared/_Layout.cshtml | 1 + src/isnd/Views/_ViewImports.cshtml | 1 + src/isnd/wwwroot/css/site.css | 3 + src/isnd/wwwroot/css/site.scss | 5 ++ 27 files changed, 320 insertions(+), 44 deletions(-) create mode 100644 contrib/testinstnuget/NuGet.Config create mode 100644 contrib/testinstnuget/call.lines create mode 100644 contrib/testinstnuget/urls.adoc create mode 100644 src/isnd/Data/Catalog/AlternatePackage.cs create mode 100644 src/isnd/Data/Catalog/CatalogEntry.cs create mode 100644 src/isnd/Data/Catalog/DependencyGroup.cs create mode 100644 src/isnd/Data/Catalog/Deprecation.cs rename src/isnd/{ViewModels/PackageIndexViewModel.cs => Data/Catalog/PackageRegistrationIndexViewModel.cs} (59%) create mode 100644 src/isnd/Data/Catalog/RegistratioinLeave.cs create mode 100644 src/isnd/Data/Catalog/RegistrationPages.cs create mode 100644 src/isnd/Data/Catalog/Vulnerabilitie.cs create mode 100644 src/isnd/ViewModels/PackageRegistrationViewModel.cs diff --git a/.gitignore b/.gitignore index 9ee2cc0..2874857 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,6 @@ appsettings.Development.json /src/isn.abst/bin /src/isn.abst/obj /src/isnd/packages/ -test/data/test-isn/bin/ -test/data/test-isn/obj +/test/data/test-isn/bin/ +/test/data/test-isn/obj +.fake diff --git a/contrib/testinstnuget/NuGet.Config b/contrib/testinstnuget/NuGet.Config new file mode 100644 index 0000000..0af3cfd --- /dev/null +++ b/contrib/testinstnuget/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/contrib/testinstnuget/call.lines b/contrib/testinstnuget/call.lines new file mode 100644 index 0000000..c6abc1a --- /dev/null +++ b/contrib/testinstnuget/call.lines @@ -0,0 +1,2 @@ +nuget install -Verbosity detailed -Source http://localhost:5000/index.json -Prerelease Yavsc.Abstract +nuget locals all -clear diff --git a/contrib/testinstnuget/urls.adoc b/contrib/testinstnuget/urls.adoc new file mode 100644 index 0000000..5c81113 --- /dev/null +++ b/contrib/testinstnuget/urls.adoc @@ -0,0 +1,4 @@ += URL's + + + diff --git a/contrib/upgrade-isn-isnd.sh b/contrib/upgrade-isn-isnd.sh index 0fc9bd7..8a93152 100755 --- a/contrib/upgrade-isn-isnd.sh +++ b/contrib/upgrade-isn-isnd.sh @@ -1,11 +1,13 @@ #!/bin/bash set -e + # compiler tout -dotnet publish -c Release +dotnet build -c Release +dotnet publish -c Release -f netcoreapp2.1 src/isnd # MAJ du serveur sudo systemctl stop isnd sudo cp -a src/isnd/bin/Release/netcoreapp2.1/publish/* /srv/www/isnd sudo systemctl start isnd # MAJ du client -sudo cp -a src/isn/bin/Release/net472/* /usr/local/lib/isn -sudo chmod +x /usr/local/lib/isn/isn.exe +sudo cp -a src/isn/bin/Release/netcoreapp2.1/* /usr/local/lib/isn +sudo chown -R root.root /usr/local/lib/isn diff --git a/src/isnd/Controllers/Packages/WebViews.cs b/src/isnd/Controllers/Packages/WebViews.cs index 1a4ea80..7b767da 100644 --- a/src/isnd/Controllers/Packages/WebViews.cs +++ b/src/isnd/Controllers/Packages/WebViews.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Threading.Tasks; using isnd.Data; +using isnd.Data.Catalog; using isnd.Helpers; using isnd.ViewModels; using Microsoft.AspNetCore.Authorization; @@ -14,15 +15,17 @@ namespace isnd.Controllers public partial class PackagesController { // Web search - public async Task Index(PackageIndexViewModel model) + public async Task Index(PackageRegistrationIndexViewModel model) { - var applicationDbContext = dbContext.Packages.Include(p => p.Versions).Where( - p => ( model.Prerelease || p.Versions.Any(v => !v.IsPrerelease)) + var applicationDbContext = dbContext.Packages.Include(p => p.Versions) + .Include(p => p.Owner) + .Where( + p => (model.Prerelease || p.Versions.Any(v => !v.IsPrerelease)) && ((model.Query == null) || p.Id.StartsWith(model.Query))); - model.Data = await applicationDbContext.ToArrayAsync(); + model.Data = await applicationDbContext.Select(p => p.ToLeave()).ToArrayAsync(); return View(model); } - + public async Task Details(string pkgid) { if (pkgid == null) diff --git a/src/isnd/Data/Catalog/AlternatePackage.cs b/src/isnd/Data/Catalog/AlternatePackage.cs new file mode 100644 index 0000000..8e0dfc5 --- /dev/null +++ b/src/isnd/Data/Catalog/AlternatePackage.cs @@ -0,0 +1,11 @@ +using NuGet.Versioning; + +namespace isnd.Data.Catalog +{ + public class AlternatePackage + { + public string id { get ; set; } + public VersionRange range { get ; set; } + } + +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/CatalogEntry.cs b/src/isnd/Data/Catalog/CatalogEntry.cs new file mode 100644 index 0000000..0fc0c4f --- /dev/null +++ b/src/isnd/Data/Catalog/CatalogEntry.cs @@ -0,0 +1,82 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public class CatalogEntry + { + /// + /// The URL to the document used to produce this object + /// + /// + [Key][Required] + [StringLength(1024)] + [JsonProperty("@id")] + public string Id { get; set; } + + /// + /// Authors + /// + /// string or array of strings + public string authors { get; set; } + + /// + /// The dependencies of the package, grouped by target framework + /// + /// array of objects + public DependencyGroup[] dependencyGroups { get; set; } + + /// + /// The deprecation associated with the package + /// + /// + public Deprecation deprecation { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + public string iconUrl { get; set; } + + /// + /// The ID of the package + /// + /// + public string idp { get; set; } + public string licenseUrl { get; set; } + public string licenseExpression { get; set; } + /// + /// Should be considered as listed if absent + /// + /// + public bool listed { get; set; } + public string minClientVersion { get; set; } + public string projectUrl { get; set; } + + /// + /// A string containing a ISO 8601 timestamp of when the package was published + /// + /// + public string published { get; set; } + public bool requireLicenseAcceptance { get; set; } + public string summary { get; set; } + + /// + /// The tags + /// + /// + public string tags { get; set; } + public string title { get; set; } + + /// + /// The full version string after normalization + /// + /// + [Required] + public string version { get; set; } // string yes + /// + /// The security vulnerabilities of the package + /// + /// + public Vulnerabilitie[] vulnerabilities { get; set; } + + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/DependencyGroup.cs b/src/isnd/Data/Catalog/DependencyGroup.cs new file mode 100644 index 0000000..240d6a3 --- /dev/null +++ b/src/isnd/Data/Catalog/DependencyGroup.cs @@ -0,0 +1,6 @@ +namespace isnd.Data.Catalog +{ + public class DependencyGroup + { + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/Deprecation.cs b/src/isnd/Data/Catalog/Deprecation.cs new file mode 100644 index 0000000..1c14a9f --- /dev/null +++ b/src/isnd/Data/Catalog/Deprecation.cs @@ -0,0 +1,14 @@ +namespace isnd.Data.Catalog +{ + public class Deprecation + { + /* + Legacy The package is no longer maintained +CriticalBugs The package has bugs which make it unsuitable for usage +Other The package is deprecated due to a reason not on this list +*/ + public string[] reasons { get; set; } // array of strings yes The reasons why the package was deprecated + public string message { get; set; } // The additional details about this deprecation + public AlternatePackage alternatePackage { get; set; } // object no The alternate package that should be used instead + } +} \ No newline at end of file diff --git a/src/isnd/ViewModels/PackageIndexViewModel.cs b/src/isnd/Data/Catalog/PackageRegistrationIndexViewModel.cs similarity index 59% rename from src/isnd/ViewModels/PackageIndexViewModel.cs rename to src/isnd/Data/Catalog/PackageRegistrationIndexViewModel.cs index c2b69a9..543d9f0 100644 --- a/src/isnd/ViewModels/PackageIndexViewModel.cs +++ b/src/isnd/Data/Catalog/PackageRegistrationIndexViewModel.cs @@ -1,16 +1,14 @@ -using isnd.Data; -using isnd.Data.Packages; using Newtonsoft.Json; -namespace isnd.ViewModels +namespace isnd.Data.Catalog { - public class PackageIndexViewModel + public class PackageRegistrationIndexViewModel { - [JsonProperty("prerelease")] + [JsonProperty("prerelease")] public bool Prerelease { get; set; } [JsonProperty("data")] - public Package[] Data {get; set;} + public RegistrationLeaf[] Data {get; set;} [JsonProperty("query")] public string Query { get; set; } diff --git a/src/isnd/Data/Catalog/PageRef.cs b/src/isnd/Data/Catalog/PageRef.cs index 3bb77da..55ffccd 100644 --- a/src/isnd/Data/Catalog/PageRef.cs +++ b/src/isnd/Data/Catalog/PageRef.cs @@ -2,7 +2,7 @@ using System; using isnd.Interfaces; using Newtonsoft.Json; -namespace isnd.Data.Packages.Catalog +namespace isnd.Data.Catalog { public class PageRef : IObject { @@ -19,7 +19,11 @@ namespace isnd.Data.Packages.Catalog /// [JsonProperty("count")] public int Count { get; set; } + + [JsonProperty("commitId")] public string CommitId { get; set; } + + [JsonProperty("commitTimeStamp")] public DateTime CommitTimeStamp { get; set; } } diff --git a/src/isnd/Data/Catalog/RegistratioinLeave.cs b/src/isnd/Data/Catalog/RegistratioinLeave.cs new file mode 100644 index 0000000..3cf85f8 --- /dev/null +++ b/src/isnd/Data/Catalog/RegistratioinLeave.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public class RegistrationLeaf + { + /* + @id string yes + catalogEntry object yes + packageContent string yes + */ + [JsonProperty("@id")] + [Key][Required] + [StringLength(1024)] + /// + /// The URL to the registration leaf + /// + /// + public string Id { get; set; } + + /// + /// The catalog entry containing the package metadata + /// + /// + [JsonProperty("catalogEntry")] + public CatalogEntry Entry { get; set; } + + /// + /// The URL to the package content (.nupkg) + /// + /// + [JsonProperty("packageContent")] + public string PackageContent { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/RegistrationPages.cs b/src/isnd/Data/Catalog/RegistrationPages.cs new file mode 100644 index 0000000..b3e945e --- /dev/null +++ b/src/isnd/Data/Catalog/RegistrationPages.cs @@ -0,0 +1,34 @@ +using System; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public class RegistrationPage + { + /* + @id string yes The URL to the registration page + count integer yes The number of registration leaves in the page + items array of objects no The array of registration leaves and their associate metadata + lower string yes The lowest SemVer 2.0.0 version in the page (inclusive) + parent string no The URL to the registration index + upper string yes The highest SemVer 2.0.0 version in the page (inclusive) */ + [JsonProperty("@id")] + public string Id { get; set; } + [JsonProperty("count")] + public int Count { get; set; } + + [JsonProperty("items")] + + public RegistrationLeaf[] Items { get; set; } + + [JsonProperty("upper")] + public Version Upper { get; set; } + + [JsonProperty("lower")] + public Version Lower { get; set; } + + [JsonProperty("parent")] + public string Parent { get; set; } + + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/Vulnerabilitie.cs b/src/isnd/Data/Catalog/Vulnerabilitie.cs new file mode 100644 index 0000000..fc85dcf --- /dev/null +++ b/src/isnd/Data/Catalog/Vulnerabilitie.cs @@ -0,0 +1,9 @@ +namespace isnd.Data.Catalog +{ + public class Vulnerabilitie + { + public string advisoryUrl { get; set; } // string yes Location of security advisory for the package + public string severity { get; set; } // string yes Severity of advisory: "0" = Low, "1" = Moderate, "2" = High, "3" = Critical + + } +} \ No newline at end of file diff --git a/src/isnd/Data/Packages/Catalog/CatalogIndex.cs b/src/isnd/Data/Packages/Catalog/CatalogIndex.cs index 4043ee5..0e7976a 100644 --- a/src/isnd/Data/Packages/Catalog/CatalogIndex.cs +++ b/src/isnd/Data/Packages/Catalog/CatalogIndex.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; using isnd.Interfaces; +using isnd.Data.Catalog; namespace isnd.Data.Packages.Catalog { diff --git a/src/isnd/Data/Packages/Package.cs b/src/isnd/Data/Packages/Package.cs index 36e8bd9..66f7e02 100644 --- a/src/isnd/Data/Packages/Package.cs +++ b/src/isnd/Data/Packages/Package.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using isnd.Data.Catalog; using isnd.Interfaces; using Newtonsoft.Json; @@ -44,5 +46,23 @@ namespace isnd.Data.Packages public virtual Commit LatestVersion{ get; set; } public DateTime CommitTimeStamp { get; set; } + + internal RegistrationLeaf ToLeave() + { + if (!(Versions != null && + Versions.Count > 0)) throw new Exception("NO VERSION"); + var v = Versions.First(); + RegistrationLeaf leave = new RegistrationLeaf + { + PackageContent = v.NugetLink, + Entry = new CatalogEntry + { + idp = Id, + version = v.FullString, + authors = $"{Owner.FullName} <${Owner.Email}>" + } + }; + return leave; + } } } \ No newline at end of file diff --git a/src/isnd/Data/Packages/PackageVersion.cs b/src/isnd/Data/Packages/PackageVersion.cs index a774123..cff7d47 100644 --- a/src/isnd/Data/Packages/PackageVersion.cs +++ b/src/isnd/Data/Packages/PackageVersion.cs @@ -12,6 +12,7 @@ namespace isnd.Data [Required] [ForeignKey("Package")] [StringLength(1024)] + [JsonProperty("id")] public string PackageId { get; set; } [Required] diff --git a/src/isnd/Interfaces/IPackageManager.cs b/src/isnd/Interfaces/IPackageManager.cs index a036d45..8a8249c 100644 --- a/src/isnd/Interfaces/IPackageManager.cs +++ b/src/isnd/Interfaces/IPackageManager.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using isn.Abstract; using isnd.Controllers; using isnd.Data; +using isnd.Data.Catalog; using isnd.Data.Packages; using isnd.Data.Packages.Catalog; using isnd.Services; @@ -18,7 +19,7 @@ namespace isnd.Interfaces CatalogIndex GetCatalogIndex(); string[] GetVersions(string pkgid, NuGetVersion parsedVersion, bool prerelease = false, string packageType = null, int skip = 0, int take = 25); - PackageIndexViewModel SearchByName(string query, int skip, int take, bool prerelease = false, string packageType = null); + PackageRegistrationIndexViewModel SearchByName(string query, int skip, int take, bool prerelease = false, string packageType = null); IEnumerable GetResources(IUnleash unleashĈlient); void ÛpdateCatalogFor(Commit commit); Task DeletePackageAsync(string pkgid, string version, string type); diff --git a/src/isnd/Services/PackageManager.cs b/src/isnd/Services/PackageManager.cs index fdd10be..e276446 100644 --- a/src/isnd/Services/PackageManager.cs +++ b/src/isnd/Services/PackageManager.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using isn.Abstract; using isnd.Controllers; using isnd.Data; +using isnd.Data.Catalog; using isnd.Data.Packages; using isnd.Data.Packages.Catalog; using isnd.Entities; @@ -121,13 +122,14 @@ namespace isnd.Services return res; } - public PackageIndexViewModel SearchByName(string query, + public PackageRegistrationIndexViewModel SearchByName(string query, int skip, int take, bool prerelease = false, string packageType = null) { var scope = dbContext.Packages .Include(p=>p.Versions) + .Include(p => p.Owner) .Where( p => (PackageIdHelpers.CamelCaseMatch(p.Id, query) || PackageIdHelpers.SeparatedByMinusMatch(p.Id, query)) && (prerelease || p.Versions.Any(v => !v.IsPrerelease)) @@ -136,18 +138,11 @@ namespace isnd.Services var total = scope.Count(); var pkgs = scope.Skip(skip).Take(take).ToArray(); - return new PackageIndexViewModel + return new PackageRegistrationIndexViewModel { Query = query, TotalHits = total, - Data = pkgs - }; - } - - private object PackageVersionToRegentry(Package v) - { - return new { - + Data = pkgs.Select(p => p.ToLeave()).ToArray() }; } @@ -325,18 +320,7 @@ namespace isnd.Services v.Type == type ); } - - public async Task GetPackageRegistrationAsync(string pkgid, string version, string type) - { - var pkgVersion = await GetPackageAsync(pkgid, version, type); - return new CatalogRegistration - { - Id = pkgVersion.PackageId, - CommitTimeStamp = pkgVersion.LatestCommit.CommitTimeStamp, - PackageContent = extUrl + pkgVersion.FullString - }; - } - + public IEnumerable GetCatalogLeaf(string id, string version, string lower) { return dbContext.PackageVersions diff --git a/src/isnd/ViewModels/PackageRegistrationViewModel.cs b/src/isnd/ViewModels/PackageRegistrationViewModel.cs new file mode 100644 index 0000000..7f410e8 --- /dev/null +++ b/src/isnd/ViewModels/PackageRegistrationViewModel.cs @@ -0,0 +1,45 @@ +using isnd.Data.Packages; + +namespace isnd.ViewModels +{ + public class RegistrationLeaf + { + /* +@id string yes The URL to the registration leaf +catalogEntry object yes The catalog entry containing the package metadata +packageContent string yes The URL to the package content (.nupkg) + */ + public static RegistrationLeaf FromPackage(Package p) + { + RegistrationLeaf v = new RegistrationLeaf + { + + }; + return v; + } + } + public class CatalogEntry + { + /* + @id string yes The URL to the document used to produce this object +authors string or array of strings no +dependencyGroups array of objects no The dependencies of the package, grouped by target framework +deprecation object no The deprecation associated with the package +description string no +iconUrl string no +id string yes The ID of the package +licenseUrl string no +licenseExpression string no +listed boolean no Should be considered as listed if absent +minClientVersion string no +projectUrl string no +published string no A string containing a ISO 8601 timestamp of when the package was published +requireLicenseAcceptance boolean no +summary string no +tags string or array of string no +title string no +version string yes The full version string after normalization +vulnerabilities array of objects no The security vulnerabilities of the package + */ + } +} \ No newline at end of file diff --git a/src/isnd/Views/PackageVersion/Index.cshtml b/src/isnd/Views/PackageVersion/Index.cshtml index 4f13d48..7e90861 100644 --- a/src/isnd/Views/PackageVersion/Index.cshtml +++ b/src/isnd/Views/PackageVersion/Index.cshtml @@ -43,7 +43,7 @@ @foreach (var item in Model.Versions) { - @Html.DisplayFor(modelItem => item.Package.Id) + @Html.DisplayFor(modelItem => item.PackageId) nuget diff --git a/src/isnd/Views/Packages/Index.cshtml b/src/isnd/Views/Packages/Index.cshtml index 1b63981..ee1536d 100644 --- a/src/isnd/Views/Packages/Index.cshtml +++ b/src/isnd/Views/Packages/Index.cshtml @@ -1,4 +1,4 @@ -@model PackageIndexViewModel +@model PackageRegistrationIndexViewModel @{ ViewData["Title"] = "Index"; @@ -28,7 +28,7 @@ @Html.DisplayNameFor(model => model.Data[0].Id) - @Html.DisplayNameFor(model => model.Data[0].Description) + @Html.DisplayNameFor(model => model.Data[0].Entry.Description) @@ -41,7 +41,7 @@ - @Html.DisplayFor(modelItem => item.Description) + @Html.DisplayFor(modelItem => item.Entry.Description) @Html.ActionLink("Details", "Details", new { pkgid = item.Id }) diff --git a/src/isnd/Views/Shared/_Layout.cshtml b/src/isnd/Views/Shared/_Layout.cshtml index ecb7e94..7f58efa 100644 --- a/src/isnd/Views/Shared/_Layout.cshtml +++ b/src/isnd/Views/Shared/_Layout.cshtml @@ -6,6 +6,7 @@ @ViewData["Title"] - isnd +
diff --git a/src/isnd/Views/_ViewImports.cshtml b/src/isnd/Views/_ViewImports.cshtml index d966b77..9f3044d 100644 --- a/src/isnd/Views/_ViewImports.cshtml +++ b/src/isnd/Views/_ViewImports.cshtml @@ -1,4 +1,5 @@ @using isnd.Data @using isnd.ViewModels @using isnd.Helpers +@using isnd.Data.Catalog; @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/isnd/wwwroot/css/site.css b/src/isnd/wwwroot/css/site.css index 85e5ce6..62817e8 100644 --- a/src/isnd/wwwroot/css/site.css +++ b/src/isnd/wwwroot/css/site.css @@ -7848,3 +7848,6 @@ a.text-dark:hover, a.text-dark:focus { .fa-copy { cursor: copy; } + +.border-top.footer.text-muted { + padding: 1em; } diff --git a/src/isnd/wwwroot/css/site.scss b/src/isnd/wwwroot/css/site.scss index 7da0fd0..7c7c09e 100644 --- a/src/isnd/wwwroot/css/site.scss +++ b/src/isnd/wwwroot/css/site.scss @@ -111,3 +111,8 @@ background-color: black; .fa-copy { cursor: copy; } + +.border-top.footer.text-muted { + padding: 1em; +} +