From 3beb8ef0d4574ffc5314213dd318e220028e3852 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 8 Jan 2025 21:29:21 +0100 Subject: [PATCH] Adding SignalR and cleanup --- pointMaster/Components/DataHub.cs | 51 ++++ .../Controllers/HeaderNavController.cs | 8 +- pointMaster/Controllers/HomeController.cs | 4 +- pointMaster/Controllers/PatruljeController.cs | 2 +- pointMaster/Controllers/PointController.cs | 3 +- pointMaster/Controllers/PrintController.cs | 51 ++-- pointMaster/Controllers/StatsApiController.cs | 125 ++++++--- pointMaster/Controllers/StatsController.cs | 16 +- pointMaster/Data/DataContext.cs | 38 ++- .../Migrations/20241121135108_DateTime.cs | 3 +- pointMaster/Program.cs | 5 + pointMaster/js/package.json | 1 + pointMaster/js/src/components/Stats.vue | 259 ++++++++++-------- pointMaster/js/src/typings.d.ts | 10 +- pointMaster/pointMaster.csproj | 1 + 15 files changed, 367 insertions(+), 210 deletions(-) create mode 100644 pointMaster/Components/DataHub.cs diff --git a/pointMaster/Components/DataHub.cs b/pointMaster/Components/DataHub.cs new file mode 100644 index 0000000..5acadbe --- /dev/null +++ b/pointMaster/Components/DataHub.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; +using pointMaster.Controllers; +using pointMaster.Data; + +namespace pointMaster.Components +{ + [Authorize(Policy = Roles.Editor)] + public class DataHub : Hub + { + private readonly DataContext dataContext; + + public DataHub(DataContext dataContext) + { + this.dataContext = dataContext; + } + + public override Task OnConnectedAsync() + { + Clients.Caller.SendAsync("ReceiveMessage", "Hey"); + return base.OnConnectedAsync(); + } + + public async Task SendMessage(string user, string message) + { + await Clients.All.SendAsync("ReceiveMessage", user, message); + } + + public async Task SendData() + { + var v0 = await StatModel.Calculate(dataContext); + var v1 = await PointRatioModel.Calculate(dataContext); + var v2 = await PointChartModel.Calculate(dataContext); + + var retval = new StatData() + { + Stats = v0, + PointRatio = v1, + PointChartModels = v2, + }; + await Clients.All.SendAsync("StatData", retval); + } + + public class StatData + { + public List Stats { get; set; } + public PointRatioModel PointRatio { get; set; } + public List PointChartModels { get; set; } + } + } +} diff --git a/pointMaster/Controllers/HeaderNavController.cs b/pointMaster/Controllers/HeaderNavController.cs index e739095..3054942 100644 --- a/pointMaster/Controllers/HeaderNavController.cs +++ b/pointMaster/Controllers/HeaderNavController.cs @@ -1,6 +1,5 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Authorization; -using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace pointMaster.Controllers { @@ -45,7 +44,8 @@ namespace pointMaster.Controllers { public List links { get; set; } = null!; } - public class NavUrl { + public class NavUrl + { public NavUrl() { } public NavUrl(string title, string url) { diff --git a/pointMaster/Controllers/HomeController.cs b/pointMaster/Controllers/HomeController.cs index 3ec109b..921498d 100644 --- a/pointMaster/Controllers/HomeController.cs +++ b/pointMaster/Controllers/HomeController.cs @@ -25,7 +25,7 @@ namespace pointMaster.Controllers var patruljer = await context.Patruljer.Include(p => p.Points).ToListAsync(); vm.Samlet = new List(); - vm.Turnout= new List(); + vm.Turnout = new List(); vm.Points = new List(); foreach (var patrulje in patruljer) @@ -52,7 +52,7 @@ namespace pointMaster.Controllers vm.Samlet = vm.Samlet.OrderByDescending(x => x.point).ToList(); vm.Points = vm.Points.OrderByDescending(x => x.point).ToList(); vm.Turnout = vm.Turnout.OrderByDescending(x => x.point).ToList(); - + return View(vm); } diff --git a/pointMaster/Controllers/PatruljeController.cs b/pointMaster/Controllers/PatruljeController.cs index 0b30707..fa556fb 100644 --- a/pointMaster/Controllers/PatruljeController.cs +++ b/pointMaster/Controllers/PatruljeController.cs @@ -131,7 +131,7 @@ namespace pointMaster.Controllers { public List patruljeModels { get; set; } = null!; public Dictionary patruljePoints { get; set; } = null!; - public Dictionary patruljeTurnout { get; set; } = null!; + public Dictionary patruljeTurnout { get; set; } = null!; } } } diff --git a/pointMaster/Controllers/PointController.cs b/pointMaster/Controllers/PointController.cs index dcb017f..c6b702c 100644 --- a/pointMaster/Controllers/PointController.cs +++ b/pointMaster/Controllers/PointController.cs @@ -89,7 +89,8 @@ namespace pointMaster.Controllers var patrulje = await context.Patruljer.FindAsync(id); - if (patrulje != null) { + if (patrulje != null) + { vm.Patrulje = patrulje; } else diff --git a/pointMaster/Controllers/PrintController.cs b/pointMaster/Controllers/PrintController.cs index bd0b1c0..558b367 100644 --- a/pointMaster/Controllers/PrintController.cs +++ b/pointMaster/Controllers/PrintController.cs @@ -3,40 +3,39 @@ using Microsoft.EntityFrameworkCore; using pointMaster.Data; using pointMaster.Models; using QRCoder; -using System.Drawing; namespace pointMaster.Controllers { - public class PrintController : Controller - { - private readonly DataContext context; + public class PrintController : Controller + { + private readonly DataContext context; - public PrintController(DataContext context) - { - this.context = context; - } + public PrintController(DataContext context) + { + this.context = context; + } - public async Task Patruljer() - { - var vm = new PrintPatruljerModel(); + public async Task Patruljer() + { + var vm = new PrintPatruljerModel(); - vm.patruljer = await context.Patruljer.Include(x => x.PatruljeMedlems).ToListAsync(); + vm.patruljer = await context.Patruljer.Include(x => x.PatruljeMedlems).ToListAsync(); - foreach (var item in vm.patruljer) - { - QRCodeGenerator QrGenerator = new QRCodeGenerator(); - QRCodeData QrCodeInfo = QrGenerator.CreateQrCode(Request.Host.Host + "/point/givpoint/" + item.Id, QRCodeGenerator.ECCLevel.Q); + foreach (var item in vm.patruljer) + { + QRCodeGenerator QrGenerator = new QRCodeGenerator(); + QRCodeData QrCodeInfo = QrGenerator.CreateQrCode(Request.Host.Host + "/point/givpoint/" + item.Id, QRCodeGenerator.ECCLevel.Q); - vm.QRcode.Add(item.Id, "data:image/png;base64," + Convert.ToBase64String(new PngByteQRCode(QrCodeInfo).GetGraphic(20))); - } + vm.QRcode.Add(item.Id, "data:image/png;base64," + Convert.ToBase64String(new PngByteQRCode(QrCodeInfo).GetGraphic(20))); + } - return View(vm); - } + return View(vm); + } - } - public class PrintPatruljerModel - { - public List patruljer { get; set; } = null!; - public Dictionary QRcode { get; set; } = new Dictionary(); - } + } + public class PrintPatruljerModel + { + public List patruljer { get; set; } = null!; + public Dictionary QRcode { get; set; } = new Dictionary(); + } } diff --git a/pointMaster/Controllers/StatsApiController.cs b/pointMaster/Controllers/StatsApiController.cs index f9a521a..95b437a 100644 --- a/pointMaster/Controllers/StatsApiController.cs +++ b/pointMaster/Controllers/StatsApiController.cs @@ -20,6 +20,42 @@ namespace pointMaster.Controllers [HttpGet("GetPointsOverTime")] public async Task GetList() + { + var retval = await PointChartModel.Calculate(dataContext); + + var rval = JsonConvert.SerializeObject(retval, new JsonSerializerSettings() + { + ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore, + + }); + + return Ok(rval); + } + + [Route("getstats")] + public async Task GetStats() + { + var vm = await StatModel.Calculate(dataContext); + + return Ok(JsonConvert.SerializeObject(vm)); + } + + [Route("pointratio")] + public async Task GetPointRatio() + { + var vm = await PointRatioModel.Calculate(dataContext); + + return Ok(JsonConvert.SerializeObject(vm)); + } + } + public class PointChartModel + { + [JsonProperty("name")] + public string Name { get; set; } = null!; + [JsonProperty("data")] + public List Data { get; set; } = null!; + + public static async Task> Calculate(DataContext dataContext) { var pointData = await dataContext.Points.Include(x => x.Patrulje).Include(x => x.Poster).ToListAsync(); @@ -36,7 +72,7 @@ namespace pointMaster.Controllers var total = 0; foreach (var point in group) { - var date = DateTime.Parse(point.DateCreated.ToString()); + var date = DateTime.Parse(point.DateCreated.ToString()).AddHours(1); total += point.Points + point.Turnout; @@ -60,23 +96,28 @@ namespace pointMaster.Controllers }); } - var rval = JsonConvert.SerializeObject(retval, new JsonSerializerSettings() - { - ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore, - - }); - - return Ok(rval); + return retval; } + } + public class PointChartDataModel + { + public string x { get; set; } = null!; + public int y { get; set; } + } - [Route("getstats")] - public async Task GetStats() + public class StatModel + { + public string Title { get; set; } = null!; + public string Value { get; set; } = null!; + + public static async Task> Calculate(DataContext dataContext) { var vm = new List(); var pointData = await dataContext.Points.ToListAsync(); var patruljeData = await dataContext.Patruljer.ToListAsync(); var postData = await dataContext.Poster.ToListAsync(); + var medlemsData = await dataContext.PatruljeMedlemmer.ToListAsync(); vm.Add(new StatModel { @@ -96,16 +137,36 @@ namespace pointMaster.Controllers Value = postData.Count().ToString() }); - return Ok(JsonConvert.SerializeObject(vm)); + vm.Add(new StatModel + { + Title = "Transaktioner", + Value = pointData.Count().ToString() + }); + + vm.Add(new StatModel + { + Title = "Antal medlemmer", + Value = medlemsData.Count().ToString() + }); + + return vm; } + } - [Route("pointratio")] - public async Task GetPointRatio() + public class PointRatioModel + { + [JsonProperty("names")] + public List Names { get; set; } = null!; + [JsonProperty("data")] + public List Data { get; set; } = null!; + + public static async Task Calculate(DataContext dataContext) { - var vm = new PointRatioModel(); - - vm.Names = new List(); - vm.Data = new List(); + var vm = new PointRatioModel + { + Names = new List(), + Data = new List() + }; var data = await dataContext.Patruljer.Include(x => x.Points).ToListAsync(); @@ -115,34 +176,8 @@ namespace pointMaster.Controllers vm.Data.Add(patrulje.Points.Sum(x => x.Points + x.Turnout)); } - return Ok(JsonConvert.SerializeObject(vm)); - } - - public class PointChartModel - { - [JsonProperty("name")] - public string Name { get; set; } = null!; - [JsonProperty("data")] - public List Data { get; set; } = null!; - } - public class PointChartDataModel - { - public string x { get; set; } = null!; - public int y { get; set; } - } - - public class StatModel - { - public string Title { get; set; } = null!; - public string Value { get; set; } = null!; - } - - public class PointRatioModel - { - [JsonProperty("names")] - public List Names { get; set; } = null!; - [JsonProperty("data")] - public List Data { get; set; } = null!; + return vm; } } + } \ No newline at end of file diff --git a/pointMaster/Controllers/StatsController.cs b/pointMaster/Controllers/StatsController.cs index ef106ef..69cfbee 100644 --- a/pointMaster/Controllers/StatsController.cs +++ b/pointMaster/Controllers/StatsController.cs @@ -1,20 +1,14 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using pointMaster.Data; -using pointMaster.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace pointMaster.Controllers { public class StatsController : Controller { - private readonly DataContext dataContext; + public StatsController() { } - public StatsController(DataContext dataContext) - { - this.dataContext = dataContext; - } - public async Task Index() + [Authorize(Policy = Roles.Editor)] + public IActionResult Index() { return View(); } diff --git a/pointMaster/Data/DataContext.cs b/pointMaster/Data/DataContext.cs index 28808da..454e9fd 100644 --- a/pointMaster/Data/DataContext.cs +++ b/pointMaster/Data/DataContext.cs @@ -1,11 +1,45 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using pointMaster.Components; +using pointMaster.Controllers; using pointMaster.Models; namespace pointMaster.Data { public class DataContext : DbContext { - public DataContext(DbContextOptions options) : base(options) { } + private readonly IHubContext _hubContext; + public DataContext(DbContextOptions options, IHubContext hubContext) : base(options) + { + _hubContext = hubContext; + } + + public override int SaveChanges() + { + return base.SaveChanges(); + } + + public async Task SaveChangesAsync() + { + return await SaveChangesAsync(CancellationToken.None); + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken) + { + var result = await base.SaveChangesAsync(cancellationToken); + + var v0 = await StatModel.Calculate(this); + var v1 = await PointRatioModel.Calculate(this); + var v2 = await PointChartModel.Calculate(this); + + await _hubContext.Clients.All.SendAsync("StatData", new DataHub.StatData + { + Stats = v0, + PointRatio = v1, + PointChartModels = v2 + }); + return result; + } public DbSet Patruljer { get; set; } = default!; public DbSet PatruljeMedlemmer { get; set; } = default!; diff --git a/pointMaster/Migrations/20241121135108_DateTime.cs b/pointMaster/Migrations/20241121135108_DateTime.cs index e113569..4721701 100644 --- a/pointMaster/Migrations/20241121135108_DateTime.cs +++ b/pointMaster/Migrations/20241121135108_DateTime.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable diff --git a/pointMaster/Program.cs b/pointMaster/Program.cs index 92ef3e1..e38af98 100644 --- a/pointMaster/Program.cs +++ b/pointMaster/Program.cs @@ -3,6 +3,7 @@ using Keycloak.AuthServices.Authorization; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using pointMaster.Components; using pointMaster.Controllers; using pointMaster.Data; @@ -55,8 +56,12 @@ builder options.ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto; }); +builder.Services.AddSignalR(); + var app = builder.Build(); +app.MapHub("/DataHub"); + if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); diff --git a/pointMaster/js/package.json b/pointMaster/js/package.json index b8b587c..b7df0f2 100644 --- a/pointMaster/js/package.json +++ b/pointMaster/js/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "@aspnet/signalr": "^1.0.27", "@vue/cli-plugin-typescript": "^5.0.8", "apexcharts": "^4.0.0", "axios": "^1.7.7", diff --git a/pointMaster/js/src/components/Stats.vue b/pointMaster/js/src/components/Stats.vue index 139345e..c24672a 100644 --- a/pointMaster/js/src/components/Stats.vue +++ b/pointMaster/js/src/components/Stats.vue @@ -1,23 +1,22 @@ @@ -26,133 +25,165 @@ import ApexCharts, { ApexOptions } from "apexcharts"; import axios from "axios"; import { defineComponent, onMounted, ref } from "vue"; +import { HubConnectionBuilder, LogLevel } from "@aspnet/signalr"; export default defineComponent({ name: "Stats", setup() { - const StatData = ref([]); + const statData = ref([]); - const GetStats = async () => { - const data = (await axios.get("/api/getstats")).data as Stat[]; + const lineChartOptions: ApexOptions = { + chart: { + type: "line", + toolbar: { + show: true, + }, + }, + stroke: { + curve: "smooth" + }, + xaxis: { + type: "datetime", + }, + series: [], + } - StatData.value = data; + const pieChartOptions: ApexOptions = { + chart: { + type: "pie", + toolbar: { + show: true, + }, + }, + series: [], }; - const initializeChart = async () => { - const options: ApexOptions = { - chart: { - type: "line", - toolbar: { - show: true, - }, - }, - xaxis: { - type: "datetime", - }, - series: [], - }; + var lineChart: ApexCharts; + var pieChart: ApexCharts; - const chart = new ApexCharts( - document.querySelector("#chart"), - options + const initalizeCharts = async () => { + + lineChart = new ApexCharts( + document.querySelector("#line-chart"), + lineChartOptions ); - chart.render(); - - const data = (await axios.get("/api/GetPointsOverTime")).data; - - chart.updateOptions({ - series: data, - } as ApexOptions); - }; - - const initializePieChart = async () => { - const options: ApexOptions = { - chart: { - type: "pie", - toolbar: { - show: true, - }, - }, - series: [], - }; - - const chart = new ApexCharts( - document.querySelector("#pie"), - options + lineChart.render(); + + pieChart = new ApexCharts( + document.querySelector("#pie-chart"), + pieChartOptions ); - chart.render(); + pieChart.render(); + } - const data = (await axios.get("/api/pointratio")).data; + const initializeHub = async () => { - chart.updateOptions({ - series: data.data, - labels: data.names, + const connection = new HubConnectionBuilder().withUrl("../DataHub").configureLogging(LogLevel.Information).build(); + + connection.start(); + + connection.on("ReceiveMessage", async (data: StatData) => { + connection.invoke("SendData"); + console.log(data); + }) + + connection.on("StatData", (a:StatData) => { + console.log(a); + updateData(a); + }) + } + + const updateData = (data: StatData) => { + lineChart.updateOptions({ + series: data.pointChartModels, } as ApexOptions); - }; + pieChart.updateOptions({ + series: data.pointRatio.data, + labels: data.pointRatio.names, + }); + + statData.value = (data.stats as Stat[]); + } + + onMounted(async () => { - await GetStats(); - await initializeChart(); - await initializePieChart(); + await initalizeCharts() + await initializeHub() }); return { - StatData, + statData, }; }, }); diff --git a/pointMaster/js/src/typings.d.ts b/pointMaster/js/src/typings.d.ts index b4188bb..716b555 100644 --- a/pointMaster/js/src/typings.d.ts +++ b/pointMaster/js/src/typings.d.ts @@ -8,6 +8,12 @@ type Point = { } type Stat = { - Title: string; - Value: string; + title: string; + value: string; +} + +type StatData = { + stats: any; + pointRatio: any; + pointChartModels: any; } \ No newline at end of file diff --git a/pointMaster/pointMaster.csproj b/pointMaster/pointMaster.csproj index d8f1978..30421be 100644 --- a/pointMaster/pointMaster.csproj +++ b/pointMaster/pointMaster.csproj @@ -12,6 +12,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive