mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-20 05:23:31 -07:00
Minor improvements in health checks
(cherry picked from commit a22f598b0c129110f2a3b663e9b40c84f3a1f02b) Closes #3752
This commit is contained in:
parent
048cbe9fd1
commit
ebf579ea08
9 changed files with 83 additions and 90 deletions
|
@ -48,13 +48,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var status = client.GetStatus();
|
var status = client.GetStatus();
|
||||||
var folders = status.OutputRootFolders;
|
var folders = status.OutputRootFolders.Where(folder => rootFolders.Any(r => r.Path.PathEquals(folder.FullPath)));
|
||||||
|
|
||||||
foreach (var folder in folders)
|
foreach (var folder in folders)
|
||||||
{
|
{
|
||||||
if (rootFolders.Any(r => r.Path.PathEquals(folder.FullPath)))
|
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientCheckDownloadingToRoot"), client.Definition.Name, folder.FullPath), "#downloads-in-root-folder");
|
||||||
{
|
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientCheckDownloadingToRoot"), client.Definition.Name, folder.FullPath), "#downloads-in-root-folder");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (DownloadClientException ex)
|
catch (DownloadClientException ex)
|
||||||
|
|
|
@ -23,12 +23,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
|
||||||
public override HealthCheck Check()
|
public override HealthCheck Check()
|
||||||
{
|
{
|
||||||
var jackettAllProviders = _providerFactory.All().Where(
|
var jackettAllProviders = _providerFactory.All()
|
||||||
i => i.ConfigContract.Equals("TorznabSettings") &&
|
.Where(
|
||||||
((i.Settings as TorznabSettings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
|
i => i.ConfigContract.Equals("TorznabSettings") &&
|
||||||
(i.Settings as TorznabSettings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
|
(((TorznabSettings)i.Settings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
(i.Settings as TorznabSettings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
|
((TorznabSettings)i.Settings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
(i.Settings as TorznabSettings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)));
|
((TorznabSettings)i.Settings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
|
((TorznabSettings)i.Settings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
if (jackettAllProviders.Empty())
|
if (jackettAllProviders.Empty())
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,13 +28,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
var enabledProviders = _providerFactory.GetAvailableProviders();
|
var enabledProviders = _providerFactory.GetAvailableProviders();
|
||||||
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
||||||
i => i.Definition.Id,
|
i => i.Definition.Id,
|
||||||
s => s.ProviderId,
|
s => s.ProviderId,
|
||||||
(i, s) => new { Provider = i, Status = s })
|
(i, s) => new { Provider = i, Status = s })
|
||||||
.Where(p => p.Status.InitialFailure.HasValue &&
|
.Where(p => p.Status.InitialFailure.HasValue &&
|
||||||
p.Status.InitialFailure.Value.Before(
|
p.Status.InitialFailure.Value.Before(DateTime.UtcNow.AddHours(-6)))
|
||||||
DateTime.UtcNow.AddHours(-6)))
|
.ToList();
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (backOffProviders.Empty())
|
if (backOffProviders.Empty())
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,13 +26,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
var enabledProviders = _providerFactory.GetAvailableProviders();
|
var enabledProviders = _providerFactory.GetAvailableProviders();
|
||||||
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
||||||
i => i.Definition.Id,
|
i => i.Definition.Id,
|
||||||
s => s.ProviderId,
|
s => s.ProviderId,
|
||||||
(i, s) => new { Provider = i, Status = s })
|
(i, s) => new { Provider = i, Status = s })
|
||||||
.Where(p => p.Status.InitialFailure.HasValue &&
|
.Where(p => p.Status.InitialFailure.HasValue &&
|
||||||
p.Status.InitialFailure.Value.After(
|
p.Status.InitialFailure.Value.After(DateTime.UtcNow.AddHours(-6)))
|
||||||
DateTime.UtcNow.AddHours(-6)))
|
.ToList();
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (backOffProviders.Empty())
|
if (backOffProviders.Empty())
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,10 +21,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
// Not best for optimization but due to possible symlinks and junctions, we get mounts based on series path so internals can handle mount resolution.
|
// Not best for optimization but due to possible symlinks and junctions, we get mounts based on series path so internals can handle mount resolution.
|
||||||
var mounts = _artistService.AllArtistPaths()
|
var mounts = _artistService.AllArtistPaths()
|
||||||
.Select(path => _diskProvider.GetMount(path.Value))
|
.Select(path => _diskProvider.GetMount(path.Value))
|
||||||
.Where(m => m != null && m.MountOptions != null && m.MountOptions.IsReadOnly)
|
.Where(m => m is { MountOptions.IsReadOnly: true })
|
||||||
.DistinctBy(m => m.RootDirectory)
|
.DistinctBy(m => m.RootDirectory)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (mounts.Any())
|
if (mounts.Any())
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,35 +31,38 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
|
||||||
public override HealthCheck Check()
|
public override HealthCheck Check()
|
||||||
{
|
{
|
||||||
if (_configService.ProxyEnabled)
|
if (!_configService.ProxyEnabled)
|
||||||
{
|
{
|
||||||
var addresses = Dns.GetHostAddresses(_configService.ProxyHostname);
|
return new HealthCheck(GetType());
|
||||||
if (!addresses.Any())
|
}
|
||||||
{
|
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckResolveIpMessage"), _configService.ProxyHostname), "#proxy-failed-resolve-ip");
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = _cloudRequestBuilder.Create()
|
var addresses = Dns.GetHostAddresses(_configService.ProxyHostname);
|
||||||
.Resource("/ping")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
try
|
if (!addresses.Any())
|
||||||
{
|
{
|
||||||
var response = _client.Execute(request);
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckResolveIpMessage"), _configService.ProxyHostname), "#proxy-failed-resolve-ip");
|
||||||
|
}
|
||||||
|
|
||||||
// We only care about 400 responses, other error codes can be ignored
|
var request = _cloudRequestBuilder.Create()
|
||||||
if (response.StatusCode == HttpStatusCode.BadRequest)
|
.Resource("/ping")
|
||||||
{
|
.Build();
|
||||||
_logger.Error("Proxy Health Check failed: {0}", response.StatusCode);
|
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckBadRequestMessage"), response.StatusCode), "#proxy-failed-test");
|
try
|
||||||
}
|
{
|
||||||
}
|
var response = _client.Execute(request);
|
||||||
catch (Exception ex)
|
|
||||||
|
// We only care about 400 responses, other error codes can be ignored
|
||||||
|
if (response.StatusCode == HttpStatusCode.BadRequest)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Proxy Health Check failed");
|
_logger.Error("Proxy Health Check failed: {0}", response.StatusCode);
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckFailedToTestMessage"), request.Url), "#proxy-failed-test");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckBadRequestMessage"), response.StatusCode), "#proxy-failed-test");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Proxy Health Check failed");
|
||||||
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckFailedToTestMessage"), request.Url), "#proxy-failed-test");
|
||||||
|
}
|
||||||
|
|
||||||
return new HealthCheck(GetType());
|
return new HealthCheck(GetType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
var status = client.GetStatus();
|
var status = client.GetStatus();
|
||||||
var folders = status.OutputRootFolders;
|
var folders = status.OutputRootFolders;
|
||||||
|
|
||||||
foreach (var folder in folders)
|
foreach (var folder in folders)
|
||||||
{
|
{
|
||||||
if (!folder.IsValid)
|
if (!folder.IsValid)
|
||||||
|
@ -70,14 +71,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckWrongOSPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckWrongOSPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else if (_osInfo.IsDocker)
|
|
||||||
|
if (_osInfo.IsDocker)
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckBadDockerPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#docker-bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckBadDockerPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#docker-bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckLocalWrongOSPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-download-client-settings");
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckLocalWrongOSPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-download-client-settings");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_diskProvider.FolderExists(folder.FullPath))
|
if (!_diskProvider.FolderExists(folder.FullPath))
|
||||||
|
@ -86,14 +86,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckDockerFolderMissing"), client.Definition.Name, folder.FullPath), "#docker-bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckDockerFolderMissing"), client.Definition.Name, folder.FullPath), "#docker-bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else if (!status.IsLocalhost)
|
|
||||||
|
if (!status.IsLocalhost)
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckLocalFolderMissing"), client.Definition.Name, folder.FullPath), "#bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckLocalFolderMissing"), client.Definition.Name, folder.FullPath), "#bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckGenericPermissions"), client.Definition.Name, folder.FullPath), "#permissions-error");
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckGenericPermissions"), client.Definition.Name, folder.FullPath), "#permissions-error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,24 +121,21 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
return new HealthCheck(GetType());
|
return new HealthCheck(GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof(TrackImportFailedEvent).IsAssignableFrom(message.GetType()))
|
if (message is TrackImportFailedEvent failureMessage)
|
||||||
{
|
{
|
||||||
var failureMessage = (TrackImportFailedEvent)message;
|
|
||||||
|
|
||||||
// if we can see the file exists but the import failed then likely a permissions issue
|
// if we can see the file exists but the import failed then likely a permissions issue
|
||||||
if (failureMessage.TrackInfo != null)
|
if (failureMessage.TrackInfo != null)
|
||||||
{
|
{
|
||||||
var trackPath = failureMessage.TrackInfo.Path;
|
var trackPath = failureMessage.TrackInfo.Path;
|
||||||
|
|
||||||
if (_diskProvider.FileExists(trackPath))
|
if (_diskProvider.FileExists(trackPath))
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckDownloadPermissions"), trackPath), "#permissions-error");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckDownloadPermissions"), trackPath), "#permissions-error");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
// If the file doesn't exist but TrackInfo is not null then the message is coming from
|
||||||
// If the file doesn't exist but TrackInfo is not null then the message is coming from
|
// ImportApprovedTracks and the file must have been removed part way through processing
|
||||||
// ImportApprovedTracks and the file must have been removed part way through processing
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFileRemoved"), trackPath), "#remote-path-file-removed");
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFileRemoved"), trackPath), "#remote-path-file-removed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the previous case did not match then the failure occured in DownloadedTracksImportService,
|
// If the previous case did not match then the failure occured in DownloadedTracksImportService,
|
||||||
|
@ -170,14 +166,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesWrongOSPath"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesWrongOSPath"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else if (_osInfo.IsDocker)
|
|
||||||
|
if (_osInfo.IsDocker)
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesBadDockerPath"), client.Definition.Name, dlpath, _osInfo.Name), "#docker-bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesBadDockerPath"), client.Definition.Name, dlpath, _osInfo.Name), "#docker-bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesLocalWrongOSPath"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-download-client-settings");
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesLocalWrongOSPath"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-download-client-settings");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_diskProvider.FolderExists(dlpath))
|
if (_diskProvider.FolderExists(dlpath))
|
||||||
|
@ -190,15 +185,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFolderPermissions"), client.Definition.Name, dlpath), "#docker-bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFolderPermissions"), client.Definition.Name, dlpath), "#docker-bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else if (!status.IsLocalhost)
|
|
||||||
|
if (!status.IsLocalhost)
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckRemoteDownloadClient"), client.Definition.Name, dlpath), "#bad-remote-path-mapping");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckRemoteDownloadClient"), client.Definition.Name, dlpath), "#bad-remote-path-mapping");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
// path mappings shouldn't be needed locally so probably a permissions issue
|
||||||
// path mappings shouldn't be needed locally so probably a permissions issue
|
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesGenericPermissions"), client.Definition.Name, dlpath), "#permissions-error");
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesGenericPermissions"), client.Definition.Name, dlpath), "#permissions-error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (DownloadClientException ex)
|
catch (DownloadClientException ex)
|
||||||
{
|
{
|
||||||
|
@ -215,10 +209,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
|
||||||
return new HealthCheck(GetType());
|
return new HealthCheck(GetType());
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return Check();
|
||||||
return Check();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
public override HealthCheck Check()
|
public override HealthCheck Check()
|
||||||
{
|
{
|
||||||
var rootFolders = _artistService.AllArtistPaths()
|
var rootFolders = _artistService.AllArtistPaths()
|
||||||
.Select(s => _rootFolderService.GetBestRootFolderPath(s.Value))
|
.Select(s => _rootFolderService.GetBestRootFolderPath(s.Value))
|
||||||
.Distinct();
|
.Distinct();
|
||||||
|
|
||||||
var missingRootFolders = rootFolders.Where(s => !_diskProvider.FolderExists(s))
|
var missingRootFolders = rootFolders.Where(s => !_diskProvider.FolderExists(s))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
missingRootFolders.AddRange(_importListFactory.All()
|
missingRootFolders.AddRange(_importListFactory.All()
|
||||||
.Select(s => s.RootFolderPath)
|
.Select(s => s.RootFolderPath)
|
||||||
|
|
|
@ -24,8 +24,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
public override HealthCheck Check()
|
public override HealthCheck Check()
|
||||||
{
|
{
|
||||||
var request = _cloudRequestBuilder.Create()
|
var request = _cloudRequestBuilder.Create()
|
||||||
.Resource("/time")
|
.Resource("/time")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var response = _client.Execute(request);
|
var response = _client.Execute(request);
|
||||||
var result = Json.Deserialize<ServiceTimeResponse>(response.Content);
|
var result = Json.Deserialize<ServiceTimeResponse>(response.Content);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue