Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suspend resume functionality #1675

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Hangfire.AspNetCore/Hangfire.AspNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net451;net461;netstandard1.3;netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>net48;net451;net461;netstandard1.3;netstandard2.0;netcoreapp3.0</TargetFrameworks>
<DebugType>portable</DebugType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<RootNamespace>Hangfire</RootNamespace>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)'=='net45' or '$(TargetFramework)'=='net461'">
<PropertyGroup Condition="'$(TargetFramework)'=='net45' or '$(TargetFramework)'=='net461' or '$(TargetFramework)'=='net48'">
<DebugType>full</DebugType>
</PropertyGroup>

Expand All @@ -28,7 +28,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netstandard2.0'">
<ItemGroup Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net48'">
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.0.0" />
Expand Down
58 changes: 55 additions & 3 deletions src/Hangfire.Core/Dashboard/Content/resx/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/Hangfire.Core/Dashboard/Content/resx/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,33 @@
<data name="Common_Error" xml:space="preserve">
<value>Error</value>
</data>

<data name="JobsSidebarMenu_Suspended" xml:space="preserve">
<value>Suspended</value>
</data>
<data name="Metrics_SuspendedCount" xml:space="preserve">
<value>Suspended</value>
</data>
<data name="SuspendedPage_Warning_Html" xml:space="preserve">
<value>&lt;h4&gt;This page can't be displayed&lt;/h4&gt;
&lt;p&gt;
Don't worry, suspend is working as expected. Your current job storage does not support
some queries required to show this page. Please try to update your storage or wait until
the full command set is implemented.
&lt;/p&gt;
&lt;p&gt;
Please go to the &lt;a href="{0}"&gt;Scheduled jobs&lt;/a&gt; page to see all the
scheduled jobs including retries.
&lt;/p&gt;</value>
</data>
<data name="SuspendedJobsPage_NoJobs" xml:space="preserve">
<value>All is OK – you have no suspended.</value>
</data>
<data name="SuspendedJobsPage_Title" xml:space="preserve">
<value>Suspended Jobs</value>
</data>
<data name="JobDetailsPage_Parameters" xml:space="preserve">
<value>Parameters</value>

</data>
</root>
23 changes: 22 additions & 1 deletion src/Hangfire.Core/Dashboard/DashboardMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static IEnumerable<DashboardMetric> GetMetrics()
}

public static readonly DashboardMetric ServerCount = new DashboardMetric(
"servers:count",
"servers:count",
"Metrics_Servers",
page => new Metric(page.Statistics.Servers)
{
Expand Down Expand Up @@ -198,5 +198,26 @@ public static IEnumerable<DashboardMetric> GetMetrics()
Style = awaitingCount > 0 ? MetricStyle.Info : MetricStyle.Default
};
});
public static readonly DashboardMetric SuspendedCount = new DashboardMetric(
"suspended:count",
"Metrics_SuspendedCount",
page =>
{
long suspendedCount = -1;

using (var connection = page.Storage.GetConnection())
{
var storageConnection = connection as JobStorageConnection;
if (storageConnection != null)
{
suspendedCount = storageConnection.GetSetCount("paused-jobs");
}
}

return new Metric(suspendedCount)
{
Style = suspendedCount > 0 ? MetricStyle.Info : MetricStyle.Default
};
});
}
}
10 changes: 10 additions & 0 deletions src/Hangfire.Core/Dashboard/DashboardRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ static DashboardRoutes()
Routes.AddRazorPage("/servers", x => new ServersPage());
Routes.AddRazorPage("/retries", x => new RetriesPage());

Routes.AddRazorPage("/jobs/suspended", x => new SuspendedJobsPage());
Routes.AddRecurringBatchCommand(
"/recurring/suspend",
(manager, jobId) => manager.SuspendJob(jobId));

Routes.AddRecurringBatchCommand(
"/recurring/resume",
(manager, jobId) => manager.ResumeJob(jobId));


#endregion
}

Expand Down
6 changes: 6 additions & 0 deletions src/Hangfire.Core/Dashboard/JobsSidebarMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ static JobsSidebarMenu()
Active = page.RequestPath.StartsWith("/jobs/awaiting"),
Metric = DashboardMetrics.AwaitingCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Suspended, page.Url.To("/jobs/suspended"))
{
Active = page.RequestPath.StartsWith("/jobs/suspended"),
Metric = DashboardMetrics.SuspendedCount
});

}
}
}
171 changes: 171 additions & 0 deletions src/Hangfire.Core/Dashboard/Pages/SuspendedJobsPage.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System
@using System.Collections.Generic
@using Hangfire
@using Hangfire.Common
@using Hangfire.Dashboard
@using Hangfire.Dashboard.Pages
@using Hangfire.Dashboard.Resources
@using Hangfire.Storage
@inherits RazorPage
@{
Layout = new LayoutPage(Strings.SuspendedJobsPage_Title);

int from, perPage;

int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);

Pager pager = null;
List<string> jobIds = null;

using (var connection = Storage.GetConnection())
{
var storageConnection = connection as JobStorageConnection;

if (storageConnection != null)
{
pager = new Pager(@from, perPage, storageConnection.GetSetCount("paused-jobs"));
jobIds = storageConnection.GetRangeFromSet("retries", pager.FromRecord, pager.FromRecord + pager.RecordsPerPage - 1);
}
}
}

@if (pager == null)
{
<div class="alert alert-warning">
@Html.Raw(String.Format(Strings.SuspendedPage_Warning_Html, Url.To("/jobs/suspended")))
</div>
}
else
{
<div class="row">
<div class="col-md-3">
@Html.JobsSidebar()
</div>
<div class="col-md-9">
<h1 class="page-header">@Strings.SuspendedJobsPage_Title</h1>
@if (jobIds.Count == 0)
{
<div class="alert alert-success">
@Strings.SuspendedJobsPage_NoJobs
</div>
}
else
{
<div class="js-jobs-list">
<div class="btn-toolbar btn-toolbar-top">
@if (!IsReadOnly)
{
<button class="js-jobs-list-command btn btn-sm btn-primary"
data-url="@Url.To("/jobs/scheduled/enqueue")"
data-loading-text="@Strings.Common_Enqueueing"
disabled="disabled">
<span class="glyphicon glyphicon-repeat"></span>
@Strings.Common_EnqueueButton_Text
</button>
}
@if (!IsReadOnly)
{
<button class="js-jobs-list-command btn btn-sm btn-default"
data-url="@Url.To("/jobs/scheduled/delete")"
data-loading-text="@Strings.Common_Deleting"
data-confirm="@Strings.Common_DeleteConfirm"
disabled="disabled">
<span class="glyphicon glyphicon-remove"></span>
@Strings.Common_DeleteSelected
</button>

@*<button class="js-jobs-list-command btn btn-sm btn-primary"
data-url="@Url.To("/jobs/scheduled/enqueue")"
data-loading-text="@Strings.Common_Enqueueing"
disabled="disabled">
<span class="glyphicon glyphicon-repeat"></span>
@Strings.Common_EnqueueButton_Text
</button>*@
}
@Html.PerPageSelector(pager)
</div>

<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
@if (!IsReadOnly)
{
<th class="min-width">
<input type="checkbox" class="js-jobs-list-select-all" />
</th>
}
<th class="min-width">@Strings.Common_Id</th>
<th class="min-width">@Strings.Common_State</th>
<th>@Strings.Common_Job</th>
<th>@Strings.Common_Reason</th>
<th class="align-right">@Strings.Common_Retry</th>
<th class="align-right">@Strings.Common_Created</th>
</tr>
</thead>
<tbody>
@foreach (var jobId in jobIds)
{
JobData jobData;
StateData stateData;
string _jobId = jobId;
using (var connection = Storage.GetConnection())
{
var recurringJob = connection.GetRecurringJob(jobId, new DefaultTimeZoneResolver(), DateTime.UtcNow);
if (recurringJob != null)
{
_jobId = recurringJob.LastJobId;
}
jobData = connection.GetJobData(_jobId);
stateData = connection.GetStateData(_jobId);
}

<tr class="js-jobs-list-row @(jobData != null ? "hover" : null)">
@if (!IsReadOnly)
{
<td>
<input type="checkbox" class="js-jobs-list-checkbox" name="jobs[]" value="@_jobId" />
</td>
}
<td class="min-width">
@Html.JobIdLink(_jobId)
</td>
@if (jobData == null)
{
<td colspan="5"><em>Job expired.</em></td>
}
else
{
<td class="min-width">
@Html.StateLabel(jobData.State)
</td>
<td class="word-break">
@Html.JobNameLink(_jobId, jobData.Job)
</td>
<td>
@(stateData?.Reason)
</td>
<td class="align-right">
@if (stateData != null && stateData.Data.ContainsKey("EnqueueAt"))
{
@Html.RelativeTime(JobHelper.DeserializeDateTime(stateData.Data["EnqueueAt"]))
}
</td>
<td class="align-right">
@Html.RelativeTime(jobData.CreatedAt)
</td>
}
</tr>
}
</tbody>
</table>
</div>

@Html.Paginator(pager)
</div>
}
</div>
</div>
}
Loading