Added a release notes page, you can access via Admin>Updates>Recent Changes tab. Note to self, need to put better comments in for users to understand!

pull/1245/head
Jamie.Rees 7 years ago
parent 14ac399886
commit 2804191781

@ -0,0 +1,9 @@
using Ombi.Api.Models.Appveyor;
namespace Ombi.Api.Interfaces
{
public interface IAppveyorApi
{
AppveyorProjects GetProjectHistory(string branchName, int records = 10);
}
}

@ -54,6 +54,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="IApiRequest.cs" />
<Compile Include="IAppveyorApi.cs" />
<Compile Include="ICouchPotatoApi.cs" />
<Compile Include="IDiscordApi.cs" />
<Compile Include="IEmbyApi.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; }
}
}

@ -49,6 +49,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Appveyor\AppveyorProject.cs" />
<Compile Include="Emby\EmbyChapter.cs" />
<Compile Include="Emby\EmbyConfiguration.cs" />
<Compile Include="Emby\EmbyEpisodeInformation.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<AppveyorProjects>(request, new Uri(AppveyorApiUrl)));
return obj;
}
private void AddHeaders(IRestRequest request)
{
request.AddHeader("Authorization", $"Bearer {Key}");
request.AddHeader("Content-Type", "application/json");
}
}
}

@ -76,6 +76,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ApiRequest.cs" />
<Compile Include="AppveyorApi.cs" />
<Compile Include="DiscordApi.cs" />
<Compile Include="EmbyApi.cs" />
<Compile Include="NetflixRouletteApi.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; }
}
}

@ -99,6 +99,7 @@
<Compile Include="IPlexReadOnlyDatabase.cs" />
<Compile Include="ISecurityExtensions.cs" />
<Compile Include="IStatusChecker.cs" />
<Compile Include="Models\RecentUpdatesModel.cs" />
<Compile Include="MovieSender.cs" />
<Compile Include="MovieSenderResult.cs" />
<Compile Include="Notification\NotificationMessage.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,
}
}

@ -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<U>(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<int>().Sum();
}
}
public class BranchAttribute : Attribute
{
public string DisplayName { get; set; }
public string BranchName { get; set; }
}
}

@ -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<PlexRequestSettings> settingsService, ICacheProvider cache, ISettingsService<SystemSettings> ss, ISecurityExtensions security, IAnalytics a) : base("admin", settingsService, security)
public SystemStatusModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, ISettingsService<SystemSettings> 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> SystemSettings { get; }
private IAnalytics Analytics { get; }
private IAppveyorApi AppveyorApi { get; }
private async Task<Negotiator> Status()
{
@ -82,19 +88,19 @@ namespace Ombi.UI.Modules.Admin
{
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.Stable),
Name =EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.Stable).DisplayName,
Value = Branches.Stable,
Selected = settings.Branch == Branches.Stable
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.EarlyAccessPreview),
Name = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.EarlyAccessPreview).DisplayName,
Value = Branches.EarlyAccessPreview,
Selected = settings.Branch == Branches.EarlyAccessPreview
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.Dev),
Name = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(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<Response> GetLatestChanges()
{
var settings = await SystemSettings.GetSettingsAsync();
var branchName = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(settings.Branch).BranchName;
var changes = AppveyorApi.GetProjectHistory(branchName);
var currentVersion = AssemblyHelper.GetProductVersion();
var model = new List<RecentUpdatesModel>();
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<Response> Save()
{

@ -51,6 +51,7 @@ namespace Ombi.UI.NinjectModules
Bind<IRadarrApi>().To<RadarrApi>();
Bind<ITraktApi>().To<TraktApi>();
Bind<IEmbyApi>().To<EmbyApi>();
Bind<IAppveyorApi>().To<AppveyorApi>();
}
}
}

@ -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")
</div>

@ -4,78 +4,127 @@
<div id="lightbox" style="display:none"></div>
<div class="col-sm-8 col-sm-push-1">
<fieldset>
<legend>Status</legend>
<legend>Updates</legend>
<div class="form-group">
<label class="control-label">Current Version: </label>
<label class="control-label">@Model.Status.CurrentVersion</label>
</div>
<ul class="nav nav-tabs">
<li class="active"><a href="#status" data-toggle="tab" aria-expanded="true">Status</a></li>
<li id="changesTab" class=""><a href="#changes" data-toggle="tab" aria-expanded="false">Recent Changes</a></li>
</ul>
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="status">
<br />
<div class="form-group">
<label class="control-label">Current Version: </label>
<label class="control-label">@Model.Status.CurrentVersion</label>
</div>
@if (Model.Status.UpdateAvailable)
{
<div class="form-group">
<label class="control-label">New Version: </label>
<label class="control-label">@Model.Status.NewVersion</label>
</div>
}
<hr />
<form id="mainForm" method="post" action="save">
<div class="form-group">
<label for="select" class="control-label">Code Branch</label>
<div id="branches">
<select class="form-control form-control-custom" id="select">
@foreach (var b in Model.BranchDropdown)
{
if (b.Selected)
{
<option selected='selected' value='@b.Value'>@b.Name</option>
}
else
{
<option value='@b.Value'>@b.Name</option>
}
}
</select>
</div>
</div>
<button id="saveSettings" class="btn btn-success-outline">Save</button>
</form>
<hr />
<div class="form-group">
<label class="control-label">Update Available: </label>
@if (Model.Status.UpdateAvailable)
{
<label class="control-label"><a href="@Model.Status.UpdateUri" target="_blank"><i class="fa fa-check"></i></a> @Html.ToolTip("Click the 'tick' to manually go to the page")</label>
<br />
<br />
<label class="control-label">Launch Arguments</label>
@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.")
<input id="args" class="form-control form-control-custom " placeholder="/etc/mono /opt/Ombi.exe">
<br />
<button id="autoUpdate" class="btn btn-success-outline">Automatic Update (beta) <i class="fa fa-download"></i></button>
}
else
{
<label class="control-label"><i class="fa fa-times"></i></label>
}
@if (Model.Status.UpdateAvailable)
{
<div class="form-group">
<label class="control-label">New Version: </label>
<label class="control-label">@Model.Status.NewVersion</label>
</div>
}
<hr/>
<form id="mainForm" method="post" action="save">
<div class="form-group">
<label for="select" class="control-label">Code Branch</label>
<div id="branches">
<select class="form-control form-control-custom" id="select">
@foreach (var b in Model.BranchDropdown)
{
if (b.Selected)
{
<option selected='selected' value='@b.Value'>@b.Name</option>
}
else
{
<option value='@b.Value'>@b.Name</option>
}
}
</select>
</div>
<hr />
@if (Model.Status.UpdateAvailable)
{
<h2>
<a href="@Model.Status.DownloadUri">@Model.Status.ReleaseTitle</a>
</h2>
<hr />
<label>Release Notes:</label>
@Html.Raw(Model.Status.ReleaseNotes)
}
</div>
<button id="saveSettings" class="btn btn-success-outline">Save</button>
</form>
<hr/>
<div class="form-group">
<label class="control-label">Update Available: </label>
@if (Model.Status.UpdateAvailable)
{
<label class="control-label"><a href="@Model.Status.UpdateUri" target="_blank"><i class="fa fa-check"></i></a> @Html.ToolTip("Click the 'tick' to manually go to the page")</label>
<div class="tab-pane fade" id="changes">
<br />
<br />
<label class="control-label">Launch Arguments</label> @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.")
<input id="args" class="form-control form-control-custom " placeholder="/etc/mono /opt/Ombi.exe">
<br/>
<button id="autoUpdate" class="btn btn-success-outline">Automatic Update (beta) <i class="fa fa-download"></i></button>
}
else
{
<label class="control-label"><i class="fa fa-times"></i></label>
}
<div id="changesArea"></div>
</div>
</div>
<hr/>
@if (Model.Status.UpdateAvailable)
{
<h2>
<a href="@Model.Status.DownloadUri">@Model.Status.ReleaseTitle</a>
</h2>
<hr />
<label>Release Notes:</label>
@Html.Raw(Model.Status.ReleaseNotes)
}
</fieldset>
</div>
<script id="changes-template" type="text/x-handlebars-template">
<fieldset>
<div class="col-md-12">
<h4 class="col-md-2">
{{version}}
</h4>
<h5 class="col-md-4">
<span class="date">
- <span title="" data-original-title="Sunday, March 12 2017 10:30am">{{date}}</span>
</span>
</h5>
<h6 class="col-md-6">
<span class="status">
<span class="label label-default">{{branch}}</span>
{{#if installed}}
<span class="label label-success">Installed</span>
{{/if}}
</span>
</h6>
</div><hr />
<div class="col-md-12">
{{message}}
</div>
</fieldset>
<br />
<br />
</script>
<script>
$('.customTooltip').tooltipster({
contentCloning: true
@ -134,4 +183,28 @@
}
});
});
var changesSource = $("#changes-template").html();
var changesTemplate = Handlebars.compile(changesSource);
var changesLoaded = false;
$('#changesTab').click(function (e) {
e.preventDefault();
if (changesLoaded) return;
var url = createBaseUrl(base, "/admin/changes");
$area = $('#changesArea');
$.ajax({
type: 'GET',
url: url,
dataType: "json",
success: function (response) {
$(response).each(function (index, item) {
item.date = moment.utc(item.data).local().format('lll');
var html = changesTemplate(item);
$area.append(html);
});
changesLoaded = true;
return;
}
});
});
</script>
Loading…
Cancel
Save