Commit 6a1b5441 authored by yangxiaodong's avatar yangxiaodong

add dashboard

parent fc50d995
using System.Reflection;
namespace DotNetCore.CAP.Dashboard
{
internal class CombinedResourceDispatcher : EmbeddedResourceDispatcher
{
private readonly Assembly _assembly;
private readonly string _baseNamespace;
private readonly string[] _resourceNames;
public CombinedResourceDispatcher(
string contentType,
Assembly assembly,
string baseNamespace,
params string[] resourceNames) : base(contentType, assembly, null)
{
_assembly = assembly;
_baseNamespace = baseNamespace;
_resourceNames = resourceNames;
}
protected override void WriteResponse(DashboardResponse response)
{
foreach (var resourceName in _resourceNames)
{
WriteResource(
response,
_assembly,
$"{_baseNamespace}.{resourceName}");
}
}
}
}
using System;
using System.Net;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
internal class CommandDispatcher : IDashboardDispatcher
{
private readonly Func<DashboardContext, bool> _command;
public CommandDispatcher(Func<DashboardContext, bool> command)
{
_command = command;
}
public Task Dispatch(DashboardContext context)
{
var request = context.Request;
var response = context.Response;
if (!"POST".Equals(request.Method, StringComparison.OrdinalIgnoreCase))
{
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
return Task.FromResult(false);
}
if (_command(context))
{
response.StatusCode = (int)HttpStatusCode.NoContent;
}
else
{
response.StatusCode = 422;
}
return Task.FromResult(true);
}
}
}
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
using System.Reflection; using System.Reflection;
namespace Hangfire.Dashboard.Resources { namespace DotNetCore.CAP.Dashboard.Resources
{
using System; using System;
...@@ -41,7 +42,7 @@ namespace Hangfire.Dashboard.Resources { ...@@ -41,7 +42,7 @@ namespace Hangfire.Dashboard.Resources {
public static global::System.Resources.ResourceManager ResourceManager { public static global::System.Resources.ResourceManager ResourceManager {
get { get {
if (object.ReferenceEquals(resourceMan, null)) { if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Hangfire.Dashboard.Content.resx.Strings", typeof(Strings).GetTypeInfo().Assembly); global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetCore.CAP.Dashboard.Content.resx.Strings", typeof(Strings).GetTypeInfo().Assembly);
resourceMan = temp; resourceMan = temp;
} }
return resourceMan; return resourceMan;
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace DotNetCore.CAP.Dashboard namespace DotNetCore.CAP.Dashboard
{ {
class DashboardContext public abstract class DashboardContext
{ {
protected DashboardContext(IStorage storage, DashboardOptions options)
{
if (storage == null) throw new ArgumentNullException(nameof(storage));
if (options == null) throw new ArgumentNullException(nameof(options));
Storage = storage;
Options = options;
}
public IStorage Storage { get; }
public DashboardOptions Options { get; }
public Match UriMatch { get; set; }
public DashboardRequest Request { get; protected set; }
public DashboardResponse Response { get; protected set; }
} }
} }
using System;
namespace DotNetCore.CAP.Dashboard
{
public class DashboardMetric
{
public DashboardMetric(string name, Func<RazorPage, Metric> func)
: this(name, name, func)
{
}
public DashboardMetric(string name, string title, Func<RazorPage, Metric> func)
{
Name = name;
Title = title;
Func = func;
}
public string Name { get; }
public Func<RazorPage, Metric> Func { get; }
public string Title { get; set; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using DotNetCore.CAP.Dashboard.Resources;
namespace DotNetCore.CAP.Dashboard
{
public static class DashboardMetrics
{
private static readonly Dictionary<string, DashboardMetric> Metrics = new Dictionary<string, DashboardMetric>();
static DashboardMetrics()
{
AddMetric(ServerCount);
AddMetric(RecurringJobCount);
AddMetric(RetriesCount);
AddMetric(EnqueuedCountOrNull);
AddMetric(FailedCountOrNull);
AddMetric(EnqueuedAndQueueCount);
AddMetric(ScheduledCount);
AddMetric(ProcessingCount);
AddMetric(SucceededCount);
AddMetric(FailedCount);
AddMetric(DeletedCount);
AddMetric(AwaitingCount);
}
public static void AddMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
lock (Metrics)
{
Metrics[metric.Name] = metric;
}
}
public static IEnumerable<DashboardMetric> GetMetrics()
{
lock (Metrics)
{
return Metrics.Values.ToList();
}
}
public static readonly DashboardMetric ServerCount = new DashboardMetric(
"servers:count",
"Metrics_Servers",
page => new Metric(page.Statistics.Servers.ToString("N0"))
{
Style = page.Statistics.Servers == 0 ? MetricStyle.Warning : MetricStyle.Default,
Highlighted = page.Statistics.Servers == 0,
Title = page.Statistics.Servers == 0
? "No active servers found. Jobs will not be processed."
: null
});
public static readonly DashboardMetric RecurringJobCount = new DashboardMetric(
"recurring:count",
"Metrics_RecurringJobs",
page => new Metric(page.Statistics.Recurring.ToString("N0")));
public static readonly DashboardMetric RetriesCount = new DashboardMetric(
"retries:count",
"Metrics_Retries",
page =>
{
long retryCount;
using (var connection = page.Storage.GetConnection())
{
var storageConnection = connection as IStorageConnection;
if (storageConnection == null)
{
return null;
}
retryCount = storageConnection.GetSetCount("retries");
}
return new Metric(retryCount.ToString("N0"))
{
Style = retryCount > 0 ? MetricStyle.Warning : MetricStyle.Default
};
});
public static readonly DashboardMetric EnqueuedCountOrNull = new DashboardMetric(
"enqueued:count-or-null",
"Metrics_EnqueuedCountOrNull",
page => page.Statistics.Enqueued > 0 || page.Statistics.Failed == 0
? new Metric(page.Statistics.Enqueued.ToString("N0"))
{
Style = page.Statistics.Enqueued > 0 ? MetricStyle.Info : MetricStyle.Default,
Highlighted = page.Statistics.Enqueued > 0 && page.Statistics.Failed == 0
}
: null);
public static readonly DashboardMetric FailedCountOrNull = new DashboardMetric(
"failed:count-or-null",
"Metrics_FailedJobs",
page => page.Statistics.Failed > 0
? new Metric(page.Statistics.Failed.ToString("N0"))
{
Style = MetricStyle.Danger,
Highlighted = true,
Title = string.Format(Strings.Metrics_FailedCountOrNull, page.Statistics.Failed)
}
: null);
public static readonly DashboardMetric EnqueuedAndQueueCount = new DashboardMetric(
"enqueued-queues:count",
"Metrics_EnqueuedQueuesCount",
page => new Metric($"{page.Statistics.Enqueued:N0} / {page.Statistics.Queues:N0}")
{
Style = page.Statistics.Enqueued > 0 ? MetricStyle.Info : MetricStyle.Default,
Highlighted = page.Statistics.Enqueued > 0
});
public static readonly DashboardMetric ScheduledCount = new DashboardMetric(
"scheduled:count",
"Metrics_ScheduledJobs",
page => new Metric(page.Statistics.Scheduled.ToString("N0"))
{
Style = page.Statistics.Scheduled > 0 ? MetricStyle.Info : MetricStyle.Default
});
public static readonly DashboardMetric ProcessingCount = new DashboardMetric(
"processing:count",
"Metrics_ProcessingJobs",
page => new Metric(page.Statistics.Processing.ToString("N0"))
{
Style = page.Statistics.Processing > 0 ? MetricStyle.Warning : MetricStyle.Default
});
public static readonly DashboardMetric SucceededCount = new DashboardMetric(
"succeeded:count",
"Metrics_SucceededJobs",
page => new Metric(page.Statistics.Succeeded.ToString("N0"))
{
IntValue = page.Statistics.Succeeded
});
public static readonly DashboardMetric FailedCount = new DashboardMetric(
"failed:count",
"Metrics_FailedJobs",
page => new Metric(page.Statistics.Failed.ToString("N0"))
{
IntValue = page.Statistics.Failed,
Style = page.Statistics.Failed > 0 ? MetricStyle.Danger : MetricStyle.Default,
Highlighted = page.Statistics.Failed > 0
});
public static readonly DashboardMetric DeletedCount = new DashboardMetric(
"deleted:count",
"Metrics_DeletedJobs",
page => new Metric(page.Statistics.Deleted.ToString("N0")));
public static readonly DashboardMetric AwaitingCount = new DashboardMetric(
"awaiting:count",
"Metrics_AwaitingCount",
page =>
{
long awaitingCount = -1;
using (var connection = page.Storage.GetConnection())
{
var storageConnection = connection as IStorageConnection;
if (storageConnection != null)
{
awaitingCount = storageConnection.GetSetCount("awaiting");
}
}
return new Metric(awaitingCount.ToString("N0"))
{
Style = awaitingCount > 0 ? MetricStyle.Info : MetricStyle.Default
};
});
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
public abstract class DashboardRequest
{
public abstract string Method { get; }
public abstract string Path { get; }
public abstract string PathBase { get; }
public abstract string LocalIpAddress { get; }
public abstract string RemoteIpAddress { get; }
public abstract string GetQuery(string key);
public abstract Task<IList<string>> GetFormValuesAsync(string key);
}
}
\ No newline at end of file
using System;
using System.IO;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
public abstract class DashboardResponse
{
public abstract string ContentType { get; set; }
public abstract int StatusCode { get; set; }
public abstract Stream Body { get; }
public abstract void SetExpire(DateTimeOffset? value);
public abstract Task WriteAsync(string text);
}
}
\ No newline at end of file
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
internal class EmbeddedResourceDispatcher : IDashboardDispatcher
{
private readonly Assembly _assembly;
private readonly string _resourceName;
private readonly string _contentType;
public EmbeddedResourceDispatcher(
string contentType,
Assembly assembly,
string resourceName)
{
if (contentType == null) throw new ArgumentNullException(nameof(contentType));
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
_assembly = assembly;
_resourceName = resourceName;
_contentType = contentType;
}
public Task Dispatch(DashboardContext context)
{
context.Response.ContentType = _contentType;
context.Response.SetExpire(DateTimeOffset.Now.AddYears(1));
WriteResponse(context.Response);
return Task.FromResult(true);
}
protected virtual void WriteResponse(DashboardResponse response)
{
WriteResource(response, _assembly, _resourceName);
}
protected void WriteResource(DashboardResponse response, Assembly assembly, string resourceName)
{
using (var inputStream = assembly.GetManifestResourceStream(resourceName))
{
if (inputStream == null)
{
throw new ArgumentException($@"Resource with name {resourceName} not found in assembly {assembly}.");
}
inputStream.CopyTo(response.Body);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Text.RegularExpressions;
using DotNetCore.CAP.Dashboard.Resources;
using DotNetCore.CAP.Dashboard.Pages;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Dashboard
{
public class HtmlHelper
{
private readonly RazorPage _page;
public HtmlHelper(RazorPage page)
{
if (page == null) throw new ArgumentNullException(nameof(page));
_page = page;
}
//public NonEscapedString Breadcrumbs(string title, IDictionary<string, string> items)
//{
// if (items == null) throw new ArgumentNullException(nameof(items));
// return RenderPartial(new Breadcrumbs(title, items));
//}
//public NonEscapedString JobsSidebar()
//{
// return RenderPartial(new SidebarMenu(JobsSidebarMenu.Items));
//}
//public NonEscapedString SidebarMenu(IEnumerable<Func<RazorPage, MenuItem>> items)
//{
// if (items == null) throw new ArgumentNullException(nameof(items));
// return RenderPartial(new SidebarMenu(items));
//}
public NonEscapedString BlockMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
return RenderPartial(new BlockMetric(metric));
}
public NonEscapedString InlineMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
return RenderPartial(new InlineMetric(metric));
}
//public NonEscapedString Paginator(Pager pager)
//{
// if (pager == null) throw new ArgumentNullException(nameof(pager));
// return RenderPartial(new Paginator(pager));
//}
//public NonEscapedString PerPageSelector(Pager pager)
//{
// if (pager == null) throw new ArgumentNullException(nameof(pager));
// return RenderPartial(new PerPageSelector(pager));
//}
public NonEscapedString RenderPartial(RazorPage partialPage)
{
partialPage.Assign(_page);
return new NonEscapedString(partialPage.ToString());
}
public NonEscapedString Raw(string value)
{
return new NonEscapedString(value);
}
public NonEscapedString JobId(string jobId, bool shorten = true)
{
Guid guid;
return new NonEscapedString(Guid.TryParse(jobId, out guid)
? (shorten ? jobId.Substring(0, 8) + "…" : jobId)
: $"#{jobId}");
}
public string JobName(Message job)
{
if (job == null)
{
return Strings.Common_CannotFindTargetMethod;
}
return job.ToString();
}
public NonEscapedString StateLabel(string stateName)
{
if (String.IsNullOrWhiteSpace(stateName))
{
return Raw($"<em>{Strings.Common_NoState}</em>");
}
return Raw($"<span class=\"label label-default\" style=\"background-color: {JobHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>");
}
public NonEscapedString JobIdLink(string jobId)
{
return Raw($"<a href=\"{_page.Url.JobDetails(jobId)}\">{JobId(jobId)}</a>");
}
public NonEscapedString JobNameLink(string jobId, Message job)
{
return Raw($"<a class=\"job-method\" href=\"{_page.Url.JobDetails(jobId)}\">{HtmlEncode(JobName(job))}</a>");
}
public NonEscapedString RelativeTime(DateTime value)
{
return Raw($"<span data-moment=\"{Helper.ToTimestamp(value)}\">{value}</span>");
}
public NonEscapedString MomentTitle(DateTime time, string value)
{
return Raw($"<span data-moment-title=\"{Helper.ToTimestamp(time)}\">{value}</span>");
}
public NonEscapedString LocalTime(DateTime value)
{
return Raw($"<span data-moment-local=\"{Helper.ToTimestamp(value)}\">{value}</span>");
}
public string ToHumanDuration(TimeSpan? duration, bool displaySign = true)
{
if (duration == null) return null;
var builder = new StringBuilder();
if (displaySign)
{
builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+");
}
duration = duration.Value.Duration();
if (duration.Value.Days > 0)
{
builder.Append($"{duration.Value.Days}d ");
}
if (duration.Value.Hours > 0)
{
builder.Append($"{duration.Value.Hours}h ");
}
if (duration.Value.Minutes > 0)
{
builder.Append($"{duration.Value.Minutes}m ");
}
if (duration.Value.TotalHours < 1)
{
if (duration.Value.Seconds > 0)
{
builder.Append(duration.Value.Seconds);
if (duration.Value.Milliseconds > 0)
{
builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}");
}
builder.Append("s ");
}
else
{
if (duration.Value.Milliseconds > 0)
{
builder.Append($"{duration.Value.Milliseconds}ms ");
}
}
}
if (builder.Length <= 1)
{
builder.Append(" <1ms ");
}
builder.Remove(builder.Length - 1, 1);
return builder.ToString();
}
public string FormatProperties(IDictionary<string, string> properties)
{
return String.Join(", ", properties.Select(x => $"{x.Key}: \"{x.Value}\""));
}
public NonEscapedString QueueLabel(string queue)
{
var label = queue != null
? $"<a class=\"text-uppercase\" href=\"{_page.Url.Queue(queue)}\">{queue}</a>"
: $"<span class=\"label label-danger\"><i>{Strings.Common_Unknown}</i></span>";
return new NonEscapedString(label);
}
public NonEscapedString ServerId(string serverId)
{
var parts = serverId.Split(':');
var shortenedId = parts.Length > 1
? String.Join(":", parts.Take(parts.Length - 1))
: serverId;
return new NonEscapedString(
$"<span class=\"labe label-defult text-uppercase\" title=\"{serverId}\">{shortenedId}</span>");
}
//private static readonly StackTraceHtmlFragments StackTraceHtmlFragments = new StackTraceHtmlFragments
//{
// BeforeFrame = "<span class='st-frame'>" , AfterFrame = "</span>",
// BeforeType = "<span class='st-type'>" , AfterType = "</span>",
// BeforeMethod = "<span class='st-method'>" , AfterMethod = "</span>",
// BeforeParameters = "<span class='st-params'>" , AfterParameters = "</span>",
// BeforeParameterType = "<span class='st-param'><span class='st-param-type'>", AfterParameterType = "</span>",
// BeforeParameterName = "<span class='st-param-name'>" , AfterParameterName = "</span></span>",
// BeforeFile = "<span class='st-file'>" , AfterFile = "</span>",
// BeforeLine = "<span class='st-line'>" , AfterLine = "</span>",
//};
public NonEscapedString StackTrace(string stackTrace)
{
try
{
//return new NonEscapedString(StackTraceFormatter.FormatHtml(stackTrace, StackTraceHtmlFragments));
return new NonEscapedString(stackTrace);
}
catch (RegexMatchTimeoutException)
{
return new NonEscapedString(HtmlEncode(stackTrace));
}
}
public string HtmlEncode(string text)
{
return WebUtility.HtmlEncode(text);
}
}
}
namespace DotNetCore.CAP.Dashboard
{
public interface IDashboardAuthorizationFilter
{
bool Authorize( DashboardContext context);
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
public interface IDashboardDispatcher
{
Task Dispatch( DashboardContext context);
}
}
// This file is part of Hangfire.
// Copyright 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Hangfire.Storage.Monitoring; using DotNetCore.CAP.Dashboard.Monitoring;
namespace Hangfire.Storage namespace DotNetCore.CAP.Dashboard
{ {
public interface IMonitoringApi public interface IMonitoringApi
{ {
......
using System;
using System.Collections.Generic;
using System.Text;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Processor.States;
using Newtonsoft.Json;
namespace DotNetCore.CAP.Dashboard
{
public static class JobHistoryRenderer
{
private static readonly IDictionary<string, Func<HtmlHelper, IDictionary<string, string>, NonEscapedString>>
Renderers = new Dictionary<string, Func<HtmlHelper, IDictionary<string, string>, NonEscapedString>>();
private static readonly IDictionary<string, string> BackgroundStateColors
= new Dictionary<string, string>();
private static readonly IDictionary<string, string> ForegroundStateColors
= new Dictionary<string, string>();
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static JobHistoryRenderer()
{
Register(SucceededState.StateName, SucceededRenderer);
Register(FailedState.StateName, FailedRenderer);
Register(ProcessingState.StateName, ProcessingRenderer);
Register(EnqueuedState.StateName, EnqueuedRenderer);
Register(ScheduledState.StateName, ScheduledRenderer);
//Register(DeletedState.StateName, NullRenderer);
//Register(AwaitingState.StateName, AwaitingRenderer);
BackgroundStateColors.Add(EnqueuedState.StateName, "#F5F5F5");
BackgroundStateColors.Add(SucceededState.StateName, "#EDF7ED");
BackgroundStateColors.Add(FailedState.StateName, "#FAEBEA");
BackgroundStateColors.Add(ProcessingState.StateName, "#FCEFDC");
BackgroundStateColors.Add(ScheduledState.StateName, "#E0F3F8");
//BackgroundStateColors.Add(DeletedState.StateName, "#ddd");
//BackgroundStateColors.Add(AwaitingState.StateName, "#F5F5F5");
ForegroundStateColors.Add(EnqueuedState.StateName, "#999");
ForegroundStateColors.Add(SucceededState.StateName, "#5cb85c");
ForegroundStateColors.Add(FailedState.StateName, "#d9534f");
ForegroundStateColors.Add(ProcessingState.StateName, "#f0ad4e");
ForegroundStateColors.Add(ScheduledState.StateName, "#5bc0de");
//ForegroundStateColors.Add(DeletedState.StateName, "#777");
//ForegroundStateColors.Add(AwaitingState.StateName, "#999");
}
public static void AddBackgroundStateColor(string stateName, string color)
{
BackgroundStateColors.Add(stateName, color);
}
public static string GetBackgroundStateColor(string stateName)
{
if (stateName == null || !BackgroundStateColors.ContainsKey(stateName))
{
return "inherit";
}
return BackgroundStateColors[stateName];
}
public static void AddForegroundStateColor(string stateName, string color)
{
ForegroundStateColors.Add(stateName, color);
}
public static string GetForegroundStateColor(string stateName)
{
if (stateName == null || !ForegroundStateColors.ContainsKey(stateName))
{
return "inherit";
}
return ForegroundStateColors[stateName];
}
public static void Register(string state, Func<HtmlHelper, IDictionary<string, string>, NonEscapedString> renderer)
{
if (!Renderers.ContainsKey(state))
{
Renderers.Add(state, renderer);
}
else
{
Renderers[state] = renderer;
}
}
public static bool Exists(string state)
{
return Renderers.ContainsKey(state);
}
public static NonEscapedString RenderHistory(
this HtmlHelper helper,
string state, IDictionary<string, string> properties)
{
var renderer = Renderers.ContainsKey(state)
? Renderers[state]
: DefaultRenderer;
return renderer?.Invoke(helper, properties);
}
public static NonEscapedString NullRenderer(HtmlHelper helper, IDictionary<string, string> properties)
{
return null;
}
public static NonEscapedString DefaultRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{
if (stateData == null || stateData.Count == 0) return null;
var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">");
foreach (var item in stateData)
{
builder.Append($"<dt>{item.Key}</dt>");
builder.Append($"<dd>{item.Value}</dd>");
}
builder.Append("</dl>");
return new NonEscapedString(builder.ToString());
}
public static NonEscapedString SucceededRenderer(HtmlHelper html, IDictionary<string, string> stateData)
{
var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">");
var itemsAdded = false;
if (stateData.ContainsKey("Latency"))
{
var latency = TimeSpan.FromMilliseconds(long.Parse(stateData["Latency"]));
builder.Append($"<dt>Latency:</dt><dd>{html.ToHumanDuration(latency, false)}</dd>");
itemsAdded = true;
}
if (stateData.ContainsKey("PerformanceDuration"))
{
var duration = TimeSpan.FromMilliseconds(long.Parse(stateData["PerformanceDuration"]));
builder.Append($"<dt>Duration:</dt><dd>{html.ToHumanDuration(duration, false)}</dd>");
itemsAdded = true;
}
if (stateData.ContainsKey("Result") && !String.IsNullOrWhiteSpace(stateData["Result"]))
{
var result = stateData["Result"];
builder.Append($"<dt>Result:</dt><dd>{System.Net.WebUtility.HtmlEncode(result)}</dd>");
itemsAdded = true;
}
builder.Append("</dl>");
if (!itemsAdded) return null;
return new NonEscapedString(builder.ToString());
}
private static NonEscapedString FailedRenderer(HtmlHelper html, IDictionary<string, string> stateData)
{
var stackTrace = html.StackTrace(stateData["ExceptionDetails"]).ToString();
return new NonEscapedString(
$"<h4 class=\"exception-type\">{stateData["ExceptionType"]}</h4><p class=\"text-muted\">{stateData["ExceptionMessage"]}</p>{"<pre class=\"stack-trace\">" + stackTrace + "</pre>"}");
}
private static NonEscapedString ProcessingRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{
var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">");
string serverId = null;
if (stateData.ContainsKey("ServerId"))
{
serverId = stateData["ServerId"];
}
else if (stateData.ContainsKey("ServerName"))
{
serverId = stateData["ServerName"];
}
if (serverId != null)
{
builder.Append("<dt>Server:</dt>");
builder.Append($"<dd>{helper.ServerId(serverId)}</dd>");
}
if (stateData.ContainsKey("WorkerId"))
{
builder.Append("<dt>Worker:</dt>");
builder.Append($"<dd>{stateData["WorkerId"].Substring(0, 8)}</dd>");
}
else if (stateData.ContainsKey("WorkerNumber"))
{
builder.Append("<dt>Worker:</dt>");
builder.Append($"<dd>#{stateData["WorkerNumber"]}</dd>");
}
builder.Append("</dl>");
return new NonEscapedString(builder.ToString());
}
private static NonEscapedString EnqueuedRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{
return new NonEscapedString(
$"<dl class=\"dl-horizontal\"><dt>Queue:</dt><dd>{helper.QueueLabel(stateData["Queue"])}</dd></dl>");
}
private static NonEscapedString ScheduledRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{
var enqueueAt = Helper.DeserializeDateTime(stateData["EnqueueAt"]);
return new NonEscapedString(
$"<dl class=\"dl-horizontal\"><dt>Enqueue at:</dt><dd data-moment=\"{Helper.ToTimestamp(enqueueAt)}\">{enqueueAt}</dd></dl>");
}
private static NonEscapedString AwaitingRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{
var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">");
if (stateData.ContainsKey("ParentId"))
{
builder.Append($"<dt>Parent</dt><dd>{helper.JobIdLink(stateData["ParentId"])}</dd>");
}
if (stateData.ContainsKey("NextState"))
{
var nextState = JsonConvert.DeserializeObject<IState>(
stateData["NextState"],
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
builder.Append($"<dt>Next State</dt><dd>{helper.StateLabel(nextState.Name)}</dd>");
}
if (stateData.ContainsKey("Options"))
{
builder.Append($"<dt>Options</dt><dd><code>{helper.HtmlEncode(stateData["Options"])}</code></dd>");
}
builder.Append("</dl>");
return new NonEscapedString(builder.ToString());
}
}
}
using System;
using System.Collections.Generic;
using DotNetCore.CAP.Dashboard.Resources;
namespace DotNetCore.CAP.Dashboard
{
public static class JobsSidebarMenu
{
public static readonly List<Func<RazorPage, MenuItem>> Items
= new List<Func<RazorPage, MenuItem>>();
static JobsSidebarMenu()
{
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Enqueued, page.Url.LinkToQueues())
{
Active = page.RequestPath.StartsWith("/jobs/enqueued"),
Metric = DashboardMetrics.EnqueuedAndQueueCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Scheduled, page.Url.To("/jobs/scheduled"))
{
Active = page.RequestPath.StartsWith("/jobs/scheduled"),
Metric = DashboardMetrics.ScheduledCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Processing, page.Url.To("/jobs/processing"))
{
Active = page.RequestPath.StartsWith("/jobs/processing"),
Metric = DashboardMetrics.ProcessingCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Succeeded, page.Url.To("/jobs/succeeded"))
{
Active = page.RequestPath.StartsWith("/jobs/succeeded"),
Metric = DashboardMetrics.SucceededCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Failed, page.Url.To("/jobs/failed"))
{
Active = page.RequestPath.StartsWith("/jobs/failed"),
Metric = DashboardMetrics.FailedCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Deleted, page.Url.To("/jobs/deleted"))
{
Active = page.RequestPath.StartsWith("/jobs/deleted"),
Metric = DashboardMetrics.DeletedCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Awaiting, page.Url.To("/jobs/awaiting"))
{
Active = page.RequestPath.StartsWith("/jobs/awaiting"),
Metric = DashboardMetrics.AwaitingCount
});
}
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
namespace DotNetCore.CAP.Dashboard
{
internal class JsonStats : IDashboardDispatcher
{
public async Task Dispatch(DashboardContext context)
{
var requestedMetrics = await context.Request.GetFormValuesAsync("metrics[]");
var page = new StubPage();
page.Assign(context);
var metrics = DashboardMetrics.GetMetrics().Where(x => requestedMetrics.Contains(x.Name));
var result = new Dictionary<string, Metric>();
foreach (var metric in metrics)
{
var value = metric.Func(page);
result.Add(metric.Name, value);
}
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new JsonConverter[]{ new StringEnumConverter { CamelCaseText = true } }
};
var serialized = JsonConvert.SerializeObject(result, settings);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(serialized);
}
private class StubPage : RazorPage
{
public override void Execute()
{
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace DotNetCore.CAP.Dashboard
{
public class LocalRequestsOnlyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
// if unknown, assume not local
if (String.IsNullOrEmpty(context.Request.RemoteIpAddress))
return false;
// check if localhost
if (context.Request.RemoteIpAddress == "127.0.0.1" || context.Request.RemoteIpAddress == "::1")
return true;
// compare with local address
if (context.Request.RemoteIpAddress == context.Request.LocalIpAddress)
return true;
return false;
}
}
}
using System.Collections.Generic;
using System.Linq;
namespace DotNetCore.CAP.Dashboard
{
public class MenuItem
{
public MenuItem(string text, string url)
{
Text = text;
Url = url;
}
public string Text { get; }
public string Url { get; }
public bool Active { get; set; }
public DashboardMetric Metric { get; set; }
public DashboardMetric[] Metrics { get; set; }
public IEnumerable<DashboardMetric> GetAllMetrics()
{
var metrics = new List<DashboardMetric> { Metric };
if (Metrics != null)
{
metrics.AddRange(Metrics);
}
return metrics.Where(x => x != null).ToList();
}
}
}
\ No newline at end of file

namespace DotNetCore.CAP.Dashboard
{
public class Metric
{
public Metric(string value)
{
Value = value;
}
public string Value { get; }
public long IntValue { get; set; }
public MetricStyle Style { get; set; }
public bool Highlighted { get; set; }
public string Title { get; set; }
}
public enum MetricStyle
{
Default,
Info,
Success,
Warning,
Danger,
}
internal static class MetricStyleExtensions
{
public static string ToClassName(this MetricStyle style)
{
switch (style)
{
case MetricStyle.Default: return "metric-default";
case MetricStyle.Info: return "metric-info";
case MetricStyle.Success: return "metric-success";
case MetricStyle.Warning: return "metric-warning";
case MetricStyle.Danger: return "metric-danger";
default: return "metric-null";
}
}
}
}
using System; using System;
using Hangfire.Common; using DotNetCore.CAP.Models;
namespace Hangfire.Storage.Monitoring namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
public class DeletedJobDto public class DeletedJobDto
{ {
...@@ -10,7 +10,7 @@ namespace Hangfire.Storage.Monitoring ...@@ -10,7 +10,7 @@ namespace Hangfire.Storage.Monitoring
InDeletedState = true; InDeletedState = true;
} }
public Job Job { get; set; } public Message Message { get; set; }
public DateTime? DeletedAt { get; set; } public DateTime? DeletedAt { get; set; }
public bool InDeletedState { get; set; } public bool InDeletedState { get; set; }
} }
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov. using DotNetCore.CAP.Models;
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System; namespace DotNetCore.CAP.Dashboard.Monitoring
using Hangfire.Common;
namespace Hangfire.Storage.Monitoring
{ {
public class EnqueuedJobDto public class EnqueuedJobDto
{ {
...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring ...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring
InEnqueuedState = true; InEnqueuedState = true;
} }
public Job Job { get; set; } public Message Message { get; set; }
public string State { get; set; } public string State { get; set; }
public DateTime? EnqueuedAt { get; set; } public DateTime? EnqueuedAt { get; set; }
public bool InEnqueuedState { get; set; } public bool InEnqueuedState { get; set; }
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov. using DotNetCore.CAP.Models;
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System; namespace DotNetCore.CAP.Dashboard.Monitoring
using Hangfire.Common;
namespace Hangfire.Storage.Monitoring
{ {
public class FailedJobDto public class FailedJobDto
{ {
...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring ...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring
InFailedState = true; InFailedState = true;
} }
public Job Job { get; set; } public Message Message { get; set; }
public string Reason { get; set; } public string Reason { get; set; }
public DateTime? FailedAt { get; set; } public DateTime? FailedAt { get; set; }
public string ExceptionType { get; set; } public string ExceptionType { get; set; }
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov. using DotNetCore.CAP.Models;
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System; namespace DotNetCore.CAP.Dashboard.Monitoring
using Hangfire.Common;
namespace Hangfire.Storage.Monitoring
{ {
public class FetchedJobDto public class FetchedJobDto
{ {
public Job Job { get; set; } public Message Message { get; set; }
public string State { get; set; } public string State { get; set; }
public DateTime? FetchedAt { get; set; } public DateTime? FetchedAt { get; set; }
} }
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Hangfire.Common; using DotNetCore.CAP.Models;
namespace Hangfire.Storage.Monitoring namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
public class JobDetailsDto public class JobDetailsDto
{ {
public Job Job { get; set; } public Message Message { get; set; }
public DateTime? CreatedAt { get; set; } public DateTime? CreatedAt { get; set; }
public IDictionary<string, string> Properties { get; set; } public IDictionary<string, string> Properties { get; set; }
public IList<StateHistoryDto> History { get; set; } public IList<StateHistoryDto> History { get; set; }
......
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System.Collections.Generic; using System.Collections.Generic;
namespace Hangfire.Storage.Monitoring namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
public class JobList<TDto> : List<KeyValuePair<string, TDto>> public class JobList<TDto> : List<KeyValuePair<string, TDto>>
{ {
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov. using DotNetCore.CAP.Models;
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System; namespace DotNetCore.CAP.Dashboard.Monitoring
using Hangfire.Common;
namespace Hangfire.Storage.Monitoring
{ {
public class ProcessingJobDto public class ProcessingJobDto
{ {
...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring ...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring
InProcessingState = true; InProcessingState = true;
} }
public Job Job { get; set; } public Message Message { get; set; }
public bool InProcessingState { get; set; } public bool InProcessingState { get; set; }
public string ServerId { get; set; } public string ServerId { get; set; }
public DateTime? StartedAt { get; set; } public DateTime? StartedAt { get; set; }
......
// This file is part of Hangfire. namespace DotNetCore.CAP.Dashboard.Monitoring
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
namespace Hangfire.Storage.Monitoring
{ {
public class QueueWithTopEnqueuedJobsDto public class QueueWithTopEnqueuedJobsDto
{ {
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov. using DotNetCore.CAP.Models;
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System; namespace DotNetCore.CAP.Dashboard.Monitoring
using Hangfire.Common;
namespace Hangfire.Storage.Monitoring
{ {
public class ScheduledJobDto public class ScheduledJobDto
{ {
...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring ...@@ -26,7 +10,7 @@ namespace Hangfire.Storage.Monitoring
InScheduledState = true; InScheduledState = true;
} }
public Job Job { get; set; } public Message Message { get; set; }
public DateTime EnqueueAt { get; set; } public DateTime EnqueueAt { get; set; }
public DateTime? ScheduledAt { get; set; } public DateTime? ScheduledAt { get; set; }
public bool InScheduledState { get; set; } public bool InScheduledState { get; set; }
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Hangfire.Storage.Monitoring namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
public class ServerDto public class ServerDto
{ {
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Hangfire.Storage.Monitoring namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
public class StateHistoryDto public class StateHistoryDto
{ {
......
// This file is part of Hangfire. namespace DotNetCore.CAP.Dashboard.Monitoring
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
namespace Hangfire.Storage.Monitoring
{ {
public class StatisticsDto public class StatisticsDto
{ {
......
using System; using System;
using Hangfire.Common; using DotNetCore.CAP.Models;
namespace Hangfire.Storage.Monitoring namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
public class SucceededJobDto public class SucceededJobDto
{ {
...@@ -10,7 +10,7 @@ namespace Hangfire.Storage.Monitoring ...@@ -10,7 +10,7 @@ namespace Hangfire.Storage.Monitoring
InSucceededState = true; InSucceededState = true;
} }
public Job Job { get; set; } public Message Message { get; set; }
public object Result { get; set; } public object Result { get; set; }
public long? TotalDuration { get; set; } public long? TotalDuration { get; set; }
public DateTime? SucceededAt { get; set; } public DateTime? SucceededAt { get; set; }
......
using System;
using System.Collections.Generic;
using DotNetCore.CAP.Dashboard.Resources;
namespace DotNetCore.CAP.Dashboard
{
public static class NavigationMenu
{
public static readonly List<Func<RazorPage, MenuItem>> Items = new List<Func<RazorPage, MenuItem>>();
static NavigationMenu()
{
Items.Add(page => new MenuItem(Strings.NavigationMenu_Jobs, page.Url.LinkToQueues())
{
Active = page.RequestPath.StartsWith("/jobs"),
Metrics = new []
{
DashboardMetrics.EnqueuedCountOrNull,
DashboardMetrics.FailedCountOrNull
}
});
Items.Add(page => new MenuItem(Strings.NavigationMenu_Retries, page.Url.To("/retries"))
{
Active = page.RequestPath.StartsWith("/retries"),
Metric = DashboardMetrics.RetriesCount
});
Items.Add(page => new MenuItem(Strings.NavigationMenu_RecurringJobs, page.Url.To("/recurring"))
{
Active = page.RequestPath.StartsWith("/recurring"),
Metric = DashboardMetrics.RecurringJobCount
});
Items.Add(page => new MenuItem(Strings.NavigationMenu_Servers, page.Url.To("/servers"))
{
Active = page.RequestPath.Equals("/servers"),
Metric = DashboardMetrics.ServerCount
});
}
}
}
\ No newline at end of file
namespace DotNetCore.CAP.Dashboard
{
public class NonEscapedString
{
private readonly string _value;
public NonEscapedString(string value)
{
_value = value;
}
public override string ToString()
{
return _value;
}
}
}
using System;
using System.Collections.Generic;
namespace DotNetCore.CAP.Dashboard
{
public class Pager
{
private const int PageItemsCount = 7;
private const int DefaultRecordsPerPage = 10;
private int _startPageIndex = 1;
private int _endPageIndex = 1;
public Pager(int from, int perPage, long total)
{
FromRecord = from >= 0 ? from : 0;
RecordsPerPage = perPage > 0 ? perPage : DefaultRecordsPerPage;
TotalRecordCount = total;
CurrentPage = FromRecord / RecordsPerPage + 1;
TotalPageCount = (int)Math.Ceiling((double)TotalRecordCount / RecordsPerPage);
PagerItems = GenerateItems();
}
public string BasePageUrl { get; set; }
public int FromRecord { get; }
public int RecordsPerPage { get; }
public int CurrentPage { get; }
public int TotalPageCount { get; }
public long TotalRecordCount { get; }
internal ICollection<Item> PagerItems { get; }
public string PageUrl(int page)
{
if (page < 1 || page > TotalPageCount) return "#";
return BasePageUrl + "?from=" + (page - 1) * RecordsPerPage + "&count=" + RecordsPerPage;
}
public string RecordsPerPageUrl(int perPage)
{
if (perPage <= 0) return "#";
return BasePageUrl + "?from=0&count=" + perPage;
}
private ICollection<Item> GenerateItems()
{
// start page index
_startPageIndex = CurrentPage - PageItemsCount / 2;
if (_startPageIndex + PageItemsCount > TotalPageCount)
_startPageIndex = TotalPageCount + 1 - PageItemsCount;
if (_startPageIndex < 1)
_startPageIndex = 1;
// end page index
_endPageIndex = _startPageIndex + PageItemsCount - 1;
if (_endPageIndex > TotalPageCount)
_endPageIndex = TotalPageCount;
var pagerItems = new List<Item>();
if (TotalPageCount == 0) return pagerItems;
AddPrevious(pagerItems);
// first page
if (_startPageIndex > 1)
pagerItems.Add(new Item(1, false, ItemType.Page));
// more page before numeric page buttons
AddMoreBefore(pagerItems);
// numeric page
AddPageNumbers(pagerItems);
// more page after numeric page buttons
AddMoreAfter(pagerItems);
// last page
if (_endPageIndex < TotalPageCount)
pagerItems.Add(new Item(TotalPageCount, false, ItemType.Page));
// Next page
AddNext(pagerItems);
return pagerItems;
}
private void AddPrevious(ICollection<Item> results)
{
var item = new Item(CurrentPage - 1, CurrentPage == 1, ItemType.PrevPage);
results.Add(item);
}
private void AddMoreBefore(ICollection<Item> results)
{
if (_startPageIndex > 2)
{
var index = _startPageIndex - 1;
if (index < 1) index = 1;
var item = new Item(index, false, ItemType.MorePage);
results.Add(item);
}
}
private void AddMoreAfter(ICollection<Item> results)
{
if (_endPageIndex < TotalPageCount - 1)
{
var index = _startPageIndex + PageItemsCount;
if (index > TotalPageCount) { index = TotalPageCount; }
var item = new Item(index, false, ItemType.MorePage);
results.Add(item);
}
}
private void AddPageNumbers(ICollection<Item> results)
{
for (var pageIndex = _startPageIndex; pageIndex <= _endPageIndex; pageIndex++)
{
var item = new Item(pageIndex, false, ItemType.Page);
results.Add(item);
}
}
private void AddNext(ICollection<Item> results)
{
var item = new Item(CurrentPage + 1, CurrentPage >= TotalPageCount, ItemType.NextPage);
results.Add(item);
}
internal class Item
{
public Item(int pageIndex, bool disabled, ItemType type)
{
PageIndex = pageIndex;
Disabled = disabled;
Type = type;
}
public int PageIndex { get; }
public bool Disabled { get; }
public ItemType Type { get; }
}
internal enum ItemType
{
Page,
PrevPage,
NextPage,
MorePage
}
}
}
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: true *@
@using System @using System
@using System.Collections.Generic @using System.Collections.Generic
@using Hangfire.Dashboard @using DotNetCore.CAP
@using Hangfire.Dashboard.Pages @using DotNetCore.CAP.Dashboard
@using Hangfire.Dashboard.Resources @using DotNetCore.CAP.Dashboard.Pages
@using Hangfire.States @using DotNetCore.CAP.Dashboard.Resources
@using Hangfire.Storage
@inherits RazorPage @inherits RazorPage
@{ @{
Layout = new LayoutPage(Strings.AwaitingJobsPage_Title); Layout = new LayoutPage(Strings.AwaitingJobsPage_Title);
...@@ -20,7 +19,7 @@ ...@@ -20,7 +19,7 @@
using (var connection = Storage.GetConnection()) using (var connection = Storage.GetConnection())
{ {
var storageConnection = connection as JobStorageConnection; var storageConnection = connection as IStorageConnection;
if (storageConnection != null) if (storageConnection != null)
{ {
...@@ -83,7 +82,7 @@ ...@@ -83,7 +82,7 @@
<tbody> <tbody>
@foreach (var jobId in jobIds) @foreach (var jobId in jobIds)
{ {
JobData jobData; MessageData jobData;
StateData stateData; StateData stateData;
StateData parentStateData = null; StateData parentStateData = null;
...@@ -92,10 +91,10 @@ ...@@ -92,10 +91,10 @@
jobData = connection.GetJobData(jobId); jobData = connection.GetJobData(jobId);
stateData = connection.GetStateData(jobId); stateData = connection.GetStateData(jobId);
if (stateData != null && stateData.Name == AwaitingState.StateName) //if (stateData != null && stateData.Name == AwaitingState.StateName)
{ //{
parentStateData = connection.GetStateData(stateData.Data["ParentId"]); // parentStateData = connection.GetStateData(stateData.Data["ParentId"]);
} //}
} }
<tr class="js-jobs-list-row @(jobData != null ? "hover" : null)"> <tr class="js-jobs-list-row @(jobData != null ? "hover" : null)">
...@@ -112,7 +111,7 @@ ...@@ -112,7 +111,7 @@
else else
{ {
<td class="word-break"> <td class="word-break">
@Html.JobNameLink(jobId, jobData.Job) @Html.JobNameLink(jobId, jobData.Message)
</td> </td>
<td class="min-width"> <td class="min-width">
@if (stateData != null && stateData.Data.ContainsKey("Options") && !String.IsNullOrWhiteSpace(stateData.Data["Options"])) @if (stateData != null && stateData.Data.ContainsKey("Options") && !String.IsNullOrWhiteSpace(stateData.Data["Options"]))
......
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
partial class BlockMetric partial class BlockMetric
{ {
......
using System.Collections.Generic; using System.Collections.Generic;
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
partial class Breadcrumbs partial class Breadcrumbs
{ {
......
using System.Collections.Generic; using System.Collections.Generic;
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
partial class HomePage internal partial class HomePage
{ {
public static readonly List<DashboardMetric> Metrics = new List<DashboardMetric>(); public static readonly List<DashboardMetric> Metrics = new List<DashboardMetric>();
} }
} }
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System @using System
@using System.Collections.Generic @using System.Collections.Generic
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@using Hangfire.Dashboard.Pages @using DotNetCore.CAP.Dashboard.Pages
@using Hangfire.Dashboard.Resources @using DotNetCore.CAP.Dashboard.Resources
@using Newtonsoft.Json @using Newtonsoft.Json
@inherits RazorPage @inherits RazorPage
@{ @{
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
#line 2 "..\..\Dashboard\Pages\HomePage.cshtml" #line 2 "..\..\Dashboard\Pages\HomePage.cshtml"
...@@ -27,19 +27,19 @@ namespace Hangfire.Dashboard.Pages ...@@ -27,19 +27,19 @@ namespace Hangfire.Dashboard.Pages
using System.Text; using System.Text;
#line 4 "..\..\Dashboard\Pages\HomePage.cshtml" #line 4 "..\..\Dashboard\Pages\HomePage.cshtml"
using Hangfire.Dashboard; using DotNetCore.CAP.Dashboard;
#line default #line default
#line hidden #line hidden
#line 5 "..\..\Dashboard\Pages\HomePage.cshtml" #line 5 "..\..\Dashboard\Pages\HomePage.cshtml"
using Hangfire.Dashboard.Pages; using DotNetCore.CAP.Dashboard.Pages;
#line default #line default
#line hidden #line hidden
#line 6 "..\..\Dashboard\Pages\HomePage.cshtml" #line 6 "..\..\Dashboard\Pages\HomePage.cshtml"
using Hangfire.Dashboard.Resources; using DotNetCore.CAP.Dashboard.Resources;
#line default #line default
#line hidden #line hidden
......
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
partial class InlineMetric internal partial class InlineMetric
{ {
public InlineMetric(DashboardMetric dashboardMetric) public InlineMetric(DashboardMetric dashboardMetric)
{ {
......
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
partial class LayoutPage partial class LayoutPage
{ {
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
@using System @using System
@using System.Globalization @using System.Globalization
@using System.Reflection @using System.Reflection
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@using Hangfire.Dashboard.Pages @using DotNetCore.CAP.Dashboard.Pages
@using Hangfire.Dashboard.Resources @using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage @inherits RazorPage
<!DOCTYPE html> <!DOCTYPE html>
<html lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName"> <html lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName">
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
#line 2 "..\..\Dashboard\Pages\LayoutPage.cshtml" #line 2 "..\..\Dashboard\Pages\LayoutPage.cshtml"
...@@ -34,19 +34,19 @@ namespace Hangfire.Dashboard.Pages ...@@ -34,19 +34,19 @@ namespace Hangfire.Dashboard.Pages
using System.Text; using System.Text;
#line 5 "..\..\Dashboard\Pages\LayoutPage.cshtml" #line 5 "..\..\Dashboard\Pages\LayoutPage.cshtml"
using Hangfire.Dashboard; using DotNetCore.CAP.Dashboard;
#line default #line default
#line hidden #line hidden
#line 6 "..\..\Dashboard\Pages\LayoutPage.cshtml" #line 6 "..\..\Dashboard\Pages\LayoutPage.cshtml"
using Hangfire.Dashboard.Pages; using DotNetCore.CAP.Dashboard.Pages;
#line default #line default
#line hidden #line hidden
#line 7 "..\..\Dashboard\Pages\LayoutPage.cshtml" #line 7 "..\..\Dashboard\Pages\LayoutPage.cshtml"
using Hangfire.Dashboard.Resources; using DotNetCore.CAP.Dashboard.Resources;
#line default #line default
#line hidden #line hidden
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System @using System
@using System.Linq @using System.Linq
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@using Hangfire.Dashboard.Pages @using DotNetCore.CAP.Dashboard.Pages
@using Hangfire.Dashboard.Resources @using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage @inherits RazorPage
@{ @{
Layout = new LayoutPage(Strings.ProcessingJobsPage_Title); Layout = new LayoutPage(Strings.ProcessingJobsPage_Title);
......
using System;
using System.Collections.Generic;
namespace DotNetCore.CAP.Dashboard.Pages
{
partial class SidebarMenu
{
public SidebarMenu( IEnumerable<Func<RazorPage, MenuItem>> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
Items = items;
}
public IEnumerable<Func<RazorPage, MenuItem>> Items { get; }
}
}
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System @using System
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@using Hangfire.Dashboard.Pages @using DotNetCore.CAP.Dashboard.Pages
@using Hangfire.Dashboard.Resources @using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage @inherits RazorPage
@{ @{
Layout = new LayoutPage(Strings.SucceededJobsPage_Title); Layout = new LayoutPage(Strings.SucceededJobsPage_Title);
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@using Hangfire.Dashboard.Resources @using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage @inherits RazorPage
@{ @{
var metric = DashboardMetric.Func(this); var metric = DashboardMetric.Func(this);
var className = metric == null ? "metric-null" : metric.Style.ToClassName(); var className = metric == null ? "metric-null" : metric.Style.ToClassName();
var highlighted = metric != null && metric.Highlighted ? "highlighted" : null; var highlighted = metric != null && metric.Highlighted ? "highlighted" : null;
} }
<div class="metric @className @highlighted"> <div class="metric @className @highlighted">
<div class="metric-body" data-metric="@DashboardMetric.Name"> <div class="metric-body" data-metric="@DashboardMetric.Name">
@(metric?.Value) @(metric?.Value)
</div> </div>
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
...@@ -17,13 +17,13 @@ namespace Hangfire.Dashboard.Pages ...@@ -17,13 +17,13 @@ namespace Hangfire.Dashboard.Pages
using System.Text; using System.Text;
#line 2 "..\..\Dashboard\Pages\_BlockMetric.cshtml" #line 2 "..\..\Dashboard\Pages\_BlockMetric.cshtml"
using Hangfire.Dashboard; using DotNetCore.CAP.Dashboard;
#line default #line default
#line hidden #line hidden
#line 3 "..\..\Dashboard\Pages\_BlockMetric.cshtml" #line 3 "..\..\Dashboard\Pages\_BlockMetric.cshtml"
using Hangfire.Dashboard.Resources; using DotNetCore.CAP.Dashboard.Resources;
#line default #line default
#line hidden #line hidden
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@inherits RazorPage @inherits RazorPage
<ol class="breadcrumb"> <ol class="breadcrumb">
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@inherits RazorPage @inherits RazorPage
@{ @{
var metric = DashboardMetric.Func(this); var metric = DashboardMetric.Func(this);
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
...@@ -17,7 +17,7 @@ namespace Hangfire.Dashboard.Pages ...@@ -17,7 +17,7 @@ namespace Hangfire.Dashboard.Pages
using System.Text; using System.Text;
#line 2 "..\..\Dashboard\Pages\_InlineMetric.cshtml" #line 2 "..\..\Dashboard\Pages\_InlineMetric.cshtml"
using Hangfire.Dashboard; using DotNetCore.CAP.Dashboard;
#line default #line default
#line hidden #line hidden
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@inherits RazorPage @inherits RazorPage
@if (NavigationMenu.Items.Count > 0) @if (NavigationMenu.Items.Count > 0)
{ {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
...@@ -17,7 +17,7 @@ namespace Hangfire.Dashboard.Pages ...@@ -17,7 +17,7 @@ namespace Hangfire.Dashboard.Pages
using System.Text; using System.Text;
#line 2 "..\..\Dashboard\Pages\_Navigation.cshtml" #line 2 "..\..\Dashboard\Pages\_Navigation.cshtml"
using Hangfire.Dashboard; using DotNetCore.CAP.Dashboard;
#line default #line default
#line hidden #line hidden
......
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
partial class Paginator partial class Paginator
{ {
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@using Hangfire.Dashboard.Resources; @using DotNetCore.CAP.Dashboard.Resources;
@inherits RazorPage @inherits RazorPage
<div class="btn-toolbar"> <div class="btn-toolbar">
......
namespace Hangfire.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
{ {
partial class PerPageSelector partial class PerPageSelector
{ {
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using Hangfire.Dashboard.Resources; @using DotNetCore.CAP.Dashboard.Resources;
@inherits Hangfire.Dashboard.RazorPage @inherits DotNetCore.CAP.Dashboard.RazorPage
<div class="btn-group pull-right paginator"> <div class="btn-group pull-right paginator">
@foreach (var count in new[] { 10, 20, 50, 100, 500 }) @foreach (var count in new[] { 10, 20, 50, 100, 500 })
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using Hangfire.Dashboard @using DotNetCore.CAP.Dashboard
@inherits RazorPage @inherits RazorPage
@if (Items.Any()) @if (Items.Any())
{ {
......
// This file is part of Hangfire. using System;
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Text; using System.Text;
using Hangfire.Storage.Monitoring; using System.Threading.Tasks;
using DotNetCore.CAP.Dashboard.Monitoring;
namespace Hangfire.Dashboard namespace DotNetCore.CAP.Dashboard
{ {
public abstract class RazorPage public abstract class RazorPage
{ {
...@@ -39,7 +24,7 @@ namespace Hangfire.Dashboard ...@@ -39,7 +24,7 @@ namespace Hangfire.Dashboard
public HtmlHelper Html { get; private set; } public HtmlHelper Html { get; private set; }
public UrlHelper Url { get; private set; } public UrlHelper Url { get; private set; }
public JobStorage Storage { get; internal set; } public IStorage Storage { get; internal set; }
public string AppPath { get; internal set; } public string AppPath { get; internal set; }
public int StatsPollingInterval { get; internal set; } public int StatsPollingInterval { get; internal set; }
public Stopwatch GenerationTime { get; private set; } public Stopwatch GenerationTime { get; private set; }
......
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
internal class RazorPageDispatcher : IDashboardDispatcher
{
private readonly Func<Match, RazorPage> _pageFunc;
public RazorPageDispatcher(Func<Match, RazorPage> pageFunc)
{
_pageFunc = pageFunc;
}
public Task Dispatch(DashboardContext context)
{
context.Response.ContentType = "text/html";
var page = _pageFunc(context.UriMatch);
page.Assign(context);
return context.Response.WriteAsync(page.ToString());
}
}
}
using System;
using System.Collections.Generic;
namespace DotNetCore.CAP.Dashboard
{
public class UrlHelper
{
private readonly DashboardContext _context;
public UrlHelper( DashboardContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
_context = context;
}
public string To(string relativePath)
{
return
_context.Request.PathBase
+ relativePath;
}
public string Home()
{
return To("/");
}
public string JobDetails(string jobId)
{
return To("/jobs/details/" + jobId);
}
public string LinkToQueues()
{
return To("/jobs/enqueued");
}
public string Queue(string queue)
{
return To("/jobs/enqueued/" + queue);
}
}
}
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using Hangfire.Dashboard
@using Hangfire.Dashboard.Pages
@using Hangfire.Dashboard.Resources
@inherits RazorPage
@{
Layout = new LayoutPage(Strings.DeletedJobsPage_Title);
int from, perPage;
int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);
var monitor = Storage.GetMonitoringApi();
var pager = new Pager(from, perPage, monitor.DeletedListCount());
var jobs = monitor.DeletedJobs(pager.FromRecord, pager.RecordsPerPage);
}
<div class="row">
<div class="col-md-3">
@Html.JobsSidebar()
</div>
<div class="col-md-9">
<h1 class="page-header">@Strings.DeletedJobsPage_Title</h1>
@if (pager.TotalPageCount == 0)
{
<div class="alert alert-info">
@Strings.DeletedJobsPage_NoJobs
</div>
}
else
{
<div class="js-jobs-list">
<div class="btn-toolbar btn-toolbar-top">
<button class="js-jobs-list-command btn btn-sm btn-primary"
data-url="@Url.To("/jobs/deleted/requeue")"
data-loading-text="@Strings.Common_Enqueueing"
disabled="disabled">
<span class="glyphicon glyphicon-repeat"></span>
@Strings.Common_RequeueJobs
</button>
@Html.PerPageSelector(pager)
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th class="min-width">
<input type="checkbox" class="js-jobs-list-select-all" />
</th>
<th class="min-width">@Strings.Common_Id</th>
<th>@Strings.Common_Job</th>
<th class="align-right">@Strings.DeletedJobsPage_Table_Deleted</th>
</tr>
</thead>
<tbody>
@foreach (var job in jobs)
{
<tr class="js-jobs-list-row @(job.Value == null || !job.Value.InDeletedState ? "obsolete-data" : null) @(job.Value != null && job.Value.InDeletedState && job.Value != null ? "hover" : null)">
<td>
@if (job.Value == null || job.Value.InDeletedState)
{
<input type="checkbox" class="js-jobs-list-checkbox" name="jobs[]" value="@job.Key" />
}
</td>
<td class="min-width">
@Html.JobIdLink(job.Key)
@if (job.Value != null && !job.Value.InDeletedState)
{
<span title="@Strings.Common_JobStateChanged_Text" class="glyphicon glyphicon-question-sign"></span>
}
</td>
@if (job.Value == null)
{
<td colspan="2"><em>@Strings.Common_JobExpired</em></td>
}
else
{
<td class="word-break">
@Html.JobNameLink(job.Key, job.Value.Job)
</td>
<td class="align-right">
@if (job.Value.DeletedAt.HasValue)
{
@Html.RelativeTime(job.Value.DeletedAt.Value)
}
</td>
}
</tr>
}
</tbody>
</table>
</div>
@Html.Paginator(pager)
</div>
}
</div>
</div>
namespace Hangfire.Dashboard.Pages
{
partial class EnqueuedJobsPage
{
public EnqueuedJobsPage(string queue)
{
Queue = queue;
}
public string Queue { get; }
}
}
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System.Collections
@using System.Collections.Generic
@using Hangfire.Dashboard
@using Hangfire.Dashboard.Pages
@using Hangfire.Dashboard.Resources
@inherits RazorPage
@{
Layout = new LayoutPage(Queue.ToUpperInvariant());
int from, perPage;
int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);
var monitor = Storage.GetMonitoringApi();
var pager = new Pager(from, perPage, monitor.EnqueuedCount(Queue));
var enqueuedJobs = monitor.EnqueuedJobs(Queue, pager.FromRecord, pager.RecordsPerPage);
}
<div class="row">
<div class="col-md-3">
@Html.JobsSidebar()
</div>
<div class="col-md-9">
@Html.Breadcrumbs(Queue.ToUpperInvariant(), new Dictionary<string, string>
{
{ "Queues", Url.LinkToQueues() }
})
<h1 class="page-header">@Queue.ToUpperInvariant() <small>@Strings.EnqueuedJobsPage_Title</small></h1>
@if (pager.TotalPageCount == 0)
{
<div class="alert alert-info">
@Strings.EnqueuedJobsPage_NoJobs
</div>
}
else
{
<div class="js-jobs-list">
<div class="btn-toolbar btn-toolbar-top">
<button class="js-jobs-list-command btn btn-sm btn-default"
data-url="@Url.To("/jobs/enqueued/delete")"
data-loading-text="@Strings.Common_Deleting"
data-confirm="@Strings.Common_DeleteConfirm"
disabled="disabled">
<span class="glyphicon glyphicon-remove"></span>
@Strings.Common_DeleteSelected
</button>
@Html.PerPageSelector(pager)
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<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 class="align-right">@Strings.Common_Enqueued</th>
</tr>
</thead>
<tbody>
@foreach (var job in enqueuedJobs)
{
<tr class="js-jobs-list-row hover @(job.Value == null || !job.Value.InEnqueuedState ? "obsolete-data" : null)">
<td>
@if (job.Value != null)
{
<input type="checkbox" class="js-jobs-list-checkbox" name="jobs[]" value="@job.Key"/>
}
</td>
<td class="min-width">
@Html.JobIdLink(job.Key)
@if (job.Value != null && !job.Value.InEnqueuedState)
{
<span title="@Strings.Common_JobStateChanged_Text" class="glyphicon glyphicon-question-sign"></span>
}
</td>
@if (job.Value == null)
{
<td colspan="3"><em>@Strings.Common_JobExpired</em></td>
}
else
{
<td class="min-width">
@Html.StateLabel(job.Value.State)
</td>
<td class="word-break">
@Html.JobNameLink(job.Key, job.Value.Job)
</td>
<td class="align-right">
@if (job.Value.EnqueuedAt.HasValue)
{
@Html.RelativeTime(job.Value.EnqueuedAt.Value)
}
else
{
<em>@Strings.Common_NotAvailable</em>
}
</td>
}
</tr>
}
</tbody>
</table>
</div>
@Html.Paginator(pager)
</div>
}
</div>
</div>
\ No newline at end of file
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System
@using Hangfire.Dashboard
@using Hangfire.Dashboard.Pages
@using Hangfire.Dashboard.Resources
@inherits RazorPage
@{
Layout = new LayoutPage(Strings.FailedJobsPage_Title);
int from, perPage;
int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);
var monitor = Storage.GetMonitoringApi();
var pager = new Pager(from, perPage, monitor.FailedCount());
var failedJobs = monitor.FailedJobs(pager.FromRecord, pager.RecordsPerPage);
}
<div class="row">
<div class="col-md-3">
@Html.JobsSidebar()
</div>
<div class="col-md-9">
<h1 class="page-header">@Strings.FailedJobsPage_Title</h1>
@if (pager.TotalPageCount == 0)
{
<div class="alert alert-success">
@Strings.FailedJobsPage_NoJobs
</div>
}
else
{
<div class="alert alert-warning">
@Html.Raw(Strings.FailedJobsPage_FailedJobsNotExpire_Warning_Html)
</div>
<div class="js-jobs-list">
<div class="btn-toolbar btn-toolbar-top">
<button class="js-jobs-list-command btn btn-sm btn-primary"
data-url="@Url.To("/jobs/failed/requeue")"
data-loading-text="@Strings.Common_Enqueueing"
disabled="disabled">
<span class="glyphicon glyphicon-repeat"></span>
@Strings.Common_RequeueJobs
</button>
<button class="js-jobs-list-command btn btn-sm btn-default"
data-url="@Url.To("/jobs/failed/delete")"
data-loading-text="@Strings.Common_Deleting"
data-confirm="@Strings.Common_DeleteConfirm">
<span class="glyphicon glyphicon-remove"></span>
@Strings.Common_DeleteSelected
</button>
@Html.PerPageSelector(pager)
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th class="min-width">
<input type="checkbox" class="js-jobs-list-select-all" />
</th>
<th class="min-width">@Strings.Common_Id</th>
<th>@Strings.FailedJobsPage_Table_Failed</th>
<th>@Strings.Common_Job</th>
</tr>
</thead>
<tbody>
@{ var index = 0; }
@foreach (var job in failedJobs)
{
<tr class="js-jobs-list-row @(!job.Value.InFailedState ? "obsolete-data" : null) @(job.Value.InFailedState ? "hover" : null)">
<td rowspan="@(job.Value.InFailedState ? "2" : "1")">
@if (job.Value.InFailedState)
{
<input type="checkbox" class="js-jobs-list-checkbox" name="jobs[]" value="@job.Key" />
}
</td>
<td class="min-width" rowspan="@(job.Value.InFailedState ? "2" : "1")">
@Html.JobIdLink(job.Key)
@if (!job.Value.InFailedState)
{
<span title="@Strings.Common_JobStateChanged_Text" class="glyphicon glyphicon-question-sign"></span>
}
</td>
<td class="min-width">
@if (job.Value.FailedAt.HasValue)
{
@Html.RelativeTime(job.Value.FailedAt.Value)
}
</td>
<td>
<div class="word-break">
@Html.JobNameLink(job.Key, job.Value.Job)
</div>
@if (!String.IsNullOrEmpty(job.Value.ExceptionMessage))
{
<div style="color: #888;">
@job.Value.Reason <a class="expander" href="#">@(index == 0 ? Strings.Common_LessDetails : Strings.Common_MoreDetails)</a>
</div>
}
</td>
</tr>
if (job.Value.InFailedState)
{
<tr>
<td colspan="2" class="failed-job-details">
<div class="expandable" style="@(index++ == 0 ? "display: block;" : null)">
<h4>@job.Value.ExceptionType</h4>
<p class="text-muted">
@job.Value.ExceptionMessage
</p>
@if (!String.IsNullOrEmpty(job.Value.ExceptionDetails))
{
<pre class="stack-trace"><code>@Html.StackTrace(job.Value.ExceptionDetails)</code></pre>
}
</div>
</td>
</tr>
}
}
</tbody>
</table>
</div>
@Html.Paginator(pager)
</div>
}
</div>
</div>
\ No newline at end of file
namespace Hangfire.Dashboard.Pages
{
partial class FetchedJobsPage
{
public FetchedJobsPage(string queue)
{
Queue = queue;
}
public string Queue { get; }
}
}
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System.Collections
@using System.Collections.Generic
@using Hangfire.Dashboard
@using Hangfire.Dashboard.Pages
@using Hangfire.Dashboard.Resources
@inherits RazorPage
@{
Layout = new LayoutPage(Queue.ToUpperInvariant());
int from, perPage;
int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);
var monitor = Storage.GetMonitoringApi();
var pager = new Pager(from, perPage, monitor.FetchedCount(Queue));
var fetchedJobs = monitor.FetchedJobs(Queue, pager.FromRecord, pager.RecordsPerPage);
}
<div class="row">
<div class="col-md-3">
@Html.JobsSidebar()
</div>
<div class="col-md-9">
@Html.Breadcrumbs(Strings.FetchedJobsPage_Title, new Dictionary<string, string>
{
{ "Queues", Url.LinkToQueues() },
{ Queue.ToUpperInvariant(), Url.Queue(Queue) }
})
<h1 class="page-header">
@Queue.ToUpperInvariant() <small>@Strings.FetchedJobsPage_Title</small>
</h1>
@if (pager.TotalPageCount == 0)
{
<div class="alert alert-info">
@Strings.FetchedJobsPage_NoJobs
</div>
}
else
{
<div class="js-jobs-list">
<div class="btn-toolbar btn-toolbar-top">
<button class="js-jobs-list-command btn btn-sm btn-primary"
data-url="@Url.To("/jobs/enqueued/requeue")"
data-loading-text="@Strings.Common_Enqueueing"
disabled="disabled">
<span class="glyphicon glyphicon-repeat"></span>
@Strings.Common_RequeueJobs
</button>
<button class="js-jobs-list-command btn btn-sm btn-default"
data-url="@Url.To("/jobs/enqueued/delete")"
data-loading-text="@Strings.Common_Deleting"
data-confirm="@Strings.Common_DeleteConfirm"
disabled="disabled">
<span class="glyphicon glyphicon-remove"></span>
@Strings.Common_DeleteSelected
</button>
@Html.PerPageSelector(pager)
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<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 class="align-right">@Strings.Common_Fetched</th>
</tr>
</thead>
<tbody>
@foreach (var job in fetchedJobs)
{
<tr class="js-jobs-list-row hover @(job.Value == null ? "obsolete-data" : null)">
<td>
@if (job.Value != null)
{
<input type="checkbox" class="js-jobs-list-checkbox" name="jobs[]" value="@job.Key" />
}
</td>
<td class="min-width">
@Html.JobIdLink(job.Key)
</td>
@if (job.Value == null)
{
<td colspan="3"><em>@Strings.Common_JobExpired</em></td>
}
else
{
<td class="min-width">
@Html.StateLabel(job.Value.State)
</td>
<td class="word-break">
@Html.JobNameLink(job.Key, job.Value.Job)
</td>
<td class="align-right">
@if (job.Value.FetchedAt.HasValue)
{
@Html.RelativeTime(job.Value.FetchedAt.Value)
}
</td>
}
</tr>
}
</tbody>
</table>
</div>
@Html.Paginator(pager)
</div>
}
</div>
</div>
\ No newline at end of file
using Hangfire.Annotations;
namespace Hangfire.Dashboard.Pages
{
partial class JobDetailsPage
{
public JobDetailsPage(string jobId)
{
JobId = jobId;
}
public string JobId { get; }
}
}
This diff is collapsed.
This diff is collapsed.
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using Hangfire.Dashboard
@using Hangfire.Dashboard.Pages
@using Hangfire.Dashboard.Resources
@inherits RazorPage
@{
Layout = new LayoutPage(Strings.ScheduledJobsPage_Title);
int from, perPage;
int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);
var monitor = Storage.GetMonitoringApi();
var pager = new Pager(from, perPage, monitor.ScheduledCount());
var scheduledJobs = monitor.ScheduledJobs(pager.FromRecord, pager.RecordsPerPage);
}
<div class="row">
<div class="col-md-3">
@Html.JobsSidebar()
</div>
<div class="col-md-9">
<h1 class="page-header">@Strings.ScheduledJobsPage_Title</h1>
@if (pager.TotalPageCount == 0)
{
<div class="alert alert-info">
@Strings.ScheduledJobsPage_NoJobs
</div>
}
else
{
<div class="js-jobs-list">
<div class="btn-toolbar btn-toolbar-top">
<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-play"></span>
@Strings.ScheduledJobsPage_EnqueueNow
</button>
<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>
@Html.PerPageSelector(pager)
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th class="min-width">
<input type="checkbox" class="js-jobs-list-select-all" />
</th>
<th class="min-width">@Strings.Common_Id</th>
<th>@Strings.ScheduledJobsPage_Table_Enqueue</th>
<th>@Strings.Common_Job</th>
<th class="align-right">@Strings.ScheduledJobsPage_Table_Scheduled</th>
</tr>
</thead>
@foreach (var job in scheduledJobs)
{
<tr class="js-jobs-list-row @(!job.Value.InScheduledState ? "obsolete-data" : null) @(job.Value.InScheduledState ? "hover" : null)">
<td>
@if (job.Value.InScheduledState)
{
<input type="checkbox" class="js-jobs-list-checkbox" name="jobs[]" value="@job.Key" />
}
</td>
<td class="min-width">
@Html.JobIdLink(job.Key)
@if (!job.Value.InScheduledState)
{
<span title="@Strings.Common_JobStateChanged_Text" class="glyphicon glyphicon-question-sign"></span>
}
</td>
<td class="min-width">
@Html.RelativeTime(job.Value.EnqueueAt)
</td>
<td class="word-break">
@Html.JobNameLink(job.Key, job.Value.Job)
</td>
<td class="align-right">
@if (job.Value.ScheduledAt.HasValue)
{
@Html.RelativeTime(job.Value.ScheduledAt.Value)
}
</td>
</tr>
}
</table>
</div>
@Html.Paginator(pager)
</div>
}
</div>
</div>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment