mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
14 Commits
zeus-oidc
...
v4.3.1.682
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c11f72c098 | ||
|
|
3617bef54b | ||
|
|
a5fb01f1e6 | ||
|
|
fa6acb7497 | ||
|
|
904259df92 | ||
|
|
65c316bd6d | ||
|
|
3b46a08606 | ||
|
|
6ad49373d4 | ||
|
|
2a1f57c085 | ||
|
|
9d9065fbcd | ||
|
|
694940452c | ||
|
|
f5d6a79998 | ||
|
|
4cc98a10a0 | ||
|
|
1751bd1a58 |
@@ -75,13 +75,23 @@ class Queue extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextState = {};
|
||||
|
||||
if (prevProps.items !== items) {
|
||||
nextState.items = items;
|
||||
}
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
const isPendingSelected = _.some(this.props.items, (item) => {
|
||||
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
|
||||
});
|
||||
|
||||
if (isPendingSelected !== this.state.isPendingSelected) {
|
||||
this.setState({ isPendingSelected });
|
||||
nextState.isPendingSelected = isPendingSelected;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(nextState)) {
|
||||
this.setState(nextState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,26 +224,29 @@ class Queue extends Component {
|
||||
|
||||
<PageContentBody>
|
||||
{
|
||||
isRefreshing && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
isRefreshing && !isAllPopulated ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isRefreshing && hasError &&
|
||||
!isRefreshing && hasError ?
|
||||
<div>
|
||||
{translate('FailedToLoadQueue')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !items.length &&
|
||||
isAllPopulated && !hasError && !items.length ?
|
||||
<div>
|
||||
{translate('QueueIsEmpty')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !!items.length &&
|
||||
isAllPopulated && !hasError && !!items.length ?
|
||||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
@@ -268,7 +281,8 @@ class Queue extends Component {
|
||||
isFetching={isRefreshing}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as dark from './dark';
|
||||
import * as light from './light';
|
||||
|
||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const auto = defaultDark ? { ...dark } : { ...light };
|
||||
|
||||
export default {
|
||||
auto,
|
||||
light,
|
||||
dark
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IsValidIPAddressFixture
|
||||
{
|
||||
[TestCase("192.168.0.1")]
|
||||
[TestCase("::1")]
|
||||
[TestCase("2001:db8:4006:812::200e")]
|
||||
public void should_validate_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("sonarr.tv")]
|
||||
public void should_not_parse_non_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("abc://my_host.com:8080/root/api/")]
|
||||
[TestCase("abc://my_host.com:8080//root/api/")]
|
||||
[TestCase("abc://my_host.com:8080/root//api/")]
|
||||
[TestCase("abc://[::1]:8080/root//api/")]
|
||||
public void should_parse(string uri)
|
||||
{
|
||||
var newUri = new HttpUri(uri);
|
||||
|
||||
@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static bool IsLocalAddress(this IPAddress ipAddress)
|
||||
{
|
||||
if (ipAddress.IsIPv6LinkLocal)
|
||||
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
|
||||
if (ipAddress.IsIPv4MappedToIPv6)
|
||||
{
|
||||
return true;
|
||||
ipAddress = ipAddress.MapToIPv4();
|
||||
}
|
||||
|
||||
// Checks loopback ranges for both IPv4 and IPv6.
|
||||
if (IPAddress.IsLoopback(ipAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 10:
|
||||
case 127:
|
||||
return true;
|
||||
case 172:
|
||||
return bytes[1] < 32 && bytes[1] >= 16;
|
||||
case 192:
|
||||
return bytes[1] == 168;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return IsLocalIPv4(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return ipAddress.IsIPv6LinkLocal ||
|
||||
ipAddress.IsIPv6UniqueLocal ||
|
||||
ipAddress.IsIPv6SiteLocal;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsLocalIPv4(byte[] ipv4Bytes)
|
||||
{
|
||||
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
|
||||
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
|
||||
// Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
|
||||
bool IsClassA() => ipv4Bytes[0] == 10;
|
||||
|
||||
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
|
||||
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
|
||||
// Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
|
||||
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
|
||||
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -192,5 +194,30 @@ namespace NzbDrone.Common.Extensions
|
||||
.Replace("'", "%27")
|
||||
.Replace("%7E", "~");
|
||||
}
|
||||
|
||||
public static bool IsValidIpAddress(this string value)
|
||||
{
|
||||
if (!IPAddress.TryParse(value, out var parsedAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.IsIPv6Multicast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
}
|
||||
|
||||
public static string ToUrlHost(this string input)
|
||||
{
|
||||
return input.Contains(":") ? $"[{input}]" : input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class HttpUri : IEquatable<HttpUri>
|
||||
{
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly string _uri;
|
||||
public string FullUri => _uri;
|
||||
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
|
||||
|
||||
var match = RegexUri.Match(_uri);
|
||||
|
||||
var scheme = match.Groups["scheme"];
|
||||
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
|
||||
var query = match.Groups["query"];
|
||||
var fragment = match.Groups["fragment"];
|
||||
|
||||
if (!match.Success || (scheme.Success && !host.Success && path.Success))
|
||||
if (!parseSuccess || (scheme.Success && !host.Success && path.Success))
|
||||
{
|
||||
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.With(h => h.Title = title)
|
||||
.With(h => h.Release = release)
|
||||
.With(h => h.Reason = reason)
|
||||
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
|
||||
.Build();
|
||||
|
||||
_heldReleases.AddRange(heldReleases);
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_pending.Add(new PendingRelease
|
||||
{
|
||||
Id = id,
|
||||
Title = "Movie.Title.2020.720p-Radarr",
|
||||
ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year },
|
||||
MovieId = _movie.Id
|
||||
});
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.With(h => h.MovieId = _movie.Id)
|
||||
.With(h => h.Title = title)
|
||||
.With(h => h.Release = release)
|
||||
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IPendingReleaseRepository>()
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
|
||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||
public string Theme => GetValue("Theme", "light", persist: false);
|
||||
public string Theme => GetValue("Theme", "auto", persist: false);
|
||||
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
||||
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
||||
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
|
||||
|
||||
@@ -283,6 +283,12 @@ namespace NzbDrone.Core.Download.Pending
|
||||
return null;
|
||||
}
|
||||
|
||||
// Languages will be empty if added before upgrading to v4, reparsing the languages if they're empty will set it to Unknown or better.
|
||||
if (release.ParsedMovieInfo.Languages.Empty())
|
||||
{
|
||||
release.ParsedMovieInfo.Languages = LanguageParser.ParseLanguages(release.Title);
|
||||
}
|
||||
|
||||
release.RemoteMovie = new RemoteMovie
|
||||
{
|
||||
Movie = movie,
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"Backups": "Backups",
|
||||
"BeforeUpdate": "Before update",
|
||||
"BindAddress": "Bind Address",
|
||||
"BindAddressHelpText": "Valid IPv4 address or '*' for all interfaces",
|
||||
"BindAddressHelpText": "Valid IP address, localhost or '*' for all interfaces",
|
||||
"Blocklist": "Blocklist",
|
||||
"Blocklisted": "Blocklisted",
|
||||
"BlocklistHelpText": "Prevents Radarr from automatically grabbing this release again",
|
||||
@@ -928,7 +928,7 @@
|
||||
"SettingsShowRelativeDates": "Show Relative Dates",
|
||||
"SettingsShowRelativeDatesHelpText": "Show relative (Today/Yesterday/etc) or absolute dates",
|
||||
"SettingsTheme": "Theme",
|
||||
"SettingsThemeHelpText": "Change Application UI Theme, Inspired by Theme.Park",
|
||||
"SettingsThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by Theme.Park",
|
||||
"SettingsTimeFormat": "Time Format",
|
||||
"SettingsWeekColumnHeader": "Week Column Header",
|
||||
"SettingsWeekColumnHeaderHelpText": "Shown above each column when week is the active view",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -43,7 +44,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
public bool UpdateLibrary { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Address => $"{Host}:{Port}";
|
||||
public string Address => $"{Host.ToUrlHost()}:{Port}";
|
||||
|
||||
public bool IsValid => !string.IsNullOrWhiteSpace(Host) && Port > 0;
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
{
|
||||
var scheme = settings.UseSsl ? "https" : "http";
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host}:{settings.Port}")
|
||||
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host.ToUrlHost()}:{settings.Port}")
|
||||
.Accept(HttpAccept.Json)
|
||||
.AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier)
|
||||
.AddQueryParam("X-Plex-Product", BuildInfo.AppName)
|
||||
|
||||
@@ -21,7 +21,11 @@ namespace NzbDrone.Core.Notifications.Webhook
|
||||
IndexerFlags = movieFile.IndexerFlags.ToString();
|
||||
Size = movieFile.Size;
|
||||
DateAdded = movieFile.DateAdded;
|
||||
MediaInfo = new WebhookMovieFileMediaInfo(movieFile);
|
||||
|
||||
if (movieFile.MediaInfo != null)
|
||||
{
|
||||
MediaInfo = new WebhookMovieFileMediaInfo(movieFile);
|
||||
}
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
@@ -86,7 +86,6 @@ namespace NzbDrone.Core.Notifications.Xbmc
|
||||
|
||||
if (moviePath != null)
|
||||
{
|
||||
moviePath = new OsPath(moviePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
_logger.Debug("Updating movie {0} (Path: {1}) on XBMC host: {2}", movie, moviePath, settings.Address);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
|
||||
public bool AlwaysUpdate { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Address => string.Format("{0}:{1}", Host, Port);
|
||||
public string Address => $"{Host.ToUrlHost()}:{Port}";
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Validators;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Validation
|
||||
{
|
||||
public static class IpValidation
|
||||
{
|
||||
public static IRuleBuilderOptions<T, string> ValidIp4Address<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
public static IRuleBuilderOptions<T, string> ValidIpAddress<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
return ruleBuilder.Must(x =>
|
||||
{
|
||||
IPAddress parsedAddress;
|
||||
|
||||
if (!IPAddress.TryParse(x, out parsedAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedAddress.AddressFamily == AddressFamily.InterNetwork;
|
||||
}).WithMessage("Must contain wildcard (*) or a valid IPv4 Address");
|
||||
return ruleBuilder.Must(x => x.IsValidIpAddress()).WithMessage("Must contain wildcard (*) or a valid IP Address");
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, string> NotListenAllIp4Address<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Validators;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Validation
|
||||
{
|
||||
public static class RuleBuilderExtensions
|
||||
{
|
||||
private static readonly Regex HostRegex = new Regex("^[-_a-z0-9.]+$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public static IRuleBuilderOptions<T, int> ValidId<T>(this IRuleBuilder<T, int> ruleBuilder)
|
||||
{
|
||||
return ruleBuilder.SetValidator(new GreaterThanValidator(0));
|
||||
@@ -24,13 +28,15 @@ namespace NzbDrone.Core.Validation
|
||||
public static IRuleBuilderOptions<T, string> ValidHost<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
return ruleBuilder.SetValidator(new RegularExpressionValidator("^[-_a-z0-9.]+$", RegexOptions.IgnoreCase)).WithMessage("must be valid Host without http://");
|
||||
|
||||
return ruleBuilder.Must(x => HostRegex.IsMatch(x) || x.IsValidIpAddress()).WithMessage("must be valid Host without http://");
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, string> ValidRootUrl<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
return ruleBuilder.SetValidator(new RegularExpressionValidator("^https?://[-_a-z0-9.]+", RegexOptions.IgnoreCase)).WithMessage("must be valid URL that starts with http(s)://");
|
||||
|
||||
return ruleBuilder.Must(x => x.IsValidUrl() && x.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)).WithMessage("must be valid URL that starts with http(s)://");
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, string> ValidUrlBase<T>(this IRuleBuilder<T, string> ruleBuilder, string example = "/radarr")
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace Radarr.Api.V3.Config
|
||||
_userService = userService;
|
||||
|
||||
SharedValidator.RuleFor(c => c.BindAddress)
|
||||
.ValidIp4Address()
|
||||
.ValidIpAddress()
|
||||
.NotListenAllIp4Address()
|
||||
.When(c => c.BindAddress != "*");
|
||||
.When(c => c.BindAddress != "*" && c.BindAddress != "localhost");
|
||||
|
||||
SharedValidator.RuleFor(c => c.Port).ValidPort();
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ namespace Radarr.Api.V3.System
|
||||
}
|
||||
|
||||
[HttpGet("status")]
|
||||
public object GetStatus()
|
||||
public SystemResource GetStatus()
|
||||
{
|
||||
return new
|
||||
return new SystemResource
|
||||
{
|
||||
AppName = BuildInfo.AppName,
|
||||
InstanceName = _configFileProvider.InstanceName,
|
||||
|
||||
43
src/Radarr.Api.V3/System/SystemResource.cs
Normal file
43
src/Radarr.Api.V3/System/SystemResource.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Update;
|
||||
|
||||
namespace Radarr.Api.V3.System
|
||||
{
|
||||
public class SystemResource
|
||||
{
|
||||
public string AppName { get; set; }
|
||||
public string InstanceName { get; set; }
|
||||
public string Version { get; set; }
|
||||
public DateTime BuildTime { get; set; }
|
||||
public bool IsDebug { get; set; }
|
||||
public bool IsProduction { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
public bool IsUserInteractive { get; set; }
|
||||
public string StartupPath { get; set; }
|
||||
public string AppData { get; set; }
|
||||
public string OsName { get; set; }
|
||||
public string OsVersion { get; set; }
|
||||
public bool IsNetCore { get; set; }
|
||||
public bool IsLinux { get; set; }
|
||||
public bool IsOsx { get; set; }
|
||||
public bool IsWindows { get; set; }
|
||||
public bool IsDocker { get; set; }
|
||||
public RuntimeMode Mode { get; set; }
|
||||
public string Branch { get; set; }
|
||||
public DatabaseType DatabaseType { get; set; }
|
||||
public Version DatabaseVersion { get; set; }
|
||||
public AuthenticationType Authentication { get; set; }
|
||||
public int MigrationVersion { get; set; }
|
||||
public string UrlBase { get; set; }
|
||||
public Version RuntimeVersion { get; set; }
|
||||
public string RuntimeName { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public string PackageVersion { get; set; }
|
||||
public string PackageAuthor { get; set; }
|
||||
public UpdateMechanism PackageUpdateMechanism { get; set; }
|
||||
public string PackageUpdateMechanismMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5955,6 +5955,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ping": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Ping"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PingResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/qualitydefinition/{id}": {
|
||||
"put": {
|
||||
"tags": [
|
||||
@@ -7635,7 +7654,24 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemResource"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemResource"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9021,6 +9057,13 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DatabaseType": {
|
||||
"enum": [
|
||||
"sqLite",
|
||||
"postgreSQL"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"DelayProfileResource": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11353,6 +11396,16 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PingResource": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ProfileFormatItemResource": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -12111,6 +12164,14 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RuntimeMode": {
|
||||
"enum": [
|
||||
"console",
|
||||
"service",
|
||||
"tray"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SelectOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -12168,6 +12229,121 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SystemResource": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"appName": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"instanceName": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"buildTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"isDebug": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isProduction": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isUserInteractive": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"startupPath": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"appData": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"osName": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"osVersion": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"isNetCore": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isLinux": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isOsx": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isWindows": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isDocker": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/components/schemas/RuntimeMode"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"databaseType": {
|
||||
"$ref": "#/components/schemas/DatabaseType"
|
||||
},
|
||||
"databaseVersion": {
|
||||
"$ref": "#/components/schemas/Version"
|
||||
},
|
||||
"authentication": {
|
||||
"$ref": "#/components/schemas/AuthenticationType"
|
||||
},
|
||||
"migrationVersion": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"urlBase": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"runtimeVersion": {
|
||||
"$ref": "#/components/schemas/Version"
|
||||
},
|
||||
"runtimeName": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"startTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"packageVersion": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"packageAuthor": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"packageUpdateMechanism": {
|
||||
"$ref": "#/components/schemas/UpdateMechanism"
|
||||
},
|
||||
"packageUpdateMechanismMessage": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TMDbCountryCode": {
|
||||
"enum": [
|
||||
"au",
|
||||
|
||||
@@ -162,39 +162,7 @@ namespace Radarr.Http.Extensions
|
||||
remoteIP = remoteIP.MapToIPv4();
|
||||
}
|
||||
|
||||
var remoteAddress = remoteIP.ToString();
|
||||
|
||||
// Only check if forwarded by a local network reverse proxy
|
||||
if (remoteIP.IsLocalAddress())
|
||||
{
|
||||
var realIPHeader = request.Headers["X-Real-IP"];
|
||||
if (realIPHeader.Any())
|
||||
{
|
||||
return realIPHeader.First().ToString();
|
||||
}
|
||||
|
||||
var forwardedForHeader = request.Headers["X-Forwarded-For"];
|
||||
if (forwardedForHeader.Any())
|
||||
{
|
||||
// Get the first address that was forwarded by a local IP to prevent remote clients faking another proxy
|
||||
foreach (var forwardedForAddress in forwardedForHeader.SelectMany(v => v.Split(',')).Select(v => v.Trim()).Reverse())
|
||||
{
|
||||
if (!IPAddress.TryParse(forwardedForAddress, out remoteIP))
|
||||
{
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
if (!remoteIP.IsLocalAddress())
|
||||
{
|
||||
return forwardedForAddress;
|
||||
}
|
||||
|
||||
remoteAddress = forwardedForAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return remoteAddress;
|
||||
return remoteIP.ToString();
|
||||
}
|
||||
|
||||
public static void DisableCache(this IHeaderDictionary headers)
|
||||
|
||||
39
src/Radarr.Http/Ping/PingController.cs
Normal file
39
src/Radarr.Http/Ping/PingController.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace Radarr.Http.Ping
|
||||
{
|
||||
public class PingController : Controller
|
||||
{
|
||||
private readonly IConfigRepository _configRepository;
|
||||
|
||||
public PingController(IConfigRepository configRepository)
|
||||
{
|
||||
_configRepository = configRepository;
|
||||
}
|
||||
|
||||
[HttpGet("/ping")]
|
||||
[Produces("application/json")]
|
||||
public ActionResult<PingResource> GetStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
_configRepository.All();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new PingResource
|
||||
{
|
||||
Status = "Error"
|
||||
});
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status200OK, new PingResource
|
||||
{
|
||||
Status = "OK"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/Radarr.Http/Ping/PingResource.cs
Normal file
7
src/Radarr.Http/Ping/PingResource.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Radarr.Http.Ping
|
||||
{
|
||||
public class PingResource
|
||||
{
|
||||
public string Status { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -69,12 +69,18 @@ namespace Radarr.Http.REST
|
||||
|
||||
foreach (var resource in resourceArgs)
|
||||
{
|
||||
// Map route Id to body resource if not set in request
|
||||
if (Request.Method == "PUT" && resource.Id == 0 && context.RouteData.Values.TryGetValue("id", out var routeId))
|
||||
{
|
||||
resource.Id = Convert.ToInt32(routeId);
|
||||
}
|
||||
|
||||
ValidateResource(resource, skipValidate, skipShared);
|
||||
}
|
||||
}
|
||||
|
||||
var attributes = descriptor.MethodInfo.CustomAttributes;
|
||||
if (attributes.Any(x => VALIDATE_ID_ATTRIBUTES.Contains(x.GetType())) && !skipValidate)
|
||||
if (attributes.Any(x => VALIDATE_ID_ATTRIBUTES.Contains(x.AttributeType)) && !skipValidate)
|
||||
{
|
||||
if (context.ActionArguments.TryGetValue("id", out var idObj))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user