├── DevApp
├── custom-pulse-ui
│ └── script.html
├── appsettings.json
├── Program.cs
├── DevApp.csproj
├── TestController.cs
├── Startup.cs
└── TestHostedService.cs
├── dotnetify.png
├── Demo
├── pulse-demo.gif
└── NetCoreService
│ ├── appsettings.json
│ ├── NetCoreService.csproj
│ ├── Worker.cs
│ ├── Program.cs
│ ├── pulse-ui
│ └── section_template.html
│ └── Startup.cs
├── DotNetifyLib.Pulse
├── pulse-ui
│ ├── style.css
│ ├── section.html
│ └── index.html
├── DotNetify.Pulse.targets
├── IPulseDataProvider.cs
├── Disposable.cs
├── Logger
│ ├── PulseLoggerProvider.cs
│ ├── LogItem.cs
│ └── LogProvider.cs
├── PulseConfiguration.cs
├── SystemUsage
│ ├── ProcessData.cs
│ ├── CpuUsageProvider.cs
│ └── MemoryUsageProvider.cs
├── PulseVM.cs
├── DotNetifyLib.Pulse.csproj
├── PulseExtensions.cs
└── PulseMiddleware.cs
├── LICENSE.md
├── DotNetify.Pulse.sln
├── README.md
└── .gitignore
/DevApp/custom-pulse-ui/script.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dotnetify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuryd/dotNetify-Pulse/HEAD/dotnetify.png
--------------------------------------------------------------------------------
/Demo/pulse-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuryd/dotNetify-Pulse/HEAD/Demo/pulse-demo.gif
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/pulse-ui/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 14px;
3 | }
4 |
5 | label {
6 | font-size: small;
7 | }
8 |
9 | .card {
10 | padding: 1rem
11 | }
12 | /*style_ext*/
--------------------------------------------------------------------------------
/Demo/NetCoreService/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
--------------------------------------------------------------------------------
/DevApp/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Trace",
5 | "Microsoft": "Information"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "DotNetifyPulse": {
10 | "PushUpdateInterval": 200,
11 | "Providers": {
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/Demo/NetCoreService/NetCoreService.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/DevApp/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore;
2 | using Microsoft.AspNetCore.Hosting;
3 |
4 | namespace DevApp
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | CreateWebHostBuilder(args).Build().Run();
11 | }
12 |
13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
14 | WebHost.CreateDefaultBuilder(args)
15 | .UseStartup();
16 | }
17 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/DotNetify.Pulse.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/DevApp/DevApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | InProcess
6 | DevApp
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2019 Dicky Suryadi
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Demo/NetCoreService/Worker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Microsoft.Extensions.Hosting;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace NetCoreService
10 | {
11 | public class Worker : BackgroundService
12 | {
13 | private readonly ILogger _logger;
14 |
15 | public Worker(ILogger logger)
16 | {
17 | _logger = logger;
18 | }
19 |
20 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
21 | {
22 | while (!stoppingToken.IsCancellationRequested)
23 | {
24 | _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
25 | await Task.Delay(1000, stoppingToken);
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/IPulseDataProvider.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using System;
15 |
16 | namespace DotNetify.Pulse
17 | {
18 | public delegate void OnPushUpdate(bool liveUpdate);
19 |
20 | public interface IPulseDataProvider
21 | {
22 | IDisposable Configure(PulseVM pulseVM, out OnPushUpdate onPushUpdate);
23 | }
24 | }
--------------------------------------------------------------------------------
/Demo/NetCoreService/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.Extensions.Hosting;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace NetCoreService
12 | {
13 | public class Program
14 | {
15 | public static void Main(string[] args)
16 | {
17 | CreateHostBuilder(args).Build().Run();
18 | }
19 |
20 | public static IHostBuilder CreateHostBuilder(string[] args) =>
21 | Host.CreateDefaultBuilder(args)
22 | .ConfigureServices((hostContext, services) =>
23 | {
24 | services.AddHostedService();
25 | })
26 | .ConfigureWebHostDefaults(webBuilder =>
27 | {
28 | webBuilder.UseStartup();
29 | });
30 | }
31 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/Disposable.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using System;
15 | using System.Linq;
16 |
17 | namespace DotNetify.Pulse
18 | {
19 | public class Disposable : IDisposable
20 | {
21 | private readonly Action _dispose;
22 |
23 | public Disposable(Action dispose) => _dispose = dispose;
24 |
25 | public Disposable(params IDisposable[] args) => _dispose = () => args.ToList().ForEach(x => x.Dispose());
26 |
27 | public void Dispose() => _dispose?.Invoke();
28 | }
29 | }
--------------------------------------------------------------------------------
/DevApp/TestController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.Extensions.Logging;
3 | using System.Collections.Generic;
4 |
5 | namespace DevApp.Controllers
6 | {
7 | [Route("api/[controller]")]
8 | [ApiController]
9 | public class TestController : ControllerBase
10 | {
11 | [HttpGet]
12 | public ActionResult Get([FromQuery] string level, [FromServices] ILogger logger)
13 | {
14 | switch (level)
15 | {
16 | case "critical": logger.LogCritical("Critical level is logged"); break;
17 | case "error": logger.LogError("Error level is logged"); break;
18 | case "warning": logger.LogWarning("Warning level is logged"); break;
19 | case "trace": logger.LogTrace("Trace level is logged"); break;
20 | case "debug": logger.LogDebug("Debug level is logged"); break;
21 | default: logger.LogInformation("Info level is logged"); break;
22 | }
23 | return "200 OK";
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/Logger/PulseLoggerProvider.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using Microsoft.Extensions.Logging;
15 |
16 | namespace DotNetify.Pulse.Log
17 | {
18 | public class PulseLoggerProvider : ILoggerProvider
19 | {
20 | private readonly ILogger _logger;
21 |
22 | public PulseLoggerProvider(IPulseLogger logger) => _logger = logger as ILogger;
23 |
24 | public ILogger CreateLogger(string categoryName) => _logger;
25 |
26 | public void Dispose()
27 | {
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/Logger/LogItem.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using Microsoft.Extensions.Logging;
15 | using System;
16 |
17 | namespace DotNetify.Pulse.Log
18 | {
19 | public struct LogItem
20 | {
21 | public DateTimeOffset Time { get; }
22 | public string FormattedTime => Time.ToString("yyyy'-'MM'-'dd HH':'mm':'ss.fff");
23 | public string Level { get; }
24 | public string Message { get; }
25 |
26 | public LogItem(LogLevel level, string message)
27 | {
28 | Level = level.ToString();
29 | Message = message;
30 | Time = DateTimeOffset.Now;
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/PulseConfiguration.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using System;
15 | using Microsoft.Extensions.Configuration;
16 |
17 | namespace DotNetify.Pulse
18 | {
19 | public class PulseConfiguration
20 | {
21 | internal static readonly string SECTION = "DotNetifyPulse";
22 |
23 | // Absolute path to the folder containing UI static files.
24 | public string UIPath { get; set; }
25 |
26 | // Minimum interval between push updates in milliseconds.
27 | public int PushUpdateInterval { get; set; } = 100;
28 |
29 | public IConfigurationSection Providers { get; set; }
30 |
31 | public T GetProvider(string key) where T : class => Providers?.GetSection(key).Get() ?? Activator.CreateInstance();
32 | }
33 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/pulse-ui/section.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Demo/NetCoreService/pulse-ui/section_template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/DevApp/Startup.cs:
--------------------------------------------------------------------------------
1 | using DotNetify;
2 | using DotNetify.Pulse;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using System.IO;
8 |
9 | namespace DevApp
10 | {
11 | public class Startup
12 | {
13 | public IConfiguration Configuration { get; }
14 |
15 | public Startup(IConfiguration configuration)
16 | {
17 | Configuration = configuration;
18 | }
19 |
20 | public void ConfigureServices(IServiceCollection services)
21 | {
22 | services.AddSignalR();
23 | services.AddDotNetify();
24 | services.AddDotNetifyPulse(Configuration);
25 |
26 | services.AddHostedService();
27 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
28 | }
29 |
30 | public void Configure(IApplicationBuilder app)
31 | {
32 | app.UseWebSockets();
33 | app.UseSignalR(config => config.MapDotNetifyHub());
34 | app.UseDotNetify();
35 | app.UseDotNetifyPulse(config =>
36 | {
37 | config.UIPath = $"{Directory.GetCurrentDirectory()}\\custom-pulse-ui";
38 | });
39 |
40 | app.UseStaticFiles();
41 | app.UseMvc();
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Demo/NetCoreService/Startup.cs:
--------------------------------------------------------------------------------
1 | using DotNetify;
2 | using DotNetify.Pulse;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Hosting;
8 |
9 | namespace NetCoreService
10 | {
11 | public class Startup
12 | {
13 | // This method gets called by the runtime. Use this method to add services to the container.
14 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
15 | public void ConfigureServices(IServiceCollection services)
16 | {
17 | services.AddSignalR();
18 | services.AddDotNetify();
19 | services.AddDotNetifyPulse();
20 | }
21 |
22 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
23 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
24 | {
25 | if (env.IsDevelopment())
26 | {
27 | app.UseDeveloperExceptionPage();
28 | }
29 |
30 | app.UseWebSockets();
31 | app.UseDotNetify();
32 | app.UseDotNetifyPulse();
33 |
34 | app.UseRouting();
35 |
36 | app.UseEndpoints(endpoints =>
37 | {
38 | endpoints.MapHub("/dotnetify");
39 | endpoints.MapGet("/", async context =>
40 | {
41 | await context.Response.WriteAsync("Hello World!");
42 | });
43 | });
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/SystemUsage/ProcessData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace DotNetify.Pulse.SystemUsage
5 | {
6 | public class ProcessData
7 | {
8 | private Process _process;
9 | private TimeSpan? _lastTotalProcessorTime;
10 | private DateTime? _lastTimeStamp;
11 |
12 | public double TotalCpu => CalculateTotalCpu();
13 | public double GCTotalMemory => GC.GetTotalMemory(false).ToMBytes();
14 |
15 | public ProcessData(Process process)
16 | {
17 | _process = process;
18 | }
19 |
20 | private double CalculateTotalCpu()
21 | {
22 | _lastTotalProcessorTime = _lastTotalProcessorTime ?? _process.TotalProcessorTime;
23 | _lastTimeStamp = _lastTimeStamp ?? _process.StartTime;
24 |
25 | double totalCpuTimeUsed = _process.TotalProcessorTime.TotalMilliseconds - _lastTotalProcessorTime.Value.TotalMilliseconds;
26 | double cpuTimeElapsed = (DateTime.UtcNow - _lastTimeStamp.Value).TotalMilliseconds * Environment.ProcessorCount;
27 |
28 | _lastTotalProcessorTime = _process.TotalProcessorTime;
29 | _lastTimeStamp = DateTime.UtcNow;
30 |
31 | return (totalCpuTimeUsed / cpuTimeElapsed).ToPercent();
32 | }
33 | }
34 |
35 | internal static class ProcessDataExtensions
36 | {
37 | private static readonly double MB = Math.Pow(1024, 2);
38 |
39 | public static double ToMBytes(this long number) => Math.Round(number / MB, 2);
40 |
41 | public static double ToPercent(this double number) => Math.Round(number * 100, 2);
42 | }
43 | }
--------------------------------------------------------------------------------
/DotNetify.Pulse.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.572
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevApp", "DevApp\DevApp.csproj", "{72C4F7BC-A682-4539-B64A-C6414DB242E2}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetifyLib.Pulse", "DotNetifyLib.Pulse\DotNetifyLib.Pulse.csproj", "{6648ED9D-47B0-4FC1-BA3D-CBAE52B0E633}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {72C4F7BC-A682-4539-B64A-C6414DB242E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {72C4F7BC-A682-4539-B64A-C6414DB242E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {72C4F7BC-A682-4539-B64A-C6414DB242E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {72C4F7BC-A682-4539-B64A-C6414DB242E2}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {6648ED9D-47B0-4FC1-BA3D-CBAE52B0E633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {6648ED9D-47B0-4FC1-BA3D-CBAE52B0E633}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {6648ED9D-47B0-4FC1-BA3D-CBAE52B0E633}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {6648ED9D-47B0-4FC1-BA3D-CBAE52B0E633}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {10E50947-D68F-4242-82F5-23AB3AEE58B5}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/PulseVM.cs:
--------------------------------------------------------------------------------
1 | using DotNetify.Elements;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Reactive.Linq;
5 |
6 | namespace DotNetify.Pulse
7 | {
8 | public class PulseVM : BaseVM
9 | {
10 | private readonly List _disposables = new List();
11 |
12 | public ReactiveProperty LiveUpdate { get; }
13 |
14 | public PulseVM(IEnumerable dataSources, PulseConfiguration pulseConfig)
15 | {
16 | LiveUpdate = AddProperty(nameof(LiveUpdate), true)
17 | .WithAttribute(new CheckboxAttribute() { Label = "Live update" });
18 |
19 | var onPushUpdates = ConfigureDataSource(dataSources);
20 |
21 | // Set minimum interval to push updates.
22 | Observable
23 | .Interval(TimeSpan.FromMilliseconds(pulseConfig.PushUpdateInterval))
24 | .Subscribe(_ =>
25 | {
26 | onPushUpdates.ForEach(x => x(LiveUpdate));
27 | if (LiveUpdate)
28 | PushUpdates();
29 | })
30 | .AddTo(_disposables);
31 | }
32 |
33 | public override void Dispose()
34 | {
35 | _disposables.ForEach(x => x.Dispose());
36 | base.Dispose();
37 | }
38 |
39 | private List ConfigureDataSource(IEnumerable dataSources)
40 | {
41 | var onPushUpdates = new List();
42 |
43 | foreach (var dataSource in dataSources)
44 | {
45 | dataSource
46 | .Configure(this, out OnPushUpdate onPushUpdate)
47 | .AddTo(_disposables);
48 |
49 | if (onPushUpdate != null)
50 | onPushUpdates.Add(onPushUpdate);
51 | }
52 |
53 | return onPushUpdates;
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/DevApp/TestHostedService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Hosting;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Diagnostics;
5 | using System.Reactive.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace DevApp
11 | {
12 | public class TestHostedService : IHostedService
13 | {
14 | private readonly ILogger _logger;
15 | private IDisposable _subs;
16 |
17 | public TestHostedService(ILogger logger) => _logger = logger;
18 |
19 | public Task StartAsync(CancellationToken cancellationToken)
20 | {
21 | int count = 0;
22 | var random = new Random();
23 | _subs = Observable
24 | .Interval(TimeSpan.FromMilliseconds(100))
25 | .Subscribe(_ =>
26 | {
27 | // Simulate logging.
28 | if (random.Next(0, 10) < 5)
29 | {
30 | var level = random.Next(0, 6);
31 | var logLevel = (LogLevel) level;
32 | _logger.Log(logLevel, $"Test log {++count} level {logLevel}");
33 | }
34 | });
35 |
36 | // Simulate CPU load.
37 | Task.Run(() =>
38 | {
39 | int percentage = 0;
40 | Stopwatch watch = new Stopwatch();
41 | watch.Start();
42 | while (!cancellationToken.IsCancellationRequested)
43 | {
44 | if (watch.ElapsedMilliseconds > percentage)
45 | {
46 | Thread.Sleep(100 - percentage);
47 | percentage = random.Next(30, 90);
48 | watch.Reset();
49 | watch.Start();
50 | }
51 | }
52 | });
53 |
54 | return Task.CompletedTask;
55 | }
56 |
57 | public Task StopAsync(CancellationToken cancellationToken)
58 | {
59 | _subs?.Dispose();
60 | return Task.CompletedTask;
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/DotNetifyLib.Pulse.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | DotNetify.Pulse
6 | true
7 | Dicky Suryadi
8 | DotNetify.Pulse
9 | DotNetify.Pulse
10 | Customizable real-time monitoring for .NET services.
11 | 1.0.0
12 | https://github.com/dsuryd/dotNetify-Pulse
13 | https://github.com/dsuryd/dotNetify-Pulse
14 | Copyright 2019-2020
15 |
16 |
17 | LICENSE.md
18 | dotnetify.png
19 |
20 |
21 |
22 |
23 | true
24 | \pulse-ui\
25 | true
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | PreserveNewest
40 |
41 |
42 |
43 |
44 |
45 | True
46 |
47 |
48 |
49 | True
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/PulseExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using System;
15 | using System.Collections.Generic;
16 | using DotNetify.Pulse.Log;
17 | using DotNetify.Pulse.SystemUsage;
18 | using Microsoft.AspNetCore.Builder;
19 | using Microsoft.Extensions.Configuration;
20 | using Microsoft.Extensions.DependencyInjection;
21 | using Microsoft.Extensions.DependencyInjection.Extensions;
22 |
23 | namespace DotNetify.Pulse
24 | {
25 | public static class PulseExtensions
26 | {
27 | public static IServiceCollection AddDotNetifyPulse(this IServiceCollection services, IConfiguration configuration = null)
28 | {
29 | var pulseConfig = configuration?.GetSection(PulseConfiguration.SECTION).Get() ?? new PulseConfiguration();
30 | services.TryAdd(ServiceDescriptor.Singleton(_ => pulseConfig));
31 |
32 | return services
33 | .AddPulseLogger()
34 | .AddPulseMemoryUsage()
35 | .AddPulseCpuUsage();
36 | }
37 |
38 | public static IServiceCollection ClearPulseDataProvider(this IServiceCollection services)
39 | {
40 | return services.RemoveAll();
41 | }
42 |
43 | public static IApplicationBuilder UseDotNetifyPulse(this IApplicationBuilder app, Action options = null)
44 | {
45 | var pulseConfig = app.ApplicationServices.GetRequiredService();
46 | options?.Invoke(pulseConfig);
47 |
48 | VMController.RegisterAssembly(typeof(PulseVM).Assembly);
49 | app.UseMiddleware();
50 | return app;
51 | }
52 |
53 | public static void AddTo(this T item, List items)
54 | {
55 | items.Add(item);
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/pulse-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pulse
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Pulse
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/SystemUsage/CpuUsageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Reactive.Linq;
4 | using System.Reactive.Subjects;
5 | using DotNetify.Elements;
6 | using Microsoft.Extensions.DependencyInjection;
7 |
8 | namespace DotNetify.Pulse.SystemUsage
9 | {
10 | public class CpuUsageProvider : IPulseDataProvider
11 | {
12 | private readonly ReplaySubject _dataStream;
13 | private readonly ProcessData _process = new ProcessData(Process.GetCurrentProcess());
14 | private readonly int _interval = 1000;
15 |
16 | public CpuUsageProvider(PulseConfiguration pulseConfig)
17 | {
18 | _dataStream = new ReplaySubject();
19 | }
20 |
21 | public IDisposable Configure(PulseVM pulseVM, out OnPushUpdate onPushUpdate)
22 | {
23 | // Property names.
24 | string TotalCpu;
25 | string TotalCpuTrend;
26 |
27 | int tick = 0;
28 |
29 | pulseVM.AddProperty(nameof(TotalCpu))
30 | .WithAttribute(new { Label = "Total CPU", Unit = "%" })
31 | .SubscribeTo(_dataStream.Select(x => x.TotalCpu))
32 | .SubscribedBy(value => pulseVM.AddList(nameof(TotalCpuTrend), ToChartData(++tick, value)), out IDisposable totalCpuTrendSubs);
33 |
34 | pulseVM.AddProperty(nameof(TotalCpuTrend), new string[][] { ToChartData(tick, _process.TotalCpu) })
35 | .WithAttribute(new ChartAttribute
36 | {
37 | Title = "Total CPU",
38 | XAxisLabel = "Time",
39 | YAxisLabel = "%",
40 | MaxDataSize = 60,
41 | YAxisMin = 0,
42 | YAxisMax = 100
43 | });
44 |
45 | onPushUpdate = _ =>
46 | {
47 | };
48 |
49 | var intervalSubs = Observable
50 | .Interval(TimeSpan.FromMilliseconds(_interval))
51 | .Subscribe(_ => _dataStream.OnNext(_process));
52 |
53 | return new Disposable(totalCpuTrendSubs, intervalSubs);
54 | }
55 |
56 | private string[] ToChartData(int tick, double value) => new string[] { $"{tick}", $"{value}" };
57 | }
58 |
59 | public static class PulseCpuUsageExtensions
60 | {
61 | public static IServiceCollection AddPulseCpuUsage(this IServiceCollection services)
62 | {
63 | return services.AddSingleton();
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/SystemUsage/MemoryUsageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Reactive.Linq;
4 | using System.Reactive.Subjects;
5 | using DotNetify.Elements;
6 | using Microsoft.Extensions.DependencyInjection;
7 |
8 | namespace DotNetify.Pulse.SystemUsage
9 | {
10 | public class MemoryUsageProvider : IPulseDataProvider
11 | {
12 | private readonly ReplaySubject _dataStream;
13 | private readonly ProcessData _process = new ProcessData(Process.GetCurrentProcess());
14 | private readonly int _interval = 1000;
15 |
16 | public MemoryUsageProvider(PulseConfiguration pulseConfig)
17 | {
18 | _dataStream = new ReplaySubject();
19 | }
20 |
21 | public IDisposable Configure(PulseVM pulseVM, out OnPushUpdate onPushUpdate)
22 | {
23 | // Property names.
24 | string GCTotalMemory;
25 | string GCTotalMemoryTrend;
26 |
27 | int tick = 0;
28 |
29 | pulseVM.AddProperty(nameof(GCTotalMemory))
30 | .WithAttribute(new { Label = "GC Total Memory", Unit = "MB" })
31 | .SubscribeTo(_dataStream.Select(x => x.GCTotalMemory))
32 | .SubscribedBy(value => pulseVM.AddList(nameof(GCTotalMemoryTrend), ToChartData(++tick, value)), out IDisposable totalMemoryTrendSubs);
33 |
34 | pulseVM.AddProperty(nameof(GCTotalMemoryTrend), new string[][] { ToChartData(tick, _process.GCTotalMemory) })
35 | .WithAttribute(new ChartAttribute
36 | {
37 | Title = "GC Total Memory",
38 | XAxisLabel = "Time",
39 | YAxisLabel = "MBytes",
40 | MaxDataSize = 60,
41 | YAxisMin = 0,
42 | YAxisMax = 100
43 | });
44 |
45 | onPushUpdate = _ =>
46 | {
47 | };
48 |
49 | var intervalSubs = Observable
50 | .Interval(TimeSpan.FromMilliseconds(_interval))
51 | .Subscribe(_ => _dataStream.OnNext(_process));
52 |
53 | return new Disposable(totalMemoryTrendSubs, intervalSubs);
54 | }
55 |
56 | private string[] ToChartData(int tick, double value) => new string[] { $"{tick}", $"{value}" };
57 | }
58 |
59 | public static class PulseMemoryUsageExtensions
60 | {
61 | public static IServiceCollection AddPulseMemoryUsage(this IServiceCollection services)
62 | {
63 | return services.AddSingleton();
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/PulseMiddleware.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using Microsoft.AspNetCore.Http;
15 | using System.IO;
16 | using System.Reflection;
17 | using System.Threading.Tasks;
18 |
19 | namespace DotNetify.Pulse
20 | {
21 | internal class PulseMiddleware
22 | {
23 | private static readonly string DEFAULT_UI_PATH = $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}pulse-ui";
24 |
25 | private readonly RequestDelegate _next;
26 | private readonly PulseConfiguration _config;
27 |
28 | public PulseMiddleware(RequestDelegate next, PulseConfiguration config)
29 | {
30 | _next = next;
31 | _config = config;
32 | }
33 |
34 | public async Task InvokeAsync(HttpContext httpContext)
35 | {
36 | var uiPath = (_config.UIPath ?? DEFAULT_UI_PATH).TrimEnd(new char[] { '/', '\\' });
37 | var requestPath = httpContext.Request.Path.ToString();
38 |
39 | if (requestPath.EndsWith("/pulse"))
40 | {
41 | string index = ReadFile("index.html", uiPath, DEFAULT_UI_PATH);
42 | string style = ReadFile("style.css", uiPath, DEFAULT_UI_PATH);
43 | string styleExt = ReadFile("style_ext.css", uiPath, DEFAULT_UI_PATH);
44 | string script = ReadFile("script.html", uiPath, DEFAULT_UI_PATH);
45 | string section = ReadFile("section.html", uiPath, DEFAULT_UI_PATH);
46 | string sectionExt = ReadFile("section_ext.html", uiPath, DEFAULT_UI_PATH);
47 |
48 | await httpContext.Response.WriteAsync(index
49 | .Replace("/*style*/", style)
50 | .Replace("/*style_ext*/", styleExt)
51 | .Replace("", script)
52 | .Replace("", section)
53 | .Replace("", sectionExt)
54 | );
55 | }
56 | else
57 | await _next(httpContext);
58 | }
59 |
60 | private string ReadFile(string fileName, string path, string defaultPath)
61 | {
62 | char directorySeparatorChar = Path.DirectorySeparatorChar;
63 | string separatorWithFileName = $"{directorySeparatorChar}{fileName}";
64 | string filePath = path + separatorWithFileName;
65 | string defaultFilePath = defaultPath + separatorWithFileName;
66 |
67 | string validPath = File.Exists(filePath) ? filePath : File.Exists(defaultFilePath) ? defaultFilePath : null;
68 | if (validPath == null)
69 | return string.Empty;
70 |
71 | return File.ReadAllText(validPath);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/DotNetifyLib.Pulse/Logger/LogProvider.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Dicky Suryadi
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | using System;
15 | using System.Collections.Generic;
16 | using System.Linq;
17 | using System.Reactive.Subjects;
18 | using DotNetify.Elements;
19 | using Microsoft.Extensions.DependencyInjection;
20 | using Microsoft.Extensions.DependencyInjection.Extensions;
21 | using Microsoft.Extensions.Logging;
22 |
23 | namespace DotNetify.Pulse.Log
24 | {
25 | public interface IPulseLogger { }
26 |
27 | public class LogProvider : ILogger, IPulseLogger, IPulseDataProvider
28 | {
29 | private readonly LoggerExternalScopeProvider _scopeProvider;
30 | private readonly ReplaySubject _logStream;
31 | private readonly LogConfiguration _logConfig;
32 |
33 | public class LogConfiguration
34 | {
35 | // How many last log items to cache.
36 | public int Buffer { get; set; } = 5;
37 |
38 | // Number of rows in the log data grid.
39 | public int Rows { get; set; } = 10;
40 | }
41 |
42 | public LogProvider(LoggerExternalScopeProvider scopeProvider, PulseConfiguration pulseConfig)
43 | {
44 | _scopeProvider = scopeProvider;
45 | _logConfig = pulseConfig.GetProvider(nameof(LogProvider));
46 | _logStream = new ReplaySubject(_logConfig.Buffer);
47 | }
48 |
49 | #region ILogger Methods
50 |
51 | public IDisposable BeginScope(TState state)
52 | {
53 | return _scopeProvider.Push(state);
54 | }
55 |
56 | public bool IsEnabled(LogLevel logLevel)
57 | {
58 | if (logLevel == LogLevel.None)
59 | return false;
60 |
61 | return true;
62 | }
63 |
64 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
65 | {
66 | _logStream.OnNext(new LogItem(logLevel, formatter(state, exception)));
67 | }
68 |
69 | #endregion ILogger Methods
70 |
71 | public IDisposable Configure(PulseVM pulseVM, out OnPushUpdate onPushUpdate)
72 | {
73 | // Property names.
74 | string Logs;
75 | string SelectedLog;
76 |
77 | var selectedLog = pulseVM.AddProperty(nameof(SelectedLog));
78 |
79 | pulseVM.AddProperty(nameof(Logs), new LogItem[] { new LogItem(LogLevel.None, "Pulse logger started") })
80 | .WithItemKey(nameof(LogItem.Time))
81 | .WithAttribute(
82 | new DataGridAttribute
83 | {
84 | RowKey = nameof(LogItem.Time),
85 | Columns = new DataGridColumn[]
86 | {
87 | new DataGridColumn(nameof(LogItem.FormattedTime), "Time") { Sortable = true },
88 | new DataGridColumn(nameof(LogItem.Level), "Level") { Sortable = true },
89 | new DataGridColumn(nameof(LogItem.Message), "Message")
90 | },
91 | Rows = _logConfig.Rows
92 | }
93 | .CanSelect(DataGridAttribute.Selection.Single, selectedLog)
94 | );
95 |
96 | var cachedLogs = new List();
97 |
98 | onPushUpdate = liveUpdate =>
99 | {
100 | lock (cachedLogs)
101 | {
102 | if (liveUpdate && cachedLogs.Count > 0)
103 | {
104 | pulseVM.AddList(nameof(Logs), cachedLogs.ToArray());
105 | selectedLog.Value = cachedLogs.Last().Time;
106 | }
107 | cachedLogs.Clear();
108 | }
109 | };
110 |
111 | return _logStream.Subscribe(log =>
112 | {
113 | lock (cachedLogs)
114 | {
115 | cachedLogs.Add(log);
116 | }
117 | });
118 | }
119 | }
120 |
121 | public static class PulseLoggerExtensions
122 | {
123 | public static IServiceCollection AddPulseLogger(this IServiceCollection services)
124 | {
125 | services.TryAddTransient();
126 | services.TryAddEnumerable(ServiceDescriptor.Singleton());
127 | services.TryAddEnumerable(ServiceDescriptor.Singleton());
128 | services.TryAddSingleton(provider => (IPulseLogger) provider.GetServices().First(x => x is LogProvider));
129 | return services;
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://badge.fury.io/nu/DotNetify.Pulse)
4 |
5 | ## DotNetify-Pulse
6 |
7 | Adds an endpoint to any .NET Core service that opens a customizable web view to monitor the service's log activities and resource usage in real-time.
8 |
9 | ### How to Install
10 |
11 | [[Link to demo project]](https://github.com/dsuryd/dotNetify-Pulse/tree/master/Demo/NetCoreService)
12 |
13 | ##### 1. Install the nuget package:
14 | ```
15 | dotnet add package DotNetify.Pulse
16 | ```
17 |
18 | ##### 2. Configure the services and the pipeline in `Startup.cs`:
19 | ```csharp
20 | using DotNetify;
21 | using DotNetify.Pulse;
22 | ...
23 |
24 | public void ConfigureServices(IServiceCollection services)
25 | {
26 | services.AddSignalR();
27 | services.AddDotNetify();
28 | services.AddDotNetifyPulse();
29 | }
30 |
31 | public void Configure(IApplicationBuilder app)
32 | {
33 | app.UseWebSockets();
34 | app.UseDotNetify();
35 | app.UseDotNetifyPulse();
36 |
37 | // .NET Core 2.x only:
38 | app.UseSignalR(config => config.MapDotNetifyHub());
39 |
40 | // .NET Core 3.x only:
41 | app.UseRouting();
42 | app.UseEndpoints(endpoints => endpoints.MapHub("/dotnetify"));
43 | }
44 | ```
45 |
46 | ##### 3. Build, then open your web browser to `/pulse`. You should see this page:
47 | > *Internet connection is required for loading the UI scripts from public CDN.*
48 |
49 |
50 |
51 | ##### 4. If you plan to use `dotnet publish`:
52 | - copy the folder 'pulse-ui' from the build output to your project.
53 | - set the build action of the files in the folder to `Content` and `Copy if newer`.
54 |
55 | ### How to Customize
56 |
57 | #### Overview
58 | Before you proceed, let's first do a bit of a dive on how this thing works.
59 |
60 | This library uses:
61 | - SignalR to push data from your service to the web browser.
62 | - [DotNetify](https://dotnetify.net) to write the code using MVVM + Reactive programming.
63 | - [DotNetify-Elements](https://dotnetify.net/elements) to provide HTML5 web components for the view.
64 |
65 | There is a dotNetify view model in this repo named `PulseVM`. This class is the one that pushes data to the browser view, and it only does that when the page is opened.
66 |
67 | When it's instantiated, it will look for service objects that implements *IPulseDataProvider* and passes its own instance to the interface's `Configure` method so that service object can add properties for the data stream. The view model then regularly checks for data updates on those properties and push them to the browser.
68 |
69 | On the browser side, when it sends the `/pulse` HTTP request, this library's middleware intercepts it and returns `index.html`. You can find it and other static files in your service's output directory under `pulse-ui` folder. The HTML markup uses highly specialized web components from dotNetify-Elements to display data grid and charts and for layout. These components are designed so that they can be configured from the server-side view model and maintain connection with the data properties to auto-update, without requiring client-side scripting.
70 |
71 | #### Steps
72 |
73 | ##### 1. Create your custom data provider class that implements _IPulseDataProvider_.
74 |
75 | For example, let's create a simple clock provider:
76 | - Use `AddProperty` to add a new observable property named "Clock" to the Pulse view model, with an initial value.
77 | - Create a timer to emit new value every second.
78 |
79 | ```csharp
80 | using DotNetify.Pulse;
81 | using System.Reactive.Linq;
82 | ...
83 | public class ClockProvider : IPulseDataProvider
84 | {
85 | public IDisposable Configure(PulseVM pulseVM, out OnPushUpdate onPushUpdate)
86 | {
87 | var clockProperty = pulseVM.AddProperty("Clock", DateTime.Now.ToString("hh:mm:ss"));
88 |
89 | onPushUpdate = _ => { }; // No op.
90 |
91 | return Observable
92 | .Interval(TimeSpan.FromSeconds(1))
93 | .Subscribe(_ => clockProperty.OnNext(DateTime.Now.ToString("hh:mm:ss")));
94 | }
95 | }
96 | ```
97 |
98 | ##### 2. Register the provider in the startup's _ConfigureServices_.
99 |
100 | ```csharp
101 | services.TryAddEnumerable(ServiceDescriptor.Singleton());
102 | ```
103 |
104 | ##### 3. Add a web component to the static HTML page and associate it with the property.
105 |
106 | To do this, you will override the default HTML fragment file called _"section.html"_. Notice that when you build your service, the library creates in your project a folder called _"pulse-ui"_ which contains _"section_template.html"_.
107 |
108 | - Copy and paste this folder to a new one and name it *_"custom-pulse-ui"_*.
109 | - Rename _"section_template.html"_ to *_"section.html"_*.
110 | - Right-click on _"section.html"_, select Properties, and set the "Copy to Output Directory" to *_"Copy if newer"_*.
111 | - Edit the html file and insert the following:
112 | ```html
113 | ...
114 |
115 |
116 |
117 |
118 | ...
119 | ```
120 | > Read the [dotNetify-Elements documentation](https://dotnetify.net/elements) for info on all the available web components.
121 |
122 | ##### 4. Configure the location of the custom UI folder in the startup's _Configure_.
123 |
124 | ```csharp
125 | app.UseDotNetifyPulse(config => config.UIPath = Directory.GetCurrentDirectory() + "\\custom-pulse-ui");
126 | ```
127 |
128 | ##### 5. (Optional) Add to application settings.
129 |
130 | If you want to pass application settings through `appsettings.json`, you can include your custom configuration in the _"DotNetifyPulse"_ configuration section, under `"Providers"`. For example:
131 | ```json
132 | {
133 | "DotNetifyPulse": {
134 | "Providers": {
135 | "ClockProvider": {
136 | "TimeFormat": "hh:mm:ss"
137 | }
138 | }
139 | }
140 | }
141 | ```
142 | To read the settings, inject `PulseConfiguration` type in your constructor, and use the `GetProvider` method:
143 | ```csharp
144 | internal class ClockSettings
145 | {
146 | public string TimeFormat { get; set; }
147 | }
148 |
149 | public ClockProvider(PulseConfiguration config)
150 | {
151 | var settings = config.GetProvider("ClockProvider");
152 | }
153 | ```
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------