diff --git a/BootstrapBlazor.Extensions.sln b/BootstrapBlazor.Extensions.sln index 1f62ca72..7dbcbca8 100644 --- a/BootstrapBlazor.Extensions.sln +++ b/BootstrapBlazor.Extensions.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.0.10828.68 main +VisualStudioVersion = 18.0.10828.68 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components", "{FF1089BE-C704-4374-B629-C57C08E1798F}" EndProject @@ -200,6 +200,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.OpcDa", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestOpcDa", "test\UnitTestOpcDa\UnitTestOpcDa.csproj", "{835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Tasks.Dashboard", "src\components\BootstrapBlazor.Tasks.Dashboard\BootstrapBlazor.Tasks.Dashboard.csproj", "{30C57119-C564-401C-AE3A-6203E2733E1A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -546,6 +548,10 @@ Global {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}.Debug|Any CPU.Build.0 = Debug|Any CPU {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}.Release|Any CPU.ActiveCfg = Release|Any CPU {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}.Release|Any CPU.Build.0 = Release|Any CPU + {30C57119-C564-401C-AE3A-6203E2733E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30C57119-C564-401C-AE3A-6203E2733E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30C57119-C564-401C-AE3A-6203E2733E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30C57119-C564-401C-AE3A-6203E2733E1A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -640,6 +646,7 @@ Global {98373A64-E224-4715-AE02-A8C6DAFF3338} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} {01007B10-7C3C-4136-83FF-981CA39AD3D4} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} + {30C57119-C564-401C-AE3A-6203E2733E1A} = {FF1089BE-C704-4374-B629-C57C08E1798F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D5EB1960-6F30-4CE1-B375-EAE1F787D6FF} diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/BootstrapBlazor.Tasks.Dashboard.csproj b/src/components/BootstrapBlazor.Tasks.Dashboard/BootstrapBlazor.Tasks.Dashboard.csproj new file mode 100644 index 00000000..bdf99fae --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/BootstrapBlazor.Tasks.Dashboard.csproj @@ -0,0 +1,29 @@ + + + + 9.0.0 + + + + Bootstrap Blazor WebAssembly wasm UI Components Task Dashboard + Bootstrap UI components extensions of Task + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/Locales/en-US.json b/src/components/BootstrapBlazor.Tasks.Dashboard/Locales/en-US.json new file mode 100644 index 00000000..d4873ebe --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/Locales/en-US.json @@ -0,0 +1,26 @@ +{ + "BootstrapBlazor.Components.Tasks.TaskDashboard": { + "Name": "Name", + "Status": "Status", + "NextRuntime": "NextRuntime", + "LastRuntime": "LastRuntime", + "LastRunResult": "LastRunResult", + "Exception": "Exception", + "Operation": "Operation", + "SchedulerStatus.Running": "Running", + "SchedulerStatus.Ready": "Ready", + "SchedulerStatus.Disabled": "Disabled", + "TriggerResult.Running": "Running", + "TriggerResult.Success": "Success", + "TriggerResult.Cancelled": "Cancelled", + "TriggerResult.Timeout": "Timeout", + "TriggerResult.Error": "Error", + "ButtonPause": "Pause", + "ButtonRun": "Run", + "ButtonLog": "Log", + "LogDialogTitle": "{0} - Log(Latest 20 Items)", + "LogDialogConsoleHeaderText": "Action Log", + "ExceptionDialogTitle": "{0} - Exception Detail", + "None": "None" + } +} diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/Locales/zh-CN.json b/src/components/BootstrapBlazor.Tasks.Dashboard/Locales/zh-CN.json new file mode 100644 index 00000000..908fdab1 --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/Locales/zh-CN.json @@ -0,0 +1,26 @@ +{ + "BootstrapBlazor.Components.Tasks.TaskDashboard": { + "Name": "名称", + "Status": "调度状态", + "NextRuntime": "下次运行时间", + "LastRuntime": "上次运行时间", + "LastRunResult": "上次结果", + "Exception": "异常", + "Operation": "操作", + "SchedulerStatus.Running": "运行中", + "SchedulerStatus.Ready": "已停止", + "SchedulerStatus.Disabled": "禁用", + "TriggerResult.Running": "运行", + "TriggerResult.Success": "成功", + "TriggerResult.Cancelled": "取消", + "TriggerResult.Timeout": "超时", + "TriggerResult.Error": "故障", + "ButtonPause": "暂停", + "ButtonRun": "运行", + "ButtonLog": "日志", + "LogDialogTitle": "{0} - 日志窗口(最新 20 条)", + "LogDialogConsoleHeaderText": "执行日志", + "ExceptionDialogTitle": "{0} - 异常明细", + "None": "无" + } +} diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor new file mode 100644 index 00000000..0b5467ac --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor @@ -0,0 +1,59 @@ +@namespace BootstrapBlazor.Components.Tasks +@inherits BootstrapComponentBase + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor.cs b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor.cs new file mode 100644 index 00000000..343b3b8c --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor.cs @@ -0,0 +1,122 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; + +namespace BootstrapBlazor.Components.Tasks; + +/// +/// Task Dashboard Component +/// +public partial class TaskDashboard +{ + [Inject, NotNull] + private IStringLocalizer? Localizer { get; set; } + + [Inject, NotNull] + private DialogService? DialogService { get; set; } + + private string? ClassString => CssBuilder.Default("bb-tasks-dashboard") + .AddClassFromAttributes(AdditionalAttributes) + .Build(); + + private IEnumerable _schedulers = []; + + /// + /// + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + + _schedulers = TaskServicesManager.ToList(); + } + + private static Color GetStatusColor(SchedulerStatus status) => status switch + { + SchedulerStatus.Running => Color.Success, + SchedulerStatus.Ready => Color.Primary, + _ => Color.Danger + }; + + private string FormatStatus(SchedulerStatus status) => status switch + { + SchedulerStatus.Running => Localizer["SchedulerStatus.Running"], + SchedulerStatus.Ready => Localizer["SchedulerStatus.Ready"], + _ => Localizer["SchedulerStatus.Disabled"] + }; + + private static string GetStatusIcon(SchedulerStatus status) => status switch + { + SchedulerStatus.Running => "fa-solid fa-play-circle", + SchedulerStatus.Ready => "fa-solid fa-stop-circle", + _ => "fa-solid fa-times-circle" + }; + + private static Color GetResultColor(TriggerResult result) => result switch + { + TriggerResult.Running => Color.Primary, + TriggerResult.Success => Color.Success, + TriggerResult.Cancelled => Color.Dark, + TriggerResult.Timeout => Color.Warning, + _ => Color.Danger, + }; + + private string FormatResult(TriggerResult result) => result switch + { + TriggerResult.Running => Localizer["TriggerResult.Running"], + TriggerResult.Success => Localizer["TriggerResult.Success"], + TriggerResult.Cancelled => Localizer["TriggerResult.Timeout"], + TriggerResult.Timeout => Localizer["TriggerResult.Timeout"], + _ => Localizer["TriggerResult.Error"], + }; + + private static Task OnPause(IScheduler scheduler) + { + scheduler.Status = SchedulerStatus.Ready; + return Task.CompletedTask; + } + + private static Task OnRun(IScheduler scheduler) + { + scheduler.Status = SchedulerStatus.Running; + return Task.CompletedTask; + } + + private async Task OnLog(IScheduler scheduler) + { + var option = new DialogOption() + { + Class = "modal-dialog-task-log", + Title = Localizer["LogDialogTitle", scheduler.Name], + Component = BootstrapDynamicComponent.CreateComponent(new Dictionary + { + [nameof(TaskInfo.Scheduler)] = scheduler, + [nameof(TaskInfo.HeaderText)] = Localizer["LogDialogConsoleHeaderText"].Value + }) + }; + await DialogService.Show(option); + } + + private static bool OnCheckPauseTaskStatus(IScheduler model) => model.Status != SchedulerStatus.Running; + + private static bool OnCheckRunTaskStatus(IScheduler model) => model.Status == SchedulerStatus.Running; + + private static string? FormatDateTime(DateTimeOffset? dateTime) => dateTime?.ToString("yyyy-MM-dd HH:mm:ss"); + + private async Task OnShowException(IScheduler scheduler, Exception ex) + { + var option = new DialogOption() + { + Class = "modal-dialog-task-ex", + IsScrolling = true, + Title = Localizer["ExceptionDialogTitle", scheduler.Name], + BodyTemplate = RenderException(ex) + }; + await DialogService.Show(option); + } + + private static RenderFragment RenderException(Exception ex) => builder => builder.AddContent(0, ex.FormatMarkupString()); +} diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor.css b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor.css new file mode 100644 index 00000000..c6afca40 --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskDashboard.razor.css @@ -0,0 +1,6 @@ +.my-component { + border: 2px dashed red; + padding: 1em; + margin: 1em 0; + background-image: url('background.png'); +} diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/TaskInfo.razor b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskInfo.razor new file mode 100644 index 00000000..d7003186 --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskInfo.razor @@ -0,0 +1,3 @@ +@namespace BootstrapBlazor.Components.Tasks + + diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/TaskInfo.razor.cs b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskInfo.razor.cs new file mode 100644 index 00000000..004b6cee --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/TaskInfo.razor.cs @@ -0,0 +1,94 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information. +// Website: https://admin.blazor.zone + +using Microsoft.AspNetCore.Components; + +namespace BootstrapBlazor.Components.Tasks; + +/// +/// TaskInfo 组件 +/// +public partial class TaskInfo : IDisposable +{ + /// + /// 获得/设置 实例 + /// + [Parameter] + [NotNull] + [EditorRequired] + public IScheduler? Scheduler { get; set; } + + /// + /// 获得/设置 日志窗口标题文本 + /// + [Parameter] + public string? HeaderText { get; set; } + + private List Messages { get; } = new(24); + + /// + /// + /// + /// + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) + { + var scheduler = TaskServicesManager.Get(Scheduler.Name); + if (scheduler != null) + { + scheduler.Triggers.First().PulseCallback += DispatchMessageCallback; + await DispatchMessage(scheduler.Triggers.First()); + } + } + } + + private void DispatchMessageCallback(ITrigger trigger) + { + _ = DispatchMessage(trigger); + } + + private async Task DispatchMessage(ITrigger trigger) + { + if (trigger.LastRuntime == null) + { + return; + } + + var message = $"Trigger({trigger.GetType().Name}) LastRuntime: {trigger.LastRuntime} Run({trigger.LastResult}) NextRuntime: {trigger.NextRuntime} Elapsed: {trigger.LastRunElapsedTime.TotalSeconds}"; + Messages.Add(new ConsoleMessageItem() + { + Message = message + }); + if (Messages.Count > 20) + { + Messages.RemoveAt(0); + } + await InvokeAsync(StateHasChanged); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + var scheduler = TaskServicesManager.Get(Scheduler.Name); + if (scheduler != null) + { + scheduler.Triggers.First().PulseCallback -= DispatchMessageCallback; + } + } + } + + /// + /// + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } +} diff --git a/src/components/BootstrapBlazor.Tasks.Dashboard/_Imports.razor b/src/components/BootstrapBlazor.Tasks.Dashboard/_Imports.razor new file mode 100644 index 00000000..ea98a2fd --- /dev/null +++ b/src/components/BootstrapBlazor.Tasks.Dashboard/_Imports.razor @@ -0,0 +1,3 @@ +@using BootstrapBlazor.Components +@using Microsoft.AspNetCore.Components.Web +@using Longbow.Tasks