diff --git a/Ombi.Api.Interfaces/IAppveyorApi.cs b/Ombi.Api.Interfaces/IAppveyorApi.cs new file mode 100644 index 000000000..d067773fd --- /dev/null +++ b/Ombi.Api.Interfaces/IAppveyorApi.cs @@ -0,0 +1,9 @@ +using Ombi.Api.Models.Appveyor; + +namespace Ombi.Api.Interfaces +{ + public interface IAppveyorApi + { + AppveyorProjects GetProjectHistory(string branchName, int records = 10); + } +} \ No newline at end of file diff --git a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj index 172c009e4..cf50e513d 100644 --- a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj +++ b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj @@ -54,6 +54,7 @@ + diff --git a/Ombi.Api.Models/Appveyor/AppveyorProject.cs b/Ombi.Api.Models/Appveyor/AppveyorProject.cs new file mode 100644 index 000000000..bf94a451a --- /dev/null +++ b/Ombi.Api.Models/Appveyor/AppveyorProject.cs @@ -0,0 +1,114 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: AppveyorProject.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; + +namespace Ombi.Api.Models.Appveyor +{ + public class AppveyorProjects + { + public Project project { get; set; } + public Build[] builds { get; set; } + } + + public class Project + { + public int projectId { get; set; } + public int accountId { get; set; } + public string accountName { get; set; } + public object[] builds { get; set; } + public string name { get; set; } + public string slug { get; set; } + public string repositoryType { get; set; } + public string repositoryScm { get; set; } + public string repositoryName { get; set; } + public bool isPrivate { get; set; } + public bool skipBranchesWithoutAppveyorYml { get; set; } + public bool enableSecureVariablesInPullRequests { get; set; } + public bool enableSecureVariablesInPullRequestsFromSameRepo { get; set; } + public bool enableDeploymentInPullRequests { get; set; } + public bool rollingBuilds { get; set; } + public bool alwaysBuildClosedPullRequests { get; set; } + public string tags { get; set; } + public Securitydescriptor securityDescriptor { get; set; } + public DateTime created { get; set; } + public DateTime updated { get; set; } + } + + public class Securitydescriptor + { + public Accessrightdefinition[] accessRightDefinitions { get; set; } + public Roleace[] roleAces { get; set; } + } + + public class Accessrightdefinition + { + public string name { get; set; } + public string description { get; set; } + } + + public class Roleace + { + public int roleId { get; set; } + public string name { get; set; } + public bool isAdmin { get; set; } + public Accessright[] accessRights { get; set; } + } + + public class Accessright + { + public string name { get; set; } + public bool allowed { get; set; } + } + + public class Build + { + public int buildId { get; set; } + public object[] jobs { get; set; } + public int buildNumber { get; set; } + public string version { get; set; } + public string message { get; set; } + public string messageExtended { get; set; } + public string branch { get; set; } + public bool isTag { get; set; } + public string commitId { get; set; } + public string authorName { get; set; } + public string authorUsername { get; set; } + public string committerName { get; set; } + public string committerUsername { get; set; } + public DateTime committed { get; set; } + public object[] messages { get; set; } + public string status { get; set; } + public DateTime started { get; set; } + public DateTime finished { get; set; } + public DateTime created { get; set; } + public DateTime updated { get; set; } + public string pullRequestId { get; set; } + public string pullRequestName { get; set; } + } + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Ombi.Api.Models.csproj b/Ombi.Api.Models/Ombi.Api.Models.csproj index addec5fa1..e13506d5b 100644 --- a/Ombi.Api.Models/Ombi.Api.Models.csproj +++ b/Ombi.Api.Models/Ombi.Api.Models.csproj @@ -49,6 +49,7 @@ + diff --git a/Ombi.Api/AppveyorApi.cs b/Ombi.Api/AppveyorApi.cs new file mode 100644 index 000000000..8471edbde --- /dev/null +++ b/Ombi.Api/AppveyorApi.cs @@ -0,0 +1,81 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: AppveyorApi.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Appveyor; +using Ombi.Helpers; +using RestSharp; + +namespace Ombi.Api +{ + public class AppveyorApi : IAppveyorApi + { + private const string AppveyorApiUrl = "https://ci.appveyor.com/api"; + + private const string Key = + "48Ku58C0794nBrXra8IxWav+dc6NqgkRw+PZB3/bQwbt/D0IrnJQkgtjzo0bd6nkooLMKsC8M+Ab7jyBO+ROjY14VRuxffpDopX9r0iG/fjBl6mZVvqkm+VTDNstDtzp"; + + + public AppveyorApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; set; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + //https://ci.appveyor.com/api/projects/tidusjar/requestplex/history?recordsNumber=10&branch=eap + public AppveyorProjects GetProjectHistory(string branchName, int records = 10) + { + var request = new RestRequest + { + Resource = "projects/tidusjar/requestplex/history?recordsNumber={records}&branch={branch}", + Method = Method.GET + }; + + request.AddUrlSegment("records", records.ToString()); + request.AddUrlSegment("branch", branchName); + AddHeaders(request); + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetProjectHistory for Appveyor, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (1), + }); + + var obj = policy.Execute(() => Api.ExecuteJson(request, new Uri(AppveyorApiUrl))); + + return obj; + } + + private void AddHeaders(IRestRequest request) + { + request.AddHeader("Authorization", $"Bearer {Key}"); + request.AddHeader("Content-Type", "application/json"); + } + } +} \ No newline at end of file diff --git a/Ombi.Api/Ombi.Api.csproj b/Ombi.Api/Ombi.Api.csproj index c9d00b030..d004e1c3f 100644 --- a/Ombi.Api/Ombi.Api.csproj +++ b/Ombi.Api/Ombi.Api.csproj @@ -76,6 +76,7 @@ + diff --git a/Ombi.Core/Models/RecentUpdatesModel.cs b/Ombi.Core/Models/RecentUpdatesModel.cs new file mode 100644 index 000000000..51a76700b --- /dev/null +++ b/Ombi.Core/Models/RecentUpdatesModel.cs @@ -0,0 +1,43 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: RecentUpdatesModel.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; + +namespace Ombi.Core.Models +{ + public class RecentUpdatesModel + { + public string Version { get; set; } + public string Message { get; set; } + public bool Installed { get; set; } + public DateTime Date { get; set; } + public string Branch { get; set; } + } + + + +} \ No newline at end of file diff --git a/Ombi.Core/Ombi.Core.csproj b/Ombi.Core/Ombi.Core.csproj index f6562c2fc..49c37b597 100644 --- a/Ombi.Core/Ombi.Core.csproj +++ b/Ombi.Core/Ombi.Core.csproj @@ -99,6 +99,7 @@ + diff --git a/Ombi.Core/SettingModels/SystemSettings.cs b/Ombi.Core/SettingModels/SystemSettings.cs index d905af2e4..ee2ef4a60 100644 --- a/Ombi.Core/SettingModels/SystemSettings.cs +++ b/Ombi.Core/SettingModels/SystemSettings.cs @@ -25,10 +25,12 @@ // ************************************************************************/ #endregion +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; using Ombi.Core.Models; +using Ombi.Helpers; namespace Ombi.Core.SettingModels { @@ -50,13 +52,15 @@ namespace Ombi.Core.SettingModels public enum Branches { - [Display(Name = "Stable")] + [Branch(DisplayName= "Stable", BranchName = "master")] Stable, - [Display(Name = "Early Access Preview")] + [Branch(DisplayName = "Early Access Preview", BranchName = "eap")] EarlyAccessPreview, - [Display(Name = "Development")] + [Branch(DisplayName = "Development", BranchName = "dev")] Dev, } + + } \ No newline at end of file diff --git a/Ombi.Helpers/EnumHelper.cs b/Ombi.Helpers/EnumHelper.cs index db1ced9da..65b0de15e 100644 --- a/Ombi.Helpers/EnumHelper.cs +++ b/Ombi.Helpers/EnumHelper.cs @@ -82,6 +82,19 @@ namespace Ombi.Helpers if (descriptionAttributes == null) return string.Empty; return (descriptionAttributes.Length > 0) ? descriptionAttributes[0].Name : value.ToString(); } + + public static BranchAttribute GetBranchValue(T value) where U : BranchAttribute + { + var fieldInfo = value.GetType().GetField(value.ToString()); + + var descriptionAttributes = fieldInfo.GetCustomAttributes( + typeof(BranchAttribute), false) as BranchAttribute[]; + + return descriptionAttributes?.FirstOrDefault(); + } + + + public static string GetDisplayDescription(T value) { var fieldInfo = value.GetType().GetField(value.ToString()); @@ -127,4 +140,9 @@ namespace Ombi.Helpers return Enum.GetValues(typeof(T)).Cast().Sum(); } } + public class BranchAttribute : Attribute + { + public string DisplayName { get; set; } + public string BranchName { get; set; } + } } \ No newline at end of file diff --git a/Ombi.UI/Modules/Admin/SystemStatusModule.cs b/Ombi.UI/Modules/Admin/SystemStatusModule.cs index cce157985..56c6aa5d3 100644 --- a/Ombi.UI/Modules/Admin/SystemStatusModule.cs +++ b/Ombi.UI/Modules/Admin/SystemStatusModule.cs @@ -35,8 +35,10 @@ using MarkdownSharp; using Nancy; using Nancy.ModelBinding; using Nancy.Responses.Negotiation; +using Ombi.Api.Interfaces; using Ombi.Common.Processes; using Ombi.Core; +using Ombi.Core.Models; using Ombi.Core.SettingModels; using Ombi.Core.StatusChecker; using Ombi.Helpers; @@ -50,11 +52,13 @@ namespace Ombi.UI.Modules.Admin { public class SystemStatusModule : BaseModule { - public SystemStatusModule(ISettingsService settingsService, ICacheProvider cache, ISettingsService ss, ISecurityExtensions security, IAnalytics a) : base("admin", settingsService, security) + public SystemStatusModule(ISettingsService settingsService, ICacheProvider cache, ISettingsService ss, + ISecurityExtensions security, IAnalytics a, IAppveyorApi appveyor) : base("admin", settingsService, security) { Cache = cache; SystemSettings = ss; Analytics = a; + AppveyorApi = appveyor; Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); @@ -62,11 +66,13 @@ namespace Ombi.UI.Modules.Admin Post["/save", true] = async (x, ct) => await Save(); Post["/autoupdate"] = x => AutoUpdate(); + Get["/changes", true] = async (x, ct) => await GetLatestChanges(); } private ICacheProvider Cache { get; } private ISettingsService SystemSettings { get; } private IAnalytics Analytics { get; } + private IAppveyorApi AppveyorApi { get; } private async Task Status() { @@ -82,19 +88,19 @@ namespace Ombi.UI.Modules.Admin { new BranchDropdown { - Name = EnumHelper.GetDisplayValue(Branches.Stable), + Name =EnumHelper.GetBranchValue(Branches.Stable).DisplayName, Value = Branches.Stable, Selected = settings.Branch == Branches.Stable }, new BranchDropdown { - Name = EnumHelper.GetDisplayValue(Branches.EarlyAccessPreview), + Name = EnumHelper.GetBranchValue(Branches.EarlyAccessPreview).DisplayName, Value = Branches.EarlyAccessPreview, Selected = settings.Branch == Branches.EarlyAccessPreview }, new BranchDropdown { - Name = EnumHelper.GetDisplayValue(Branches.Dev), + Name = EnumHelper.GetBranchValue(Branches.Dev).DisplayName, Value = Branches.Dev, Selected = settings.Branch == Branches.Dev }, @@ -103,6 +109,34 @@ namespace Ombi.UI.Modules.Admin return View["Status", settings]; } + public async Task GetLatestChanges() + { + var settings = await SystemSettings.GetSettingsAsync(); + var branchName = EnumHelper.GetBranchValue(settings.Branch).BranchName; + var changes = AppveyorApi.GetProjectHistory(branchName); + var currentVersion = AssemblyHelper.GetProductVersion(); + var model = new List(); + + foreach (var build in changes.builds) + { + model.Add(new RecentUpdatesModel + { + Date = build.finished, + Message = BuildAppveyorMessage(build.message, build.messageExtended), + Version = build.version, + Installed = currentVersion.Equals(build.version, StringComparison.CurrentCultureIgnoreCase) , + Branch = branchName + }); + } + + return Response.AsJson(model); + } + + private string BuildAppveyorMessage(string message, string extended) + { + return extended == null ? message : $"{message} {extended}"; + } + private async Task Save() { diff --git a/Ombi.UI/NinjectModules/ApiModule.cs b/Ombi.UI/NinjectModules/ApiModule.cs index 8ca7c5dbd..d8b40cb33 100644 --- a/Ombi.UI/NinjectModules/ApiModule.cs +++ b/Ombi.UI/NinjectModules/ApiModule.cs @@ -51,6 +51,7 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + Bind().To(); } } } \ No newline at end of file diff --git a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml index a93a452d3..03d533097 100644 --- a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml +++ b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml @@ -40,7 +40,7 @@ @Html.GetSidebarUrl(Context, "/admin/logs", "Logs", "fa fa-edit") - @Html.GetSidebarUrl(Context, "/admin/status", "Status", "fa fa-dashboard") + @Html.GetSidebarUrl(Context, "/admin/status", "Updates", "fa fa-dashboard") @Html.GetSidebarUrl(Context, "/admin/scheduledjobs", "Scheduled Jobs", "fa fa-hand-spock-o") @Html.GetSidebarUrl(Context, "/admin/faultqueue", "Request Fault Queue", "fa fa-history") diff --git a/Ombi.UI/Views/SystemStatus/Status.cshtml b/Ombi.UI/Views/SystemStatus/Status.cshtml index 4ef61a2e6..232a29a78 100644 --- a/Ombi.UI/Views/SystemStatus/Status.cshtml +++ b/Ombi.UI/Views/SystemStatus/Status.cshtml @@ -4,78 +4,127 @@
- Status + Updates -
- - -
- - @if (Model.Status.UpdateAvailable) - { -
- - -
- } -
-
- -
- -
- + +
+
+
+
+ +
-
- - -
-
- - @if (Model.Status.UpdateAvailable) - { - -
-
- @Html.ToolTip("This is if you run Ombi outside of a regular install e.g. you are launching with a custom port. This field will be used after we have updated to launch the application.") - -
- - } - else - { - - } + @if (Model.Status.UpdateAvailable) + { +
+ + +
+ } +
+
+ +
+ +
+ +
+
+ +
+
+
+ + @if (Model.Status.UpdateAvailable) + { + +
+
+ + @Html.ToolTip("This is if you run Ombi outside of a regular install e.g. you are launching with a custom port. This field will be used after we have updated to launch the application.") + +
+ + } + else + { + + } + +
+
+ @if (Model.Status.UpdateAvailable) + { +

+ @Model.Status.ReleaseTitle +

+
+ + @Html.Raw(Model.Status.ReleaseNotes) + } +
+
+
+
+
-
- @if (Model.Status.UpdateAvailable) - { -

- @Model.Status.ReleaseTitle -

-
- - @Html.Raw(Model.Status.ReleaseNotes) - } + + + +
+ + \ No newline at end of file