mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-27 05:37:10 +08:00
Compare commits
128 Commits
10.10.20.0
...
10.12.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c31cfcbc5 | ||
|
|
0e44bc67cd | ||
|
|
12dfbba42c | ||
|
|
96b4287f3a | ||
|
|
7d406de29f | ||
|
|
81f0ef466a | ||
|
|
3f2d6b133c | ||
|
|
e776dc67eb | ||
|
|
bc5827d140 | ||
|
|
21838bf4af | ||
|
|
6090108597 | ||
|
|
b47b9e6f43 | ||
|
|
18d1cffb2d | ||
|
|
516fd7f235 | ||
|
|
2ee16c3533 | ||
|
|
7d22f5c78e | ||
|
|
3e604ee2fd | ||
|
|
47e442874c | ||
|
|
2a8c0cbab1 | ||
|
|
c26898b49d | ||
|
|
00c24d06a3 | ||
|
|
3461f34240 | ||
|
|
aa1ce08c02 | ||
|
|
9c230c2da9 | ||
|
|
21215d0379 | ||
|
|
7448183791 | ||
|
|
35edd7dc43 | ||
|
|
bd178831e3 | ||
|
|
fe9ec6ad10 | ||
|
|
6f9ec2e24b | ||
|
|
c0337e2b19 | ||
|
|
8a95f48f5a | ||
|
|
14f3c31265 | ||
|
|
1bad65378f | ||
|
|
db3affc67e | ||
|
|
5ee8b50a92 | ||
|
|
301beda2a2 | ||
|
|
628b51a353 | ||
|
|
f03445bc83 | ||
|
|
55a2ff5487 | ||
|
|
0fef7dcf3b | ||
|
|
19d9702606 | ||
|
|
a8a9774932 | ||
|
|
aad0f0e8c3 | ||
|
|
e74eae50a7 | ||
|
|
3b16d7019f | ||
|
|
3e038028c2 | ||
|
|
b1d8041f7e | ||
|
|
53a98b26cd | ||
|
|
42c740fa1b | ||
|
|
556819c90c | ||
|
|
2522333a9c | ||
|
|
bd4ce7c09b | ||
|
|
156ed88bd6 | ||
|
|
2416226eb0 | ||
|
|
976323a716 | ||
|
|
3c9e397403 | ||
|
|
79406ad4a0 | ||
|
|
20c44f10ca | ||
|
|
31d6b2a9e6 | ||
|
|
68e5a9c546 | ||
|
|
b2ea9f99b9 | ||
|
|
6d7d0e468a | ||
|
|
ff1f632de2 | ||
|
|
fc3d7015ee | ||
|
|
40c5acb522 | ||
|
|
f9cc1cbb05 | ||
|
|
cf6e8b58f0 | ||
|
|
615e3bb24c | ||
|
|
4a7534b210 | ||
|
|
58e099cb93 | ||
|
|
a94a9c953c | ||
|
|
35e1ffa3e9 | ||
|
|
4921642151 | ||
|
|
d71ee29da8 | ||
|
|
901aa2d59f | ||
|
|
764957c014 | ||
|
|
0e3898218b | ||
|
|
61f13cef3c | ||
|
|
0b663d9e01 | ||
|
|
6c95c6209f | ||
|
|
4d223d2622 | ||
|
|
e8d7e91b64 | ||
|
|
8175f541ec | ||
|
|
0adbdb926b | ||
|
|
42adee9980 | ||
|
|
427a7404bc | ||
|
|
3658199e0a | ||
|
|
82eedee50a | ||
|
|
6a18fc3e06 | ||
|
|
c37e314ed6 | ||
|
|
a937a85d90 | ||
|
|
35dd4ae9d3 | ||
|
|
0b829ac85c | ||
|
|
aa247422d2 | ||
|
|
2e00e8c135 | ||
|
|
34dd2cf0a7 | ||
|
|
8404e20c5e | ||
|
|
662aa162e9 | ||
|
|
5927738c32 | ||
|
|
3c9f97a5c3 | ||
|
|
179ca0aa0e | ||
|
|
fc09a52da1 | ||
|
|
5436b91c89 | ||
|
|
d1c46f51a6 | ||
|
|
4539d8d198 | ||
|
|
9ea9529a5f | ||
|
|
4e6be23aac | ||
|
|
2fabbd236b | ||
|
|
163a66530e | ||
|
|
29073a00c4 | ||
|
|
c6d4d1ecfa | ||
|
|
ba16889cad | ||
|
|
5aaed35b0f | ||
|
|
df067c91eb | ||
|
|
2078b4a60b | ||
|
|
20a2e3ff8e | ||
|
|
61a973b1b5 | ||
|
|
cbd72e2081 | ||
|
|
4e0377b20c | ||
|
|
fd318d3cdc | ||
|
|
515bdb9700 | ||
|
|
46c1780017 | ||
|
|
fe78a4c3ca | ||
|
|
2d7effadf9 | ||
|
|
346c560f8b | ||
|
|
8e3bd89f61 | ||
|
|
6da142d080 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -364,8 +364,5 @@ FodyWeavers.xsd
|
||||
|
||||
/src/*Pro*/
|
||||
/src/*Pro*
|
||||
/src/**/*Pro*
|
||||
/src/*pro*
|
||||
/src/*pro*/
|
||||
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json
|
||||
/src/.idea/
|
||||
|
||||
14
README.md
14
README.md
@@ -1,11 +1,18 @@
|
||||
# ThingsGateway
|
||||
|
||||
|
||||
[](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
|
||||
[](https://github.com/ThingsGateway/ThingsGateway)
|
||||
[](https://deepwiki.com/ThingsGateway/ThingsGateway)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://thingsgateway.cn/docs/1)
|
||||
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569">
|
||||
<img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ">
|
||||
</a>
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
A cross-platform, high-performance edge data collection gateway based on net9.
|
||||
A cross-platform, high-performance edge data collection gateway based on net8/10.
|
||||
|
||||
|
||||
## Documentation
|
||||
@@ -29,7 +36,6 @@ Account: **SuperAdmin**
|
||||
Password: **111111**
|
||||
|
||||
|
||||
**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
# ThingsGateway
|
||||
|
||||
[](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
|
||||
[](https://github.com/ThingsGateway/ThingsGateway)
|
||||
[](https://deepwiki.com/ThingsGateway/ThingsGateway)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://thingsgateway.cn/docs/1)
|
||||
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569">
|
||||
<img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ">
|
||||
</a>
|
||||
|
||||
## 介绍
|
||||
|
||||
基于net9的跨平台高性能边缘采集网关
|
||||
基于net8/10的跨平台高性能边缘采集网关
|
||||
|
||||
## 文档
|
||||
|
||||
@@ -19,7 +29,6 @@
|
||||
|
||||
密码 : **111111**
|
||||
|
||||
**右上角个人弹出框中,切换到物联网关模块**
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -37,9 +37,8 @@ public class FileController : ControllerBase
|
||||
var root = Directory.GetCurrentDirectory();
|
||||
var wwwroot = Path.Combine(root, "wwwroot");
|
||||
var filePath = Path.Combine(wwwroot, fileName);
|
||||
// 防止路径穿越攻击
|
||||
#pragma warning disable CA3003
|
||||
if (filePath.Contains("..") || !System.IO.File.Exists(filePath))
|
||||
if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public class VerificatInfo : PrimaryIdEntity
|
||||
/// 登录IP
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)]
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public string LoginIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -78,5 +79,6 @@ public class VerificatInfo : PrimaryIdEntity
|
||||
/// 登录设备
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public string Device { get; set; }
|
||||
}
|
||||
|
||||
@@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter
|
||||
|
||||
if (exception == null)
|
||||
{
|
||||
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
|
||||
if (logger.IsEnabled(LogLevel.Warning))
|
||||
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
@@ -19,11 +22,7 @@ public class HardwareInfo
|
||||
/// 当前磁盘信息
|
||||
/// </summary>
|
||||
public DriveInfo DriveInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 硬件信息获取
|
||||
/// </summary>
|
||||
public MachineInfo? MachineInfo { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 主机环境
|
||||
@@ -40,19 +39,118 @@ public class HardwareInfo
|
||||
/// </summary>
|
||||
public string OsArchitecture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 唯一编码
|
||||
/// </summary>
|
||||
public string UUID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进程占用内存
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public int WorkingSet { get; set; }
|
||||
/// <summary>系统名称</summary>
|
||||
public String OSName { get; set; }
|
||||
|
||||
/// <summary>系统版本</summary>
|
||||
public String OSVersion { get; set; }
|
||||
public String UUID { get; set; }
|
||||
|
||||
/// <summary>内存总量。单位MB</summary>
|
||||
public UInt64 Memory { get; set; }
|
||||
|
||||
/// <summary>可用内存。单位MB</summary>
|
||||
public UInt64 AvailableMemory { get; set; }
|
||||
|
||||
/// <summary>CPU占用率</summary>
|
||||
public Double CpuRate { get; set; }
|
||||
public Double Battery { get; set; }
|
||||
public Double Temperature { get; set; }
|
||||
|
||||
|
||||
/// <summary>处理器型号</summary>
|
||||
public String? Processor { get; set; }
|
||||
#region GC与进程内存信息
|
||||
|
||||
/// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary>
|
||||
[DisplayName("GC高内存阈值")]
|
||||
public UInt64 HighMemoryLoadThreshold { get; set; }
|
||||
|
||||
/// <summary>GC 可用内存上限。单位:MB</summary>
|
||||
[DisplayName("GC可用内存上限")]
|
||||
public UInt64 TotalAvailableMemory { get; set; }
|
||||
|
||||
/// <summary>当前托管堆容量。单位:MB</summary>
|
||||
[DisplayName("托管堆容量")]
|
||||
public UInt64 HeapSize { get; set; }
|
||||
|
||||
/// <summary>托管堆已用内存。单位:MB</summary>
|
||||
[DisplayName("托管堆已用")]
|
||||
public UInt64 TotalMemory { get; set; }
|
||||
|
||||
/// <summary>托管堆碎片大小。单位:MB</summary>
|
||||
[DisplayName("托管堆碎片")]
|
||||
public UInt64 FragmentedBytes { get; set; }
|
||||
|
||||
/// <summary>GC识别可用内存。单位:MB</summary>
|
||||
[DisplayName("GC识别可用内存")]
|
||||
public UInt64 GCAvailableMemory { get; set; }
|
||||
|
||||
/// <summary>GC 已提交的内存。单位:MB</summary>
|
||||
[DisplayName("GC已提交内存")]
|
||||
public UInt64 CommittedBytes { get; set; }
|
||||
|
||||
/// <summary>GC 累计分配的托管内存。单位:MB</summary>
|
||||
[DisplayName("GC累计分配")]
|
||||
public UInt64 TotalAllocatedBytes { get; set; }
|
||||
/// <summary>GC 暂停累计时间。单位:毫秒</summary>
|
||||
[DisplayName("GC累计暂停时间")]
|
||||
public UInt64 TotalPauseDurationMs { get; set; }
|
||||
/// <summary>GC 代0收集次数</summary>
|
||||
[DisplayName("GC Gen0 次数")]
|
||||
public Int32 GcGen0Count { get; set; }
|
||||
|
||||
/// <summary>GC 代1收集次数</summary>
|
||||
[DisplayName("GC Gen1 次数")]
|
||||
public Int32 GcGen1Count { get; set; }
|
||||
|
||||
/// <summary>GC 代2收集次数</summary>
|
||||
[DisplayName("GC Gen2 次数")]
|
||||
public Int32 GcGen2Count { get; set; }
|
||||
|
||||
/// <summary>Server GC 是否启用</summary>
|
||||
[DisplayName("是否使用Server GC")]
|
||||
public Boolean IsServerGC { get; set; }
|
||||
|
||||
/// <summary>GC 延迟模式</summary>
|
||||
[DisplayName("GC延迟模式")]
|
||||
public GCLatencyMode? GCLatencyMode { get; set; }
|
||||
|
||||
/// <summary>GC 固定对象数</summary>
|
||||
[DisplayName("固定对象数")]
|
||||
public Int64 PinnedObjectsCount { get; set; }
|
||||
|
||||
/// <summary>终结队列挂起对象数</summary>
|
||||
[DisplayName("终结挂起数")]
|
||||
public Int64 FinalizationPendingCount { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 进程内存信息
|
||||
|
||||
/// <summary>进程虚拟内存使用量。单位:MB</summary>
|
||||
[DisplayName("虚拟内存")]
|
||||
public UInt64 VirtualMemory { get; set; }
|
||||
|
||||
/// <summary>进程私有内存使用量。单位:MB</summary>
|
||||
[DisplayName("私有内存")]
|
||||
public UInt64 PrivateMemory { get; set; }
|
||||
|
||||
/// <summary>进程峰值工作集。单位:MB</summary>
|
||||
[DisplayName("峰值工作集")]
|
||||
public UInt64 PeakWorkingSet { get; set; }
|
||||
|
||||
/// <summary>进程当前工作集。单位:MB</summary>
|
||||
[DisplayName("当前工作集")]
|
||||
public UInt64 WorkingSet { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public string UpdateTime { get; set; }
|
||||
public DateTime UpdateTime { get; set; }
|
||||
public ulong AppRunTotalMinute { get; set; }
|
||||
public ulong SystemRunTotalMinute { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,12 +8,10 @@
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
@@ -43,7 +41,7 @@ public class HardwareJob : IJob, IHardwareJob
|
||||
/// <summary>
|
||||
/// 运行信息获取
|
||||
/// </summary>
|
||||
public HardwareInfo HardwareInfo { get; } = new();
|
||||
public HardwareInfo HardwareInfo { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public HardwareInfoOptions HardwareInfoOptions { get; private set; }
|
||||
@@ -76,9 +74,10 @@ public class HardwareJob : IJob, IHardwareJob
|
||||
{
|
||||
try
|
||||
{
|
||||
if (HardwareInfo.MachineInfo == null)
|
||||
var machine = MachineInfo.GetCurrent();
|
||||
if (HardwareInfo == null)
|
||||
{
|
||||
HardwareInfo.MachineInfo = MachineInfo.GetCurrent();
|
||||
HardwareInfo=machine.AdaptHardwareInfo();
|
||||
|
||||
string currentPath = Directory.GetCurrentDirectory();
|
||||
DriveInfo drive = new(Path.GetPathRoot(currentPath));
|
||||
@@ -88,10 +87,9 @@ public class HardwareJob : IJob, IHardwareJob
|
||||
HardwareInfo.DriveInfo = drive;
|
||||
HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构
|
||||
HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架
|
||||
HardwareInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production";
|
||||
HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID;
|
||||
HardwareInfo.Environment = App.HostEnvironment.EnvironmentName;
|
||||
|
||||
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
|
||||
HardwareInfo.UpdateTime = TimerX.Now;
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -99,9 +97,12 @@ public class HardwareJob : IJob, IHardwareJob
|
||||
}
|
||||
try
|
||||
{
|
||||
HardwareInfo.MachineInfo.Refresh();
|
||||
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
|
||||
HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt();
|
||||
var machine = MachineInfo.GetCurrent();
|
||||
machine.Refresh();
|
||||
machine.AdaptHardwareInfo(HardwareInfo);
|
||||
HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 /60;
|
||||
HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 /60;
|
||||
HardwareInfo.UpdateTime = TimerX.Now;
|
||||
error = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -123,10 +124,10 @@ public class HardwareJob : IJob, IHardwareJob
|
||||
{
|
||||
Date = TimerX.Now,
|
||||
DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(),
|
||||
Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(),
|
||||
Battery = (HardwareInfo.Battery * 100).ToInt(),
|
||||
MemoryUsage = (HardwareInfo.WorkingSet),
|
||||
CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(),
|
||||
Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(),
|
||||
CpuUsage = (HardwareInfo.CpuRate * 100).ToInt(),
|
||||
Temperature = (HardwareInfo.Temperature).ToInt(),
|
||||
};
|
||||
await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
|
||||
MemoryCache.Remove(CacheKey);
|
||||
|
||||
@@ -21,7 +21,7 @@ public class HistoryHardwareInfo
|
||||
|
||||
/// <inheritdoc/>
|
||||
[SugarColumn(ColumnDescription = "内存")]
|
||||
public int MemoryUsage { get; set; }
|
||||
public UInt64 MemoryUsage { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[SugarColumn(ColumnDescription = "CPU使用率")]
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
|
||||
"UserNull": "User {0} does not exist"
|
||||
},
|
||||
|
||||
|
||||
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
|
||||
"UserExpire": "User expired, please login again"
|
||||
},
|
||||
@@ -46,12 +46,44 @@
|
||||
"FileTypeError": "Not supported format {0}"
|
||||
},
|
||||
"ThingsGateway.Admin.Application.HardwareInfo": {
|
||||
"Environment": "HostEnvironment",
|
||||
"FrameworkDescription": ".NETFramework",
|
||||
"OsArchitecture": "System Architecture",
|
||||
"UpdateTime": "UpdateTime",
|
||||
"UUID": "UUID"
|
||||
"DriveInfo": "Current Disk Info",
|
||||
"AppRunTotalMinute": "AppRunTotalMinute(min)",
|
||||
"SystemRunTotalMinute": "SystemRunTotalMinute(min)",
|
||||
"Environment": "Host Environment",
|
||||
"FrameworkDescription": ".NET Framework",
|
||||
"OsArchitecture": "OS Architecture",
|
||||
"OSName": "OS Name",
|
||||
"OSVersion": "OS Version",
|
||||
"UUID": "UUID",
|
||||
"Memory": "Total Memory",
|
||||
"AvailableMemory": "Available Memory",
|
||||
"CpuRate": "CPU Usage",
|
||||
"Battery": "Battery Level",
|
||||
"Temperature": "Temperature",
|
||||
"Processor": "Processor Model",
|
||||
"HighMemoryLoadThreshold": "GC High Memory Threshold",
|
||||
"TotalAvailableMemory": "GC Total Available Memory",
|
||||
"HeapSize": "Managed Heap Size",
|
||||
"TotalMemory": "Managed Heap Used",
|
||||
"FragmentedBytes": "Managed Heap Fragmented",
|
||||
"GCAvailableMemory": "GC Available Memory",
|
||||
"CommittedBytes": "GC Committed Bytes",
|
||||
"TotalAllocatedBytes": "GC Total Allocated (MB)",
|
||||
"TotalPauseDurationMs": "GC Total Pause Duration (ms)",
|
||||
"GcGen0Count": "GC Gen0 Count",
|
||||
"GcGen1Count": "GC Gen1 Count",
|
||||
"GcGen2Count": "GC Gen2 Count",
|
||||
"IsServerGC": "Server GC",
|
||||
"GCLatencyMode": "GC Latency Mode",
|
||||
"PinnedObjectsCount": "Pinned Objects Count",
|
||||
"FinalizationPendingCount": "Finalization Pending Count",
|
||||
"VirtualMemory": "Virtual Memory",
|
||||
"PrivateMemory": "Private Memory",
|
||||
"PeakWorkingSet": "Peak Working Set",
|
||||
"WorkingSet": "Current Working Set",
|
||||
"UpdateTime": "Update Time"
|
||||
},
|
||||
|
||||
"ThingsGateway.Admin.Application.HardwareJob": {
|
||||
"GetHardwareInfoFail": "Get Hardwareinfo Fail"
|
||||
},
|
||||
|
||||
@@ -46,11 +46,42 @@
|
||||
"FileTypeError": "不支持 {0} 格式"
|
||||
},
|
||||
"ThingsGateway.Admin.Application.HardwareInfo": {
|
||||
"DriveInfo": "当前磁盘信息",
|
||||
"AppRunTotalMinute": "软件运行时长(min)",
|
||||
"SystemRunTotalMinute": "系统运行时长(min)",
|
||||
"Environment": "主机环境",
|
||||
"FrameworkDescription": "NET框架",
|
||||
"FrameworkDescription": ".NET 框架",
|
||||
"OsArchitecture": "系统架构",
|
||||
"UpdateTime": "更新时间",
|
||||
"UUID": "唯一编码"
|
||||
"OSName": "系统名称",
|
||||
"OSVersion": "系统版本",
|
||||
"UUID": "UUID",
|
||||
"Memory": "系统总内存",
|
||||
"AvailableMemory": "系统可用内存",
|
||||
"CpuRate": "CPU 占用率",
|
||||
"Battery": "电池电量",
|
||||
"Temperature": "温度",
|
||||
"Processor": "处理器型号",
|
||||
"HighMemoryLoadThreshold": "GC 高内存阈值",
|
||||
"TotalAvailableMemory": "GC 总内存阈值",
|
||||
"HeapSize": "托管堆容量",
|
||||
"TotalMemory": "托管对象占用",
|
||||
"FragmentedBytes": "托管堆碎片",
|
||||
"GCAvailableMemory": "GC 可用内存",
|
||||
"CommittedBytes": "GC 提交内存总量",
|
||||
"TotalAllocatedBytes": "GC 累计分配(MB)",
|
||||
"TotalPauseDurationMs": "GC 累计暂停时间(ms)",
|
||||
"GcGen0Count": "GC Gen0 次数",
|
||||
"GcGen1Count": "GC Gen1 次数",
|
||||
"GcGen2Count": "GC Gen2 次数",
|
||||
"IsServerGC": "Server GC",
|
||||
"GCLatencyMode": "GC 延迟模式",
|
||||
"PinnedObjectsCount": "固定对象数",
|
||||
"FinalizationPendingCount": "终结挂起数",
|
||||
"VirtualMemory": "虚拟内存",
|
||||
"PrivateMemory": "私有内存",
|
||||
"PeakWorkingSet": "峰值工作集",
|
||||
"WorkingSet": "当前工作集",
|
||||
"UpdateTime": "更新时间"
|
||||
},
|
||||
"ThingsGateway.Admin.Application.HardwareJob": {
|
||||
"GetHardwareInfoFail": "获取硬件信息出错"
|
||||
|
||||
@@ -8,12 +8,20 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
|
||||
public static partial class AdminMapper
|
||||
{
|
||||
public static partial HardwareInfo AdaptHardwareInfo(this MachineInfo src);
|
||||
public static partial void AdaptHardwareInfo(this MachineInfo src, HardwareInfo dto);
|
||||
|
||||
public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src);
|
||||
public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);
|
||||
public static partial SessionOutput AdaptSessionOutput(this SysUser src);
|
||||
|
||||
@@ -145,7 +145,7 @@ public class AdminOAuthHandler<TOptions>(
|
||||
var loginEvent = new LoginEvent
|
||||
{
|
||||
Ip = appService.RemoteIpAddress,
|
||||
Device = appService.UserAgent?.Platform,
|
||||
Device = appService.UserAgent?.Platform ?? "Unknown",
|
||||
Expire = expire,
|
||||
SysUser = sysUser,
|
||||
VerificatId = CommonUtils.GetSingleId()
|
||||
@@ -156,7 +156,7 @@ public class AdminOAuthHandler<TOptions>(
|
||||
//生成verificat信息
|
||||
var verificatInfo = new VerificatInfo
|
||||
{
|
||||
Device = loginEvent.Device,
|
||||
Device = loginEvent.Device ?? "Unknown",
|
||||
Expire = loginEvent.Expire,
|
||||
VerificatTimeout = tokenTimeout,
|
||||
Id = loginEvent.VerificatId,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"Module": 2,
|
||||
"Title": "权限管理",
|
||||
"Code": "System",
|
||||
"NavLinkMatch": "All",
|
||||
"NavLinkMatch": "Prefix",
|
||||
"Category": "MENU",
|
||||
"Target": "_self",
|
||||
"Href": null,
|
||||
@@ -47,7 +47,7 @@
|
||||
"ParentId": 0,
|
||||
"Module": 2,
|
||||
"Title": "系统运维",
|
||||
"NavLinkMatch": "All",
|
||||
"NavLinkMatch": "Prefix",
|
||||
"Code": "System",
|
||||
"Category": "MENU",
|
||||
"Target": "_self",
|
||||
|
||||
@@ -235,7 +235,7 @@ public class AuthService : IAuthService
|
||||
var logingEvent = new LoginEvent
|
||||
{
|
||||
Ip = _appService.RemoteIpAddress,
|
||||
Device = _appService.UserAgent?.Platform,
|
||||
Device = _appService.UserAgent?.Platform ?? "Unknown",
|
||||
Expire = expire,
|
||||
SysUser = sysUser,
|
||||
VerificatId = verificatId
|
||||
@@ -344,7 +344,7 @@ public class AuthService : IAuthService
|
||||
//生成verificat信息
|
||||
var verificatInfo = new VerificatInfo
|
||||
{
|
||||
Device = loginEvent.Device,
|
||||
Device = loginEvent.Device ?? "Unknown",
|
||||
Expire = loginEvent.Expire,
|
||||
VerificatTimeout = tokenTimeout,
|
||||
Id = loginEvent.VerificatId,
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application;
|
||||
/// <typeparam name="TEntry"></typeparam>
|
||||
public class EventService<TEntry> : IEventService<TEntry>, IDisposable
|
||||
{
|
||||
private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new();
|
||||
private NonBlockingDictionary<string, Func<TEntry, Task>> Cache = new();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
internal sealed class NoticeService : INoticeService
|
||||
{
|
||||
private IEventService<AppMessage>? MessageDispatchService { get; set; }
|
||||
|
||||
@@ -282,7 +282,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
|
||||
if (sysRole != null)
|
||||
{
|
||||
var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false);
|
||||
var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu).Where(a => menuIds.Contains(a.Id));
|
||||
var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu && menuIds.Contains(a.Id));
|
||||
|
||||
#region 角色模块处理
|
||||
|
||||
|
||||
@@ -377,9 +377,9 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
/// 获取用户拥有的资源
|
||||
/// </summary>
|
||||
/// <param name="id">用户id</param>
|
||||
public async Task<GrantResourceData> OwnResourceAsync(long id)
|
||||
public Task<GrantResourceData> OwnResourceAsync(long id)
|
||||
{
|
||||
return await _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource).ConfigureAwait(false);
|
||||
return _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -505,10 +505,10 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
var password = await GetDefaultPassWord(true).ConfigureAwait(false);//获取默认密码,这里不走Aop所以需要加密一下
|
||||
using var db = GetDB();
|
||||
//重置密码
|
||||
if (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
if ((await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
{
|
||||
Password = password
|
||||
}, it => it.Id == id).ConfigureAwait(false))
|
||||
}, it => it.Id == id).ConfigureAwait(false)) > 0)
|
||||
{
|
||||
DeleteUserFromCache(id);//从cache删除用户信息
|
||||
var verificatInfoIds = _verificatInfoService.GetListByUserId(id);
|
||||
@@ -550,7 +550,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
if (sysUser != null)
|
||||
{
|
||||
var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false);
|
||||
var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu).Where(a => menuIds.Contains(a.Id));
|
||||
var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu && menuIds.Contains(a.Id));
|
||||
|
||||
#region 用户模块处理
|
||||
|
||||
|
||||
@@ -185,12 +185,12 @@ internal sealed class UserCenterService : BaseService<SysUser>, IUserCenterServi
|
||||
using var db = GetDB();
|
||||
|
||||
//更新指定字段
|
||||
var result = await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
var result = (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
{
|
||||
Email = input.Email,
|
||||
Phone = input.Phone,
|
||||
Avatar = input.Avatar,
|
||||
}, it => it.Id == UserManager.UserId).ConfigureAwait(false);
|
||||
}, it => it.Id == UserManager.UserId).ConfigureAwait(false)) > 0;
|
||||
if (result)
|
||||
_userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
|
||||
public void Add(VerificatInfo verificatInfo)
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Insertable<VerificatInfo>(verificatInfo).ExecuteCommand();
|
||||
db.InsertableT<VerificatInfo>(verificatInfo).ExecuteCommand();
|
||||
VerificatInfoService.RemoveCache(verificatInfo.Id);
|
||||
if (verificatInfo != null)
|
||||
VerificatInfoService.SetCahce(verificatInfo);
|
||||
@@ -132,7 +132,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
|
||||
public void Update(VerificatInfo verificatInfo)
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Updateable<VerificatInfo>(verificatInfo).ExecuteCommand();
|
||||
db.UpdateableT<VerificatInfo>(verificatInfo).ExecuteCommand();
|
||||
VerificatInfoService.RemoveCache(verificatInfo.Id);
|
||||
if (verificatInfo != null)
|
||||
VerificatInfoService.SetCahce(verificatInfo);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -19,19 +19,21 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
|
||||
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET9Version)" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="$(NET9Version)" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="$(NET9Version)" />
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET10Version)" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="$(NET10Version)" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="$(NET10Version)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Remove="SeedData\Admin\*.json" />
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
<div class="tg-table h-100">
|
||||
|
||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||
DataService="DataService" CreateItemCallback="CreateItemCallback!"
|
||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" BeforeShowEditDialogCallback="BeforeShowEditDialogCallback!"
|
||||
<Table Id=@Id TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||
DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode OnColumnCreating=OnColumnCreating
|
||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!"
|
||||
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
|
||||
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
|
||||
ShowEmpty="ShowEmpty" EmptyText="@EmptyText" EmptyImage="@($"{WebsiteConst.DefaultResourceUrl}images/empty.svg")" SortString="@SortString" EditDialogSize="EditDialogSize"
|
||||
@@ -14,7 +14,7 @@
|
||||
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
|
||||
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
|
||||
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
|
||||
ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView
|
||||
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView OnColumnVisibleChanged=OnColumnVisibleChanged
|
||||
FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
|
||||
IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
|
||||
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
|
||||
@@ -29,7 +29,7 @@
|
||||
ShowMultiFilterHeader=ShowMultiFilterHeader
|
||||
ShowFilterHeader=ShowFilterHeader
|
||||
ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
|
||||
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
|
||||
CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!"
|
||||
ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!"
|
||||
DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
|
||||
DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"
|
||||
@@ -41,6 +41,7 @@
|
||||
DoubleClickToEdit="DoubleClickToEdit"
|
||||
OnDoubleClickCellCallback="OnDoubleClickCellCallback"
|
||||
OnDoubleClickRowCallback="OnDoubleClickRowCallback"
|
||||
RowContentTemplate="RowContentTemplate"
|
||||
OnClickRowCallback="OnClickRowCallback">
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,41 @@ namespace ThingsGateway.Admin.Razor;
|
||||
[CascadingTypeParameter(nameof(TItem))]
|
||||
public partial class AdminTable<TItem> where TItem : class, new()
|
||||
{
|
||||
/// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/>
|
||||
[Parameter]
|
||||
public Func<string, bool, Task> OnColumnVisibleChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnColumnCreating"/>
|
||||
[Parameter]
|
||||
public Func<List<ITableColumn>, Task> OnColumnCreating { get; set; }
|
||||
/// <inheritdoc cref="Table{TItem}.RenderMode"/>
|
||||
[Parameter]
|
||||
public TableRenderMode RenderMode { get; set; }
|
||||
|
||||
public List<ITableColumn> Columns => Instance?.Columns;
|
||||
|
||||
public IEnumerable<ITableColumn> GetVisibleColumns => Instance?.GetVisibleColumns();
|
||||
public List<TItem> Rows => Instance?.Rows;
|
||||
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
|
||||
[Parameter]
|
||||
public List<TItem> SelectedRows { get; set; } = new();
|
||||
|
||||
private async Task privateSelectedRowsChanged(List<TItem> items)
|
||||
{
|
||||
SelectedRows = items;
|
||||
if (SelectedRowsChanged.HasDelegate)
|
||||
await SelectedRowsChanged.InvokeAsync(items);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DoubleClickToEdit"/>
|
||||
[Parameter]
|
||||
public bool DoubleClickToEdit { get; set; } = false;
|
||||
@@ -22,6 +57,10 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
/// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; }
|
||||
/// <inheritdoc cref="Table{TItem}.RowContentTemplate"/>
|
||||
[Parameter]
|
||||
public RenderFragment<TableRowContext<TItem>>? RowContentTemplate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? OnClickRowCallback { get; set; }
|
||||
@@ -128,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public IDataService<TItem> DataService { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.CreateItemCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem> CreateItemCallback { get; set; }
|
||||
@@ -210,14 +252,6 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? SearchTemplate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
|
||||
[Parameter]
|
||||
public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
|
||||
[Parameter]
|
||||
public Func<TItem, string?>? SetRowClassFormatter { get; set; }
|
||||
@@ -266,6 +300,15 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public bool ShowExportButton { get; set; } = false;
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableEditButtonCallback"/>
|
||||
public Func<List<TItem>, bool> DisableEditButtonCallback { get; set; } = (list) =>
|
||||
list.Count != 1;
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableDeleteButtonCallback"/>
|
||||
[Parameter]
|
||||
public Func<List<TItem>, bool> DisableDeleteButtonCallback { get; set; } = (list) =>
|
||||
list.Count <= 0;
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.ShowExportCsvButton"/>
|
||||
[Parameter]
|
||||
public bool ShowExportCsvButton { get; set; } = false;
|
||||
|
||||
@@ -125,13 +125,22 @@ public class BlazorAppContext
|
||||
var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
|
||||
OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
|
||||
AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList();
|
||||
OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem()
|
||||
OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item =>
|
||||
{
|
||||
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
|
||||
Text = item.Title,
|
||||
Icon = item.Icon,
|
||||
Url = item.Href,
|
||||
Target = item.Target.ToString(),
|
||||
var menu = new MenuItem()
|
||||
{
|
||||
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix,
|
||||
Text = item.Title,
|
||||
Icon = item.Icon,
|
||||
Url = item.Href,
|
||||
Target = item.Target.ToString(),
|
||||
};
|
||||
if (menu.Url.IsNullOrEmpty())
|
||||
{
|
||||
menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix;
|
||||
}
|
||||
return menu;
|
||||
|
||||
}).ToList();
|
||||
UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
"SetDefaultModule": "Set as default module"
|
||||
},
|
||||
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
||||
"GCMemoryInfoConfig": "GCMemoryInfoConfig(MB)",
|
||||
"GCMemoryInfo": "GCMemoryInfo(MB)",
|
||||
"GCAnalyzeInfo": "GCAnalyzeInfo",
|
||||
"AvailableDisk": "Available Disk",
|
||||
"AvailableMemory": "Available Memory",
|
||||
"CpuUsage": "CPU Usage",
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
"SetDefaultModule": "设置为默认模块"
|
||||
},
|
||||
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
||||
"GCMemoryInfoConfig": "GC配置信息(MB)",
|
||||
"GCMemoryInfo": "GC内存信息(MB)",
|
||||
"GCAnalyzeInfo": "GC统计",
|
||||
|
||||
"AvailableDisk": "可用磁盘",
|
||||
"AvailableMemory": "可用内存",
|
||||
"CpuUsage": "CPU使用率",
|
||||
|
||||
@@ -5,131 +5,189 @@
|
||||
@using ThingsGateway.Admin.Application
|
||||
@namespace ThingsGateway.Admin.Razor
|
||||
<div class="row g-2 mx-1 form-inline">
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-12">
|
||||
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["SystemInfo"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<EditorFormObject IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow="1" RowType=RowType.Inline LabelWidth="160">
|
||||
<FieldItems>
|
||||
<EditorItem @bind-Field="@context.MachineInfo">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-12">
|
||||
<Display @bind-Value="@value.MachineInfo.OSName" DisplayText="@Localizer[nameof(value.MachineInfo.OSName)]" ShowTooltip ShowLabel=true>
|
||||
</Display>
|
||||
</div>
|
||||
<div class="col-12 col-md-12">
|
||||
<Display @bind-Value="@value.MachineInfo.OSVersion" DisplayText="@Localizer[nameof(value.MachineInfo.OSVersion)]" ShowTooltip ShowLabel=true>
|
||||
</Display>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.UUID">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-12">
|
||||
<Display @bind-Value="@value.UUID" ShowTooltip ShowLabel=true>
|
||||
</Display>
|
||||
</div>
|
||||
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.DriveInfo" Ignore=true>
|
||||
</EditorItem>
|
||||
</FieldItems>
|
||||
</EditorFormObject>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
<div class="col-12 col-md-8">
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["HardwareInfo"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<div class="d-flex align-items-center justify-content-center w-100 h-100" >
|
||||
|
||||
<div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
|
||||
<div class="col-12 col-md-4 justify-content-center h-100" >
|
||||
@{
|
||||
var item = HardwareJob.HardwareInfo.MachineInfo.CpuRate;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(item*100<=100?item*100:100))
|
||||
Color=@((item*100>70?Color.Warning:Color.Success))
|
||||
StrokeWidth="4" ShowProgress=false>
|
||||
<div class="circle-hardware">
|
||||
<span>
|
||||
@Localizer["CpuUsage"] <i> @((item * 100).ToString("F0") + " %")</i>
|
||||
</span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@(HardwareJob.HardwareInfo.MachineInfo.Processor)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var availableMemory = HardwareJob.HardwareInfo.MachineInfo.AvailableMemory;
|
||||
var memory = HardwareJob.HardwareInfo.MachineInfo.Memory;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center ">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
|
||||
Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6> @((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %") </h6>
|
||||
|
||||
<span> @Localizer["WorkingSet"] <i> @(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
|
||||
<span> @Localizer["AvailableMemory"] <i> @((availableMemory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
|
||||
<span> @Localizer["TotalMemory"] <i> @((memory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
|
||||
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span> @Localizer["MemoryUsage"] </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
|
||||
var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center ">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
|
||||
Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6> @((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
|
||||
<span> @(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel) </span>
|
||||
<span> @Localizer["AvailableDisk"] <i> @((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
<span> @Localizer["TotalDisk"] <i> @((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span> @Localizer["DiskUsage"] </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center w-100">
|
||||
<span>@Localizer["SystemInfo"]</span>
|
||||
<small class="text-muted">
|
||||
@HardwareJob.HardwareInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
</small>
|
||||
</div>
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<EditorForm class="mt-3" AutoGenerateAllItem="false" IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow=3 RowType=RowType.Inline LabelWidth=300>
|
||||
<FieldItems>
|
||||
|
||||
<EditorItem @bind-Field="@context.OSName" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.OsArchitecture" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.OSVersion" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.Environment" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.FrameworkDescription" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.UUID" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.SystemRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.AppRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.Memory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
<EditorItem @bind-Field="@context.TotalAvailableMemory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
<EditorItem @bind-Field="@context.HighMemoryLoadThreshold" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2 />
|
||||
|
||||
<EditorItem TValue="bool" TModel="HardwareInfo" @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
<EditorItem @bind-Field="@context.GCLatencyMode" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.WorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.PrivateMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.PeakWorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.TotalMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.FragmentedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.CommittedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
|
||||
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.GCAvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.VirtualMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.GcGen0Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem @bind-Field="@context.GcGen1Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem @bind-Field="@context.GcGen2Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem @bind-Field="@context.TotalPauseDurationMs" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
|
||||
</FieldItems>
|
||||
</EditorForm>
|
||||
|
||||
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-12">
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["HardwareInfo"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<div class="d-flex align-items-center justify-content-center w-100 h-100">
|
||||
<div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
|
||||
<!-- ✅ CPU 使用率 -->
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var cpuUsage = HardwareJob.HardwareInfo.CpuRate;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(cpuUsage*100<=100?cpuUsage*100:100))
|
||||
Color=@((cpuUsage*100>70?Color.Warning:Color.Success))
|
||||
StrokeWidth="4" ShowProgress=false>
|
||||
<div class="circle-hardware">
|
||||
<span>
|
||||
@Localizer["CpuUsage"] <i>@((cpuUsage * 100).ToString("F0") + " %")</i>
|
||||
</span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@(HardwareJob.HardwareInfo.Processor)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ 内存使用率 -->
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var availableMemory = HardwareJob.HardwareInfo.AvailableMemory;
|
||||
var memory = HardwareJob.HardwareInfo.Memory;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
|
||||
Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6>@((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")</h6>
|
||||
<span>@Localizer["WorkingSet"] <i>@(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
|
||||
<span>@Localizer["AvailableMemory"] <i>@((availableMemory / 1024.00).ToString("F2") + " GB")</i></span>
|
||||
<span>@Localizer["TotalMemory"] <i>@((memory / 1024.00).ToString("F2") + " GB")</i></span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@Localizer["MemoryUsage"]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ 磁盘使用率 -->
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
|
||||
var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
|
||||
Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6>@((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
|
||||
<span>@(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel)</span>
|
||||
<span>@Localizer["AvailableDisk"] <i>@((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
<span>@Localizer["TotalDisk"] <i>@((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@Localizer["DiskUsage"]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-2 mx-1 form-inline">
|
||||
|
||||
<div class="col-12 col-md-12">
|
||||
@@ -140,7 +198,7 @@
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" />
|
||||
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="() => { chartInit = true; return Task.CompletedTask; }" />
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable
|
||||
ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)];
|
||||
ChartDataSource.Options.X.Title = Localizer["DateTime"];
|
||||
ChartDataSource.Options.Y.Title = Localizer["Data"];
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz"));
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm"));
|
||||
ChartDataSource.Data.Add(new ChartDataset()
|
||||
{
|
||||
Tension = 0.4f,
|
||||
@@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable
|
||||
else
|
||||
{
|
||||
var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos();
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz"));
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm"));
|
||||
ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage);
|
||||
ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage);
|
||||
ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-sm-10 h-100">
|
||||
<div class="col-12 col-sm-10 h-100 ps-2">
|
||||
<AdminTable @ref=table TItem="SysUser"
|
||||
AutoGenerateColumns="true"
|
||||
ShowAdvancedSearch=false
|
||||
|
||||
@@ -20,5 +20,6 @@ public class Startup : AppStartup
|
||||
services.AddScoped<IMenuService, MenuService>();
|
||||
services.AddScoped<IAuthRazorService, AuthRazorService>();
|
||||
services.AddBootstrapBlazorTableExportService();
|
||||
services.AddBootstrapBlazorWinBoxService();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,21 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.4" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
||||
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
|
||||
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET8Version)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net9.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET9Version)" />
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net10.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET10Version)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
|
||||
|
||||
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -41,15 +41,22 @@ public static class AdminResourceUtil
|
||||
return items
|
||||
.Where(it => it.ParentId == parentId)
|
||||
.Select((item, index) =>
|
||||
new MenuItem()
|
||||
{
|
||||
var menu = new MenuItem()
|
||||
{
|
||||
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
|
||||
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix,
|
||||
Text = item.Title,
|
||||
Icon = item.Icon,
|
||||
Url = item.Href,
|
||||
Target = item.Target.ToString(),
|
||||
Items = BuildMenuTrees(items, item.Id).ToList()
|
||||
};
|
||||
if (menu.Url.IsNullOrEmpty())
|
||||
{
|
||||
menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix;
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<Target Name="AdminPostPublish" AfterTargets="Publish">
|
||||
<ItemGroup>
|
||||
<!-- setting up the variable for convenience -->
|
||||
<AdminFiles Include="bin\$(Configuration)\$(TargetFramework)\SeedData\**" />
|
||||
<AdminFiles Include="$(OutputPath)\$(TargetFramework)\SeedData\**" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
<link rel="apple-touch-icon" href="favicon.png">
|
||||
<base href="/" />
|
||||
<title>ThingsGateway</title>
|
||||
<link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css") />
|
||||
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css") />
|
||||
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") />
|
||||
<link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css") />
|
||||
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css") />
|
||||
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css") />
|
||||
|
||||
@* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
@@ -38,12 +40,13 @@
|
||||
|
||||
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
|
||||
|
||||
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script>
|
||||
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script>
|
||||
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js")></script>
|
||||
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<!-- PWA Service Worker -->
|
||||
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>
|
||||
|
||||
<script src="pwa-install.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
|
||||
}
|
||||
@* 版本号 *@
|
||||
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
|
||||
<div class="px-1 navbar-header-text text-nowrap d-none d-lg-block">@_versionString</div>
|
||||
|
||||
@* 主题切换 *@
|
||||
@* <ThemeToggle /> *@
|
||||
@@ -89,12 +89,19 @@
|
||||
</div>
|
||||
</Side>
|
||||
<Main>
|
||||
<Tab @ref=_tab ClickTabToNavigation="true" ShowToolbar="true" ShowContextMenu="true" ShowContextMenuFullScreen="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
|
||||
AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems"
|
||||
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
|
||||
{
|
||||
return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
|
||||
})>
|
||||
<Tab @ref=_tab ClickTabToNavigation="true" ShowToolbar="true" ShowContextMenu="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
|
||||
ShowFullscreenToolbarButton=false ShowContextMenuFullScreen=false ShowFullScreen=false AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems"
|
||||
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
|
||||
{
|
||||
return Task.FromResult(!(a.Url == "/" || a.Url.IsNullOrEmpty()));
|
||||
})
|
||||
>
|
||||
<BeforeContextMenuTemplate>
|
||||
|
||||
<ContextMenuItem Icon="fa fa-window-restore" Text="@Localizer["WindowRestore"]" OnClick="WinboxRender"></ContextMenuItem>
|
||||
<ContextMenuDivider></ContextMenuDivider>
|
||||
|
||||
</BeforeContextMenuTemplate>
|
||||
</Tab>
|
||||
</Main>
|
||||
<NotAuthorized>
|
||||
|
||||
@@ -120,6 +120,38 @@ public partial class MainLayout : IDisposable
|
||||
|
||||
#endregion 注销
|
||||
|
||||
|
||||
private async Task WinboxRender(ContextMenuItem item, object? context)
|
||||
{
|
||||
if (context is TabItem tabItem)
|
||||
{
|
||||
await WinboxRender(tabItem.ChildContent, tabItem.Text);
|
||||
//await _tab.RemoveTab(tabItem);
|
||||
}
|
||||
}
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private WinBoxService? WinBoxService { get; set; }
|
||||
|
||||
private async Task WinboxRender(RenderFragment item, string title)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
var option = new WinBoxOption()
|
||||
{
|
||||
Title = title,
|
||||
ContentTemplate = item,
|
||||
Max = false,
|
||||
Width = "80%",
|
||||
Height = "80%",
|
||||
Top = "0%",
|
||||
Left = "10%",
|
||||
Background = "var(--bb-primary-color)",
|
||||
Overflow = true
|
||||
};
|
||||
await WinBoxService.Show(option);
|
||||
}
|
||||
}
|
||||
private string _versionString = string.Empty;
|
||||
[Inject]
|
||||
[NotNull]
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Text;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.DB;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Log;
|
||||
|
||||
namespace ThingsGateway.AdminServer;
|
||||
@@ -64,7 +65,7 @@ public class Program
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
builder.Host.UseSystemd();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (Runtime.IsLegacyWindows)
|
||||
builder.Logging.ClearProviders(); //去除默认的事件日志提供者,某些情况下会日志输出异常,导致程序崩溃
|
||||
}).ConfigureBuilder(builder =>
|
||||
{
|
||||
|
||||
@@ -92,7 +92,8 @@ public class Startup : AppStartup
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
@@ -103,6 +104,7 @@ public class Startup : AppStartup
|
||||
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
});
|
||||
|
||||
#else
|
||||
@@ -112,7 +114,8 @@ public class Startup : AppStartup
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
}).AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
@@ -132,7 +135,11 @@ public class Startup : AppStartup
|
||||
services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.All;
|
||||
#if NET10_0_OR_GREATER
|
||||
options.KnownIPNetworks.Clear();
|
||||
#else
|
||||
options.KnownNetworks.Clear();
|
||||
#endif
|
||||
options.KnownProxies.Clear();
|
||||
});
|
||||
|
||||
@@ -183,25 +190,37 @@ public class Startup : AppStartup
|
||||
services.AddScoped<IAuthorizationHandler, BlazorServerAuthenticationHandler>();
|
||||
services.AddScoped<AuthenticationStateProvider, BlazorServerAuthenticationStateProvider>();
|
||||
|
||||
if (!NewLife.Runtime.IsLegacyWindows)
|
||||
{
|
||||
#if NET9_0_OR_GREATER
|
||||
var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
#else
|
||||
var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
#endif
|
||||
services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo("Keys"))
|
||||
.ProtectKeysWithCertificate(certificate)
|
||||
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
|
||||
{
|
||||
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
|
||||
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
|
||||
});
|
||||
services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo("Keys"))
|
||||
.ProtectKeysWithCertificate(certificate)
|
||||
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
|
||||
{
|
||||
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
|
||||
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env)
|
||||
{
|
||||
var app = (WebApplication)applicationBuilder;
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All, KnownNetworks = { }, KnownProxies = { } });
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.All,
|
||||
#if NET10_0_OR_GREATER
|
||||
KnownIPNetworks = { },
|
||||
#else
|
||||
KnownNetworks = { },
|
||||
#endif
|
||||
KnownProxies = { }
|
||||
});
|
||||
app.UseBootstrapBlazor();
|
||||
|
||||
// 启用本地化
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -15,15 +15,11 @@
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
<CETCompat>false</CETCompat>
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -53,9 +49,9 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET9Version)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET9Version)" />
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET10Version)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET10Version)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
34
src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
Normal file
34
src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
Normal file
@@ -0,0 +1,34 @@
|
||||
let installPromptTriggered = false;
|
||||
|
||||
function getCookie(name) {
|
||||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
||||
return match ? match[2] : null;
|
||||
}
|
||||
|
||||
function hasShownInstallPrompt() {
|
||||
return getCookie("tgPWAInstallPromptShown") === "true";
|
||||
}
|
||||
|
||||
function markInstallPromptShown() {
|
||||
document.cookie = "tgPWAInstallPromptShown=true; max-age=31536000; path=/";
|
||||
}
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!hasShownInstallPrompt() && !installPromptTriggered) {
|
||||
installPromptTriggered = true;
|
||||
setTimeout(() => {
|
||||
e.prompt()
|
||||
.then(() => e.userChoice)
|
||||
.then(choiceResult => {
|
||||
markInstallPromptShown();
|
||||
})
|
||||
.catch(err => {
|
||||
// 可选错误处理
|
||||
});
|
||||
}, 2000); // 延迟 2 秒提示
|
||||
} else {
|
||||
// console.log("已提示过安装,不再弹出");
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -477,7 +477,7 @@ public class ConcurrentList<T> : IList<T>, IReadOnlyList<T>
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.IndexOf(item);
|
||||
return m_list.LastIndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,27 @@ public class ImportPreviewOutputBase
|
||||
/// <summary>
|
||||
/// 返回状态
|
||||
/// </summary>
|
||||
public ConcurrentList<(int Row, bool Success, string? ErrorMessage)> Results { get; set; } = new();
|
||||
public ConcurrentList<ImportPreviewResult> Results { get; set; } = new();
|
||||
}
|
||||
public class ImportPreviewResult
|
||||
{
|
||||
public ImportPreviewResult()
|
||||
{
|
||||
|
||||
}
|
||||
public ImportPreviewResult(int row, bool success, string error)
|
||||
{
|
||||
this.Row = row;
|
||||
this.Success = success;
|
||||
this.ErrorMessage = error;
|
||||
}
|
||||
|
||||
public int Row { get; set; }
|
||||
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 导入预览
|
||||
/// </summary>
|
||||
|
||||
@@ -31,8 +31,8 @@ public static class GenericExtensions
|
||||
|
||||
// 比较oldModel和model的属性,找出差异
|
||||
var differences = properties
|
||||
.Where(prop => prop.CanRead && prop.CanWrite) // 确保属性可读可写
|
||||
.Where(prop => !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 找出值不同的属性
|
||||
.Where(prop => prop.CanRead && prop.CanWrite && !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 确保属性可读可写
|
||||
// 找出值不同的属性
|
||||
.ToDictionary(prop => prop.Name, prop => prop.GetValue(model)); // 将属性名和新值存储到字典中
|
||||
|
||||
// 应用差异到channels列表中的每个Channel对象
|
||||
|
||||
@@ -16,6 +16,8 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
|
||||
namespace ThingsGateway.Common.Extension;
|
||||
/// <summary>
|
||||
/// 对象拓展类
|
||||
@@ -48,113 +50,7 @@ public static class ObjectExtensions
|
||||
bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTimeOffset 转换成本地 DateTime
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertToDateTime(this DateTimeOffset dateTime)
|
||||
{
|
||||
if (dateTime.Offset.Equals(TimeSpan.Zero))
|
||||
return dateTime.UtcDateTime;
|
||||
if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime)))
|
||||
return dateTime.ToLocalTime().DateTime;
|
||||
else
|
||||
return dateTime.DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTimeOffset? 转换成本地 DateTime?
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime)
|
||||
{
|
||||
return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTime 转换成 DateTimeOffset
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime)
|
||||
{
|
||||
return DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTime? 转换成 DateTimeOffset?
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime)
|
||||
{
|
||||
return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将流保存到本地磁盘
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static void CopyToSave(this Stream stream, string path)
|
||||
{
|
||||
// 空检查
|
||||
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path));
|
||||
|
||||
using var fileStream = File.Create(path);
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组保存到本地磁盘
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static void CopyToSave(this byte[] bytes, string path)
|
||||
{
|
||||
using var stream = new MemoryStream(bytes);
|
||||
stream.CopyToSave(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将流保存到本地磁盘
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="path">需包含文件名完整路径</param>
|
||||
/// <returns></returns>
|
||||
public static async Task CopyToSaveAsync(this Stream stream, string path)
|
||||
{
|
||||
// 空检查
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
// 文件名判断
|
||||
if (string.IsNullOrWhiteSpace(Path.GetFileName(path)))
|
||||
{
|
||||
throw new ArgumentException("The parameter of <path> parameter must include the complete file name.");
|
||||
}
|
||||
|
||||
using var fileStream = File.Create(path);
|
||||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组保存到本地磁盘
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task CopyToSaveAsync(this byte[] bytes, string path)
|
||||
{
|
||||
using var stream = new MemoryStream(bytes);
|
||||
await stream.CopyToSaveAsync(path).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并两个字典
|
||||
@@ -186,7 +82,7 @@ public static class ObjectExtensions
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="dic">字典</param>
|
||||
/// <param name="newDic">新字典</param>
|
||||
internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
{
|
||||
foreach (var (key, value) in newDic)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class ParallelExtensions
|
||||
/// <typeparam name="T">集合元素类型</typeparam>
|
||||
/// <param name="source">要操作的集合</param>
|
||||
/// <param name="body">要执行的操作</param>
|
||||
public static void ParallelForEach<T>(this IList<T> source, Action<T> body)
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body)
|
||||
{
|
||||
ParallelOptions options = new();
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
|
||||
@@ -38,7 +38,7 @@ public static class ParallelExtensions
|
||||
/// <typeparam name="T">集合元素类型</typeparam>
|
||||
/// <param name="source">要操作的集合</param>
|
||||
/// <param name="body">要执行的操作</param>
|
||||
public static void ParallelForEach<T>(this IList<T> source, Action<T, ParallelLoopState, long> body)
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T, ParallelLoopState, long> body)
|
||||
{
|
||||
ParallelOptions options = new();
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
|
||||
@@ -53,7 +53,7 @@ public static class ParallelExtensions
|
||||
/// <param name="source">要操作的集合</param>
|
||||
/// <param name="body">要执行的操作</param>
|
||||
/// <param name="parallelCount">最大并行度</param>
|
||||
public static void ParallelForEach<T>(this IList<T> source, Action<T> body, int parallelCount)
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body, int parallelCount)
|
||||
{
|
||||
// 创建并行操作的选项对象,设置最大并行度为指定的值
|
||||
var options = new ParallelOptions();
|
||||
@@ -109,7 +109,7 @@ public static class ParallelExtensions
|
||||
/// <param name="parallelCount">最大并行度</param>
|
||||
/// <param name="cancellationToken">取消操作的标志</param>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default)
|
||||
public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 创建并行操作的选项对象,设置最大并行度和取消标志
|
||||
var options = new ParallelOptions();
|
||||
@@ -126,7 +126,7 @@ public static class ParallelExtensions
|
||||
/// <param name="body">异步执行的操作</param>
|
||||
/// <param name="cancellationToken">取消操作的标志</param>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default)
|
||||
public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return ParallelForEachAsync(source, body, Environment.ProcessorCount, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ internal class CacheManager
|
||||
{
|
||||
private IMemoryCache Cache { get; set; }
|
||||
|
||||
private IServiceProvider Provider { get; set; }
|
||||
private static IServiceProvider Provider => App.RootServices;
|
||||
|
||||
[NotNull]
|
||||
private static CacheManager? Instance { get; set; }
|
||||
@@ -40,8 +40,7 @@ internal class CacheManager
|
||||
static CacheManager()
|
||||
{
|
||||
Instance = new();
|
||||
Instance.Provider = App.RootServices;
|
||||
Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>();
|
||||
Instance.Cache = Provider.GetRequiredService<IMemoryCache>();
|
||||
Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value;
|
||||
}
|
||||
|
||||
@@ -105,37 +104,6 @@ internal class CacheManager
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 App 开始时间
|
||||
/// </summary>
|
||||
public void SetStartTime() => SetStartTime(DateTimeOffset.Now);
|
||||
|
||||
/// <summary>
|
||||
/// 设置 App 开始时间
|
||||
/// </summary>
|
||||
private void SetStartTime(DateTimeOffset startDateTimeOffset)
|
||||
{
|
||||
GetOrCreate("BootstrapBlazor_StartTime", entry =>
|
||||
{
|
||||
entry.Priority = CacheItemPriority.NeverRemove;
|
||||
return startDateTimeOffset;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 App 开始时间
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DateTimeOffset GetStartTime()
|
||||
{
|
||||
var ret = DateTimeOffset.MinValue;
|
||||
if (Cache.TryGetValue("BootstrapBlazor_StartTime", out var v) && v is DateTimeOffset d)
|
||||
{
|
||||
ret = d;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得 缓存数量
|
||||
/// </summary>
|
||||
@@ -236,7 +204,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic
|
||||
? null
|
||||
: Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
||||
: Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 <see cref="JsonLocalizationOptions"/> 值
|
||||
@@ -244,7 +212,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
private static JsonLocalizationOptions GetJsonLocalizationOption()
|
||||
{
|
||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
||||
var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
||||
return localizationOptions.Value;
|
||||
}
|
||||
/// <summary>
|
||||
@@ -253,7 +221,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
private static BootstrapBlazorOptions GetBootstrapBlazorOption()
|
||||
{
|
||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
||||
var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
||||
return localizationOptions.Value;
|
||||
}
|
||||
/// <summary>
|
||||
@@ -269,7 +237,7 @@ internal class CacheManager
|
||||
return null;
|
||||
}
|
||||
IStringLocalizer? ret = null;
|
||||
var factories = Instance.Provider.GetServices<IStringLocalizerFactory>();
|
||||
var factories = Provider.GetServices<IStringLocalizerFactory>();
|
||||
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
|
||||
if (factory != null)
|
||||
{
|
||||
@@ -345,7 +313,7 @@ internal class CacheManager
|
||||
/// <param name="typeName"></param>
|
||||
/// <param name="includeParentCultures"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
||||
public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
||||
#endregion
|
||||
|
||||
#region DisplayName
|
||||
|
||||
@@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (Logger?.IsEnabled(LogLevel.Error) == true)
|
||||
Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -176,7 +177,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (jsonLocalizationOptions.IgnoreLocalizerMissing == false)
|
||||
{
|
||||
Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (Logger?.IsEnabled(LogLevel.Information) == true)
|
||||
Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
}
|
||||
_missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}");
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.9.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -27,10 +27,15 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
|
||||
public virtual long Id { get; set; }
|
||||
}
|
||||
|
||||
public interface IPrimaryKeyEntity
|
||||
{
|
||||
string ExtJson { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主键实体基类
|
||||
/// </summary>
|
||||
public abstract class PrimaryKeyEntity : PrimaryIdEntity
|
||||
public abstract class PrimaryKeyEntity : PrimaryIdEntity, IPrimaryKeyEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 拓展信息
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace ThingsGateway.DB;
|
||||
|
||||
@@ -41,4 +42,31 @@ public static class FileExtensions
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
/// <summary>
|
||||
/// 存储本地文件
|
||||
/// </summary>
|
||||
/// <param name="pPath">存储的第一层目录</param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns>文件全路径</returns>
|
||||
public static async Task<string> StorageLocal(this IFormFile file, string pPath = "imports")
|
||||
{
|
||||
string uploadFileFolder = App.WebHostEnvironment?.WebRootPath ?? "wwwroot"!;//赋值路径
|
||||
var now = CommonUtils.GetSingleId();
|
||||
var filePath = Path.Combine(uploadFileFolder, pPath);
|
||||
if (!Directory.Exists(filePath))//如果不存在就创建文件夹
|
||||
Directory.CreateDirectory(filePath);
|
||||
//var fileSuffix = Path.GetExtension(file.Name).ToLower();// 文件后缀
|
||||
var fileObjectName = $"{now}{file.Name}";//存储后的文件名
|
||||
var fileName = Path.Combine(filePath, fileObjectName);//获取文件全路径
|
||||
fileName = fileName.Replace("\\", "/");//格式化一系
|
||||
//存储文件
|
||||
using (var stream = File.Create(Path.Combine(filePath, fileObjectName)))
|
||||
{
|
||||
await file.CopyToAsync(stream).ConfigureAwait(false);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -209,16 +209,10 @@ public static class SqlSugarExtensions
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new()
|
||||
public static Task<int> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
|
||||
{
|
||||
return await db.Updateable(updateObjs).ExecuteCommandAsync().ConfigureAwait(false) > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
|
||||
{
|
||||
return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
|
||||
.ExecuteCommandAsync().ConfigureAwait(false) > 0;
|
||||
return db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
private static IEnumerable<T> Sort<T>(this IEnumerable<T> list, BasePageInput basePageInput)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
https://gitee.com/dotnetchina/Furion/commit/8bf85f6908c1630268e45eeec607267a03947d2b
|
||||
https://gitee.com/dotnetchina/Furion/commit/f1c07d65cccb623aca9d1905bf2e1ac6e4f4b714
|
||||
@@ -12,7 +12,7 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
using ThingsGateway;
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Reflection;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting;
|
||||
@@ -44,7 +44,7 @@ public static class HostBuilderExtensions
|
||||
|
||||
hostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, combineAssembliesName);
|
||||
|
||||
// 实现假的 Starup,解决泛型主机启动问题
|
||||
// 实现假的 Startup,解决泛型主机启动问题
|
||||
hostBuilder.UseStartup<FakeStartup>();
|
||||
return hostBuilder;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway.Extensions;
|
||||
namespace ThingsGateway.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// 对象拓展类
|
||||
@@ -28,70 +28,10 @@ namespace ThingsGateway.Extensions;
|
||||
[SuppressSniffer]
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 DateTimeOffset 转换成本地 DateTime
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertToDateTime(this DateTimeOffset dateTime)
|
||||
{
|
||||
if (dateTime.Offset.Equals(TimeSpan.Zero))
|
||||
return dateTime.UtcDateTime;
|
||||
if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime)))
|
||||
return dateTime.ToLocalTime().DateTime;
|
||||
else
|
||||
return dateTime.DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTimeOffset? 转换成本地 DateTime?
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime)
|
||||
{
|
||||
return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTime 转换成 DateTimeOffset
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime)
|
||||
{
|
||||
return DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTime? 转换成 DateTimeOffset?
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime)
|
||||
{
|
||||
return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将时间戳转换为 DateTime
|
||||
/// </summary>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
internal static DateTime ConvertToDateTime(this long timestamp)
|
||||
{
|
||||
var timeStampDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
var digitCount = (int)Math.Floor(Math.Log10(timestamp) + 1);
|
||||
|
||||
if (digitCount != 13 && digitCount != 10)
|
||||
{
|
||||
throw new ArgumentException("Data is not a valid timestamp format.");
|
||||
}
|
||||
|
||||
return (digitCount == 13
|
||||
? timeStampDateTime.AddMilliseconds(timestamp) // 13 位时间戳
|
||||
: timeStampDateTime.AddSeconds(timestamp)).ToLocalTime(); // 10 位时间戳
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 IFormFile 转换成 byte[]
|
||||
@@ -265,7 +205,7 @@ public static class ObjectExtensions
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="dic">字典</param>
|
||||
/// <param name="newDic">新字典</param>
|
||||
internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
{
|
||||
foreach (var (key, value) in newDic)
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ public class StartupFilter : IStartupFilter
|
||||
else
|
||||
{
|
||||
// 输出当前环境标识
|
||||
context.Response.Headers["environment"] = envName;
|
||||
context.Response.Headers["Environment"] = envName;
|
||||
|
||||
// 输出框架版本
|
||||
context.Response.Headers[nameof(ThingsGateway)] = version;
|
||||
|
||||
@@ -215,6 +215,10 @@ internal static class InternalApp
|
||||
// 获取环境变量名,如果没找到,则读取 NETCORE_ENVIRONMENT 环境变量信息识别(用于非 Web 环境)
|
||||
var envName = hostEnvironment?.EnvironmentName ?? Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "Unknown";
|
||||
|
||||
// 获取 JSON 文件扫描配置(2025.07.25),修复 docker 中挂载大文件数据卷导致启动缓慢的问题
|
||||
var jsonFileScanner = configuration.GetSection("AppSettings:JsonFileScanner")
|
||||
.Get<JsonFileScanner>() ?? new JsonFileScanner();
|
||||
|
||||
// 读取忽略的配置文件
|
||||
var ignoreConfigurationFiles = (configuration.GetSection("IgnoreConfigurationFiles")
|
||||
.Get<string[]>()
|
||||
@@ -240,7 +244,7 @@ internal static class InternalApp
|
||||
// 循环加载
|
||||
foreach (var jsonFile in files)
|
||||
{
|
||||
configurationBuilder.AddJsonFile(jsonFile, optional: true, reloadOnChange: true);
|
||||
configurationBuilder.AddJsonFile(jsonFile, optional: jsonFileScanner.Optional, reloadOnChange: jsonFileScanner.ReloadOnChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,10 @@ public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions
|
||||
/// 【部署】二级虚拟目录
|
||||
/// </summary>
|
||||
public string VirtualPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// JSON 文件扫描配置
|
||||
/// </summary>
|
||||
public JsonFileScanner JsonFileScanner { get; set; }
|
||||
/// <summary>
|
||||
/// 后期配置
|
||||
/// </summary>
|
||||
@@ -67,3 +70,20 @@ public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions
|
||||
options.VirtualPath ??= string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON 文件扫描配置
|
||||
/// </summary>
|
||||
/// <remarks>修复 docker 中挂载大文件数据卷导致启动缓慢的问题。</remarks>
|
||||
public class JsonFileScanner
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否可选
|
||||
/// </summary>
|
||||
public bool Optional { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否改变的时候重载
|
||||
/// </summary>
|
||||
public bool ReloadOnChange { get; set; } = true;
|
||||
}
|
||||
@@ -701,7 +701,7 @@ public static class Serve
|
||||
var applicationPartManager = app.Services.GetService<ApplicationPartManager>();
|
||||
|
||||
applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name));
|
||||
// 配置所有 Starup Configure
|
||||
// 配置所有 Startup Configure
|
||||
UseStartups(app);
|
||||
UseStartups(app.Services);
|
||||
|
||||
@@ -820,7 +820,7 @@ public static class Serve
|
||||
var applicationPartManager = app.Services.GetService<ApplicationPartManager>();
|
||||
|
||||
applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name));
|
||||
// 配置所有 Starup Configure
|
||||
// 配置所有 Startup Configure
|
||||
UseStartups(app.Services);
|
||||
// 释放内存
|
||||
App.AppStartups.Clear();
|
||||
@@ -943,7 +943,7 @@ public static class Serve
|
||||
var applicationPartManager = app.Services.GetService<ApplicationPartManager>();
|
||||
|
||||
applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name));
|
||||
// 配置所有 Starup Configure
|
||||
// 配置所有 Startup Configure
|
||||
UseStartups(app.Services);
|
||||
// 释放内存
|
||||
App.AppStartups.Clear();
|
||||
@@ -1005,7 +1005,7 @@ public static class Serve
|
||||
var applicationPartManager = app.Services.GetService<ApplicationPartManager>();
|
||||
applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name));
|
||||
|
||||
// 配置所有 Starup Configure
|
||||
// 配置所有 Startup Configure
|
||||
UseStartups(app.Services);
|
||||
// 释放内存
|
||||
App.AppStartups.Clear();
|
||||
|
||||
@@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions
|
||||
/// <param name="mvcBuilder"></param>
|
||||
/// <param name="configure"></param>
|
||||
/// <returns></returns>
|
||||
public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<ConcurrentDictionary<Type, Type>> configure = default)
|
||||
public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<NonBlockingDictionary<Type, Type>> configure = default)
|
||||
{
|
||||
mvcBuilder.Services.AddFromConvertBinding(configure);
|
||||
|
||||
@@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configure"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<ConcurrentDictionary<Type, Type>> configure = default)
|
||||
public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<NonBlockingDictionary<Type, Type>> configure = default)
|
||||
{
|
||||
// 非 Web 环境跳过注册
|
||||
if (App.WebHostEnvironment == default) return services;
|
||||
|
||||
// 定义模型绑定转换器集合
|
||||
var modelBinderConverts = new ConcurrentDictionary<Type, Type>();
|
||||
var modelBinderConverts = new NonBlockingDictionary<Type, Type>();
|
||||
modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder));
|
||||
modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
|
||||
namespace ThingsGateway.AspNetCore;
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder
|
||||
/// <summary>
|
||||
/// 定义模型绑定转换器集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts;
|
||||
private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
|
||||
public FromConvertBinder(ConcurrentDictionary<Type, Type> modelBinderConverts)
|
||||
public FromConvertBinder(NonBlockingDictionary<Type, Type> modelBinderConverts)
|
||||
{
|
||||
_modelBinderConverts = modelBinderConverts;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider
|
||||
/// <summary>
|
||||
/// 定义模型绑定转换器集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts;
|
||||
private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
|
||||
public FromConvertBinderProvider(ConcurrentDictionary<Type, Type> modelBinderConverts)
|
||||
public FromConvertBinderProvider(NonBlockingDictionary<Type, Type> modelBinderConverts)
|
||||
{
|
||||
_modelBinderConverts = modelBinderConverts;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
|
||||
namespace ThingsGateway.AspNetCore;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
|
||||
namespace ThingsGateway.AspNetCore;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ using System.Reflection;
|
||||
|
||||
using ThingsGateway;
|
||||
using ThingsGateway.ConfigurableOptions;
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
||||
@@ -34,9 +34,12 @@ public static class PBKDF2Encryption
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
var salt = new byte[saltSize];
|
||||
rng.GetBytes(salt);
|
||||
|
||||
#if NET10_0_OR_GREATER
|
||||
var hash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), salt, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
|
||||
#else
|
||||
using var pbkdf2 = new Rfc2898DeriveBytes(text, salt, iterationCount, HashAlgorithmName.SHA256);
|
||||
var hash = pbkdf2.GetBytes(derivedKeyLength);
|
||||
#endif
|
||||
|
||||
// 分别编码盐和哈希,并用分隔符拼接
|
||||
return Convert.ToBase64String(salt) + SaltHashSeparator + Convert.ToBase64String(hash);
|
||||
@@ -65,8 +68,12 @@ public static class PBKDF2Encryption
|
||||
if (saltBytes.Length != saltSize || storedHashBytes.Length != derivedKeyLength)
|
||||
return false;
|
||||
|
||||
#if NET10_0_OR_GREATER
|
||||
var computedHash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), saltBytes, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
|
||||
#else
|
||||
using var pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, iterationCount, HashAlgorithmName.SHA256);
|
||||
var computedHash = pbkdf2.GetBytes(derivedKeyLength);
|
||||
#endif
|
||||
|
||||
return computedHash.SequenceEqual(storedHashBytes);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Templates.Extensions;
|
||||
|
||||
namespace ThingsGateway.DataValidation;
|
||||
@@ -40,7 +40,7 @@ public static class DataValidator
|
||||
/// <summary>
|
||||
/// 验证类型正则表达式
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas;
|
||||
private static readonly NonBlockingDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
@@ -57,7 +57,7 @@ public static class DataValidator
|
||||
ValidationItemMetadatas = GetValidationValidationItemMetadatas();
|
||||
|
||||
// 缓存所有正则表达式
|
||||
GetValidationTypeValidationItemMetadataCached = new ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)>();
|
||||
GetValidationTypeValidationItemMetadataCached = new NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -203,7 +203,7 @@ public static class DataValidator
|
||||
/// <summary>
|
||||
/// 获取验证类型验证Item集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached;
|
||||
private static readonly NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached;
|
||||
|
||||
/// <summary>
|
||||
/// 获取验证类型正则表达式(需要缓存)
|
||||
@@ -267,9 +267,9 @@ public static class DataValidator
|
||||
/// 获取验证类型所有有效的正则表达式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static ConcurrentDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas()
|
||||
private static NonBlockingDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas()
|
||||
{
|
||||
var vaidationItems = new ConcurrentDictionary<string, ValidationItemMetadataAttribute>();
|
||||
var vaidationItems = new NonBlockingDictionary<string, ValidationItemMetadataAttribute>();
|
||||
|
||||
// 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义
|
||||
var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields()
|
||||
|
||||
@@ -353,7 +353,7 @@ public static class DependencyInjectionServiceCollectionExtensions
|
||||
/// <summary>
|
||||
/// 类型名称集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, Type> TypeNamedCollection;
|
||||
private static readonly NonBlockingDictionary<string, Type> TypeNamedCollection;
|
||||
|
||||
/// <summary>
|
||||
/// 创建代理方法
|
||||
@@ -374,7 +374,7 @@ public static class DependencyInjectionServiceCollectionExtensions
|
||||
GlobalServiceProxyType = App.EffectiveTypes
|
||||
.FirstOrDefault(u => typeof(AspectDispatchProxy).IsAssignableFrom(u) && typeof(IGlobalDispatchProxy).IsAssignableFrom(u) && u.IsClass && !u.IsInterface && !u.IsAbstract);
|
||||
|
||||
TypeNamedCollection = new ConcurrentDictionary<string, Type>();
|
||||
TypeNamedCollection = new NonBlockingDictionary<string, Type>();
|
||||
DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ internal sealed class NamedServiceProvider<TService> : INamedServiceProvider<TSe
|
||||
#pragma warning disable CA1851
|
||||
if (services
|
||||
.OfType<AspectDispatchProxy>()
|
||||
.FirstOrDefault(u => ResovleServiceName(((dynamic)u).Target.GetType()) == serviceName) is not TService service)
|
||||
.FirstOrDefault(u => ResolveServiceName(((dynamic)u).Target.GetType()) == serviceName) is not TService service)
|
||||
{
|
||||
service = services.FirstOrDefault(u => ResovleServiceName(u.GetType()) == serviceName);
|
||||
service = services.FirstOrDefault(u => ResolveServiceName(u.GetType()) == serviceName);
|
||||
}
|
||||
#pragma warning restore CA1851
|
||||
|
||||
@@ -85,9 +85,9 @@ internal sealed class NamedServiceProvider<TService> : INamedServiceProvider<TSe
|
||||
#pragma warning disable CA1851
|
||||
if (services
|
||||
.OfType<AspectDispatchProxy>()
|
||||
.FirstOrDefault(u => ResovleServiceName(((dynamic)u).Target.GetType()) == serviceName) is not TService service)
|
||||
.FirstOrDefault(u => ResolveServiceName(((dynamic)u).Target.GetType()) == serviceName) is not TService service)
|
||||
{
|
||||
service = services.FirstOrDefault(u => ResovleServiceName(u.GetType()) == serviceName);
|
||||
service = services.FirstOrDefault(u => ResolveServiceName(u.GetType()) == serviceName);
|
||||
}
|
||||
#pragma warning restore CA1851
|
||||
|
||||
@@ -116,7 +116,7 @@ internal sealed class NamedServiceProvider<TService> : INamedServiceProvider<TSe
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
private static string ResovleServiceName(Type type)
|
||||
private static string ResolveServiceName(Type type)
|
||||
{
|
||||
if (type.IsDefined(typeof(InjectionAttribute)))
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.UnifyResult;
|
||||
|
||||
namespace ThingsGateway.DynamicApiController;
|
||||
|
||||
@@ -28,21 +28,21 @@ internal static class Penetrates
|
||||
/// <summary>
|
||||
/// 请求动词映射字典
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<string, string> VerbToHttpMethods { get; private set; }
|
||||
internal static NonBlockingDictionary<string, string> VerbToHttpMethods { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 控制器排序集合
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; }
|
||||
internal static NonBlockingDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
static Penetrates()
|
||||
{
|
||||
ControllerOrderCollection = new ConcurrentDictionary<string, (string, int, Type)>();
|
||||
ControllerOrderCollection = new NonBlockingDictionary<string, (string, int, Type)>();
|
||||
|
||||
VerbToHttpMethods = new ConcurrentDictionary<string, string>
|
||||
VerbToHttpMethods = new NonBlockingDictionary<string, string>
|
||||
{
|
||||
["post"] = "POST",
|
||||
["add"] = "POST",
|
||||
@@ -67,13 +67,13 @@ internal static class Penetrates
|
||||
["patch"] = "PATCH"
|
||||
};
|
||||
|
||||
//IsApiControllerCached = new ConcurrentDictionary<Type, bool>();
|
||||
//IsApiControllerCached = new NonBlockingDictionary<Type, bool>();
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// <see cref="IsApiController(Type)"/> 缓存集合
|
||||
///// </summary>
|
||||
//private static readonly ConcurrentDictionary<Type, bool> IsApiControllerCached;
|
||||
//private static readonly NonBlockingDictionary<Type, bool> IsApiControllerCached;
|
||||
|
||||
/// <summary>
|
||||
/// 是否是Api控制器
|
||||
|
||||
@@ -61,7 +61,7 @@ internal sealed class EventBusHostedService : BackgroundService
|
||||
/// <summary>
|
||||
/// 事件处理程序集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new();
|
||||
private readonly NonBlockingDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
@@ -295,7 +295,8 @@ internal sealed class EventBusHostedService : BackgroundService
|
||||
, retryAction: (total, times) =>
|
||||
{
|
||||
// 输出重试日志
|
||||
_logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId);
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true)
|
||||
_logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -17,7 +17,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Templates.Extensions;
|
||||
|
||||
namespace ThingsGateway.FriendlyException;
|
||||
@@ -31,7 +31,7 @@ public static class Oops
|
||||
/// <summary>
|
||||
/// 方法错误异常特性
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<MethodBase, MethodIfException> _errorMethods;
|
||||
private static readonly NonBlockingDictionary<MethodBase, MethodIfException> _errorMethods;
|
||||
|
||||
/// <summary>
|
||||
/// 错误代码类型
|
||||
@@ -41,7 +41,7 @@ public static class Oops
|
||||
/// <summary>
|
||||
/// 错误消息字典
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, string> _errorCodeMessages;
|
||||
private static readonly NonBlockingDictionary<string, string> _errorCodeMessages;
|
||||
|
||||
/// <summary>
|
||||
/// 友好异常设置
|
||||
@@ -53,7 +53,7 @@ public static class Oops
|
||||
/// </summary>
|
||||
static Oops()
|
||||
{
|
||||
_errorMethods = new ConcurrentDictionary<MethodBase, MethodIfException>();
|
||||
_errorMethods = new NonBlockingDictionary<MethodBase, MethodIfException>();
|
||||
_friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true);
|
||||
_errorCodeTypes = GetErrorCodeTypes();
|
||||
_errorCodeMessages = GetErrorCodeMessages();
|
||||
@@ -258,9 +258,9 @@ public static class Oops
|
||||
/// 获取所有错误消息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static ConcurrentDictionary<string, string> GetErrorCodeMessages()
|
||||
private static NonBlockingDictionary<string, string> GetErrorCodeMessages()
|
||||
{
|
||||
var defaultErrorCodeMessages = new ConcurrentDictionary<string, string>();
|
||||
var defaultErrorCodeMessages = new NonBlockingDictionary<string, string>();
|
||||
|
||||
// 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义
|
||||
var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute))))
|
||||
|
||||
@@ -27,13 +27,15 @@ public sealed class Retry
|
||||
/// <param name="exceptionTypes">异常类型,可多个</param>
|
||||
/// <param name="fallbackPolicy">重试失败回调</param>
|
||||
/// <param name="retryAction">重试时调用方法</param>
|
||||
/// <param name="shouldExit">退出条件</param>
|
||||
public static void Invoke(Action action
|
||||
, int numRetries
|
||||
, int retryTimeout = 1000
|
||||
, bool finalThrow = true
|
||||
, Type[] exceptionTypes = default
|
||||
, Action<Exception> fallbackPolicy = default
|
||||
, Action<int, int> retryAction = default)
|
||||
, Action<int, int> retryAction = default
|
||||
, Func<bool> shouldExit = default)
|
||||
{
|
||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||
|
||||
@@ -46,7 +48,7 @@ public sealed class Retry
|
||||
{
|
||||
fallbackPolicy?.Invoke(ex);
|
||||
return Task.CompletedTask;
|
||||
}, retryAction).GetAwaiter().GetResult();
|
||||
}, retryAction, shouldExit).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,6 +61,7 @@ public sealed class Retry
|
||||
/// <param name="exceptionTypes">异常类型,可多个</param>
|
||||
/// <param name="fallbackPolicy">重试失败回调</param>
|
||||
/// <param name="retryAction">重试时调用方法</param>
|
||||
/// <param name="shouldExit">退出条件</param>
|
||||
/// <returns><see cref="Task"/></returns>
|
||||
public static async Task InvokeAsync(Func<Task> action
|
||||
, int numRetries
|
||||
@@ -66,7 +69,8 @@ public sealed class Retry
|
||||
, bool finalThrow = true
|
||||
, Type[] exceptionTypes = default
|
||||
, Func<Exception, Task> fallbackPolicy = default
|
||||
, Action<int, int> retryAction = default)
|
||||
, Action<int, int> retryAction = default
|
||||
, Func<bool> shouldExit = default)
|
||||
{
|
||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||
|
||||
@@ -117,6 +121,12 @@ public sealed class Retry
|
||||
|
||||
// 如果可重试异常数大于 0,则间隔指定时间后继续执行
|
||||
if (retryTimeout > 0) await Task.Delay(retryTimeout).ConfigureAwait(false);
|
||||
|
||||
// 处理退出机制
|
||||
if (shouldExit != null && shouldExit())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ using Microsoft.AspNetCore.SignalR;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway;
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.InstantMessaging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder;
|
||||
|
||||
@@ -23,7 +23,7 @@ public static class ILoggerExtensions
|
||||
/// 设置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
|
||||
namespace ThingsGateway.Logging;
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public static class StringLoggingExtensions
|
||||
/// 配置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties)
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
|
||||
/// 记录日志所有滚动文件名
|
||||
/// </summary>
|
||||
/// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks>
|
||||
internal readonly ConcurrentDictionary<string, FileInfo> _rollingFileNames = new();
|
||||
internal readonly NonBlockingDictionary<string, FileInfo> _rollingFileNames = new();
|
||||
|
||||
/// <summary>
|
||||
/// 文件日志写入器
|
||||
|
||||
@@ -36,8 +36,9 @@ using System.Text.Json;
|
||||
|
||||
using ThingsGateway;
|
||||
using ThingsGateway.DataValidation;
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.FriendlyException;
|
||||
using ThingsGateway.JsonSerialization;
|
||||
using ThingsGateway.Logging;
|
||||
using ThingsGateway.Templates;
|
||||
using ThingsGateway.UnifyResult;
|
||||
@@ -150,7 +151,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
return;
|
||||
}
|
||||
|
||||
await MonitorAsync(actionMethod, context.ActionArguments, context, next).ConfigureAwait(false);
|
||||
await MonitorAsync(actionMethod, context.ActionArguments, context, () => next()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -182,7 +183,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
return;
|
||||
}
|
||||
|
||||
await MonitorAsync(actionMethod, context.HandlerArguments, context, next).ConfigureAwait(false);
|
||||
await MonitorAsync(actionMethod, context.HandlerArguments, context, () => next()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -639,6 +640,11 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
// 解决 JsonElement 序列化问题
|
||||
jsonSerializerSettings.Converters.Add(new JsonElementConverter());
|
||||
|
||||
// 解决 JsonObject 和 JsonArray 序列化问题
|
||||
jsonSerializerSettings.Converters.Add(new NewtonsoftJsonJsonObjectJsonConverter());
|
||||
jsonSerializerSettings.Converters.Add(new NewtonsoftJsonJsonArrayJsonConverter());
|
||||
|
||||
|
||||
// 解决 DateTimeOffset 序列化/反序列化问题
|
||||
if (obj is DateTimeOffset)
|
||||
{
|
||||
@@ -783,12 +789,12 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
return typeName;
|
||||
}
|
||||
|
||||
private async Task MonitorAsync(MethodInfo actionMethod, IDictionary<string, object> parameterValues, FilterContext context, dynamic next)
|
||||
private async Task MonitorAsync<T>(MethodInfo actionMethod, IDictionary<string, object> parameterValues, FilterContext context, Func<Task<T>> next)
|
||||
{
|
||||
// 排除 WebSocket 请求处理
|
||||
if (context.HttpContext.IsWebSocketRequest())
|
||||
{
|
||||
_ = await next();
|
||||
_ = await next().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -799,14 +805,14 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
if (actionMethod.IsDefined(typeof(SuppressMonitorAttribute), true)
|
||||
|| actionMethod.DeclaringType.IsDefined(typeof(SuppressMonitorAttribute), true))
|
||||
{
|
||||
_ = await next();
|
||||
_ = await next().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断是否自定义了日志筛选器,如果是则检查是否符合条件
|
||||
if (LoggingMonitorSettings.InternalWriteFilter?.Invoke(context) == false)
|
||||
{
|
||||
_ = await next();
|
||||
_ = await next().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -819,7 +825,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
// 解决局部和全局触发器同时配置触发两次问题
|
||||
if (isDefinedScopedAttribute && Settings.FromGlobalFilter == true)
|
||||
{
|
||||
_ = await next();
|
||||
_ = await next().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -833,7 +839,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
&& !Settings.IncludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
// 查找是否包含匹配,忽略大小写
|
||||
_ = await next();
|
||||
_ = await next().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -841,7 +847,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
if (Settings.GlobalEnabled
|
||||
&& Settings.ExcludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = await next();
|
||||
_ = await next().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -952,7 +958,8 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
|
||||
// 计算接口执行时间
|
||||
var timeOperation = Stopwatch.StartNew();
|
||||
var resultContext = await next();
|
||||
|
||||
var resultContext = await next().ConfigureAwait(false);
|
||||
timeOperation.Stop();
|
||||
writer.WriteNumber("timeOperationElapsedMilliseconds", timeOperation.ElapsedMilliseconds);
|
||||
|
||||
@@ -1008,8 +1015,13 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
var environment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>().EnvironmentName;
|
||||
writer.WriteString(nameof(environment), environment);
|
||||
|
||||
Exception exception = null;
|
||||
// 获取异常对象情况
|
||||
Exception exception = resultContext.Exception;
|
||||
if (resultContext is PageHandlerExecutedContext pageHandlerExecutedContext)
|
||||
exception = pageHandlerExecutedContext.Exception;
|
||||
else if (resultContext is ActionExecutedContext actionExecutedContext)
|
||||
exception = actionExecutedContext.Exception;
|
||||
|
||||
if (exception == null)
|
||||
{
|
||||
// 解析存储的验证信息
|
||||
|
||||
@@ -94,7 +94,7 @@ public sealed partial class StringLoggingPart
|
||||
/// <summary>
|
||||
/// 配置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public StringLoggingPart ScopeContext(IDictionary<string, object> properties)
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ public static class Log
|
||||
/// <summary>
|
||||
/// 配置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Options;
|
||||
|
||||
namespace Microsoft.Extensions.Options;
|
||||
|
||||
@@ -20,9 +20,10 @@ public sealed class DailyAtAttribute : CronAttribute
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="field">字段值</param>
|
||||
/// <param name="fields">字段值</param>
|
||||
public DailyAtAttribute(params object[] fields)
|
||||
: base("@daily", fields)
|
||||
public DailyAtAttribute(object field, params object[] fields)
|
||||
: base("@daily", new[] { field }.Concat(fields).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,10 @@ public sealed class HourlyAtAttribute : CronAttribute
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="field">字段值</param>
|
||||
/// <param name="fields">字段值</param>
|
||||
public HourlyAtAttribute(params object[] fields)
|
||||
: base("@hourly", fields)
|
||||
public HourlyAtAttribute(object field, params object[] fields)
|
||||
: base("@hourly", new[] { field }.Concat(fields).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,10 @@ public sealed class MinutelyAtAttribute : CronAttribute
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="field">字段值</param>
|
||||
/// <param name="fields">字段值</param>
|
||||
public MinutelyAtAttribute(params object[] fields)
|
||||
: base("@minutely", fields)
|
||||
public MinutelyAtAttribute(object field, params object[] fields)
|
||||
: base("@minutely", new[] { field }.Concat(fields).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,10 @@ public sealed class MonthlyAtAttribute : CronAttribute
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="field">字段值</param>
|
||||
/// <param name="fields">字段值</param>
|
||||
public MonthlyAtAttribute(params object[] fields)
|
||||
: base("@monthly", fields)
|
||||
public MonthlyAtAttribute(object field, params object[] fields)
|
||||
: base("@monthly", new[] { field }.Concat(fields).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,16 @@ namespace ThingsGateway.Schedule;
|
||||
/// <summary>
|
||||
/// 特定秒开始作业触发器特性
|
||||
/// </summary>
|
||||
[SecondlyAtAttribute, AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
[SuppressSniffer, AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public sealed class SecondlyAtAttribute : CronAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="field">字段值</param>
|
||||
/// <param name="fields">字段值</param>
|
||||
public SecondlyAtAttribute(params object[] fields)
|
||||
: base("@secondly", fields)
|
||||
public SecondlyAtAttribute(object field, params object[] fields)
|
||||
: base("@secondly", new[] { field }.Concat(fields).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,10 @@ public sealed class WeeklyAtAttribute : CronAttribute
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="field">字段值</param>
|
||||
/// <param name="fields">字段值</param>
|
||||
public WeeklyAtAttribute(params object[] fields)
|
||||
: base("@weekly", fields)
|
||||
public WeeklyAtAttribute(object field, params object[] fields)
|
||||
: base("@weekly", new[] { field }.Concat(fields).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,10 @@ public sealed class YearlyAtAttribute : CronAttribute
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="field">字段值</param>
|
||||
/// <param name="fields">字段值</param>
|
||||
public YearlyAtAttribute(params object[] fields)
|
||||
: base("@yearly", fields)
|
||||
public YearlyAtAttribute(object field, params object[] fields)
|
||||
: base("@yearly", new[] { field }.Concat(fields).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ internal sealed class JobCancellationToken : IJobCancellationToken
|
||||
/// <summary>
|
||||
/// 取消作业执行 Token 集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokenSources;
|
||||
private readonly NonBlockingDictionary<string, CancellationTokenSource> _cancellationTokenSources;
|
||||
|
||||
/// <summary>
|
||||
/// 作业调度器日志服务
|
||||
@@ -93,12 +93,14 @@ internal sealed class JobCancellationToken : IJobCancellationToken
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex) when (!(ex is OperationCanceledException ||
|
||||
ex is ObjectDisposedException ||
|
||||
(ex is AggregateException aggEx && aggEx.InnerExceptions.All(e => e is OperationCanceledException || e is ObjectDisposedException))))
|
||||
{ }
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出非任务取消异常日志
|
||||
if (!(ex is OperationCanceledException || (ex is AggregateException aggEx && aggEx.InnerExceptions.Count == 1 && aggEx.InnerExceptions[0] is TaskCanceledException)))
|
||||
{
|
||||
// 待输出
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ThingsGateway.Schedule;
|
||||
|
||||
/// <summary>
|
||||
@@ -152,7 +154,31 @@ public abstract class JobExecutionContext
|
||||
writer.WriteEndObject();
|
||||
});
|
||||
}
|
||||
/// <summary>
|
||||
/// 检查作业任务是否处于正常状态
|
||||
/// </summary>
|
||||
/// <param name="schedulerFactory"><see cref="ISchedulerFactory"/></param>
|
||||
/// <returns><see cref="bool"/></returns>
|
||||
public bool IsNormalStatus(ISchedulerFactory schedulerFactory = null)
|
||||
{
|
||||
// 解析作业计划工厂服务
|
||||
schedulerFactory ??= ServiceProvider.GetRequiredService<ISchedulerFactory>();
|
||||
|
||||
// 情况 1:检查作业是否存在
|
||||
if (schedulerFactory.TryGetJob(JobId, out var scheduler) != ScheduleResult.Succeed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 情况 2:检查作业触发器是否存在
|
||||
if (scheduler.TryGetTrigger(TriggerId, out var trigger) != ScheduleResult.Succeed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 情况 3:检查作业触发器是否正常运行
|
||||
return trigger.IsNormalStatus();
|
||||
}
|
||||
/// <summary>
|
||||
/// 作业执行上下文转字符串输出输出
|
||||
/// </summary>
|
||||
|
||||
@@ -209,7 +209,7 @@ public sealed class ScheduleUIMiddleware
|
||||
case "remove":
|
||||
_schedulerFactory.RemoveJob(jobId);
|
||||
break;
|
||||
// 立即执行
|
||||
// 手动执行
|
||||
case "run":
|
||||
_schedulerFactory.RunJob(jobId);
|
||||
break;
|
||||
@@ -264,7 +264,7 @@ public sealed class ScheduleUIMiddleware
|
||||
case "remove":
|
||||
scheduler1?.RemoveTrigger(triggerId);
|
||||
break;
|
||||
// 立即执行
|
||||
// 手动执行
|
||||
case "run":
|
||||
scheduler1?.Run(triggerId);
|
||||
break;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/__schedule__/static/css/main.fbe5db1c.css",
|
||||
"main.js": "/__schedule__/static/js/main.851eb0b3.js",
|
||||
"main.css": "/__schedule__/static/css/main.765127e9.css",
|
||||
"main.js": "/__schedule__/static/js/main.326c761f.js",
|
||||
"index.html": "/__schedule__/index.html",
|
||||
"main.fbe5db1c.css.map": "/__schedule__/static/css/main.fbe5db1c.css.map",
|
||||
"main.851eb0b3.js.map": "/__schedule__/static/js/main.851eb0b3.js.map"
|
||||
"main.765127e9.css.map": "/__schedule__/static/css/main.765127e9.css.map",
|
||||
"main.326c761f.js.map": "/__schedule__/static/js/main.326c761f.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.fbe5db1c.css",
|
||||
"static/js/main.851eb0b3.js"
|
||||
"static/css/main.765127e9.css",
|
||||
"static/js/main.326c761f.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1,19 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/__schedule__/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Schedule Dashboard"/><link rel="apple-touch-icon" href="/__schedule__/logo192.png"/><script src="/__schedule__/apiconfig.js"></script><title>Schedule Dashboard</title><script defer="defer" src="/__schedule__/static/js/main.851eb0b3.js"></script><link href="/__schedule__/static/css/main.fbe5db1c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>document.title=window.apiconfig.title</script></body></html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/__schedule__/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Schedule Dashboard" />
|
||||
<link rel="apple-touch-icon" href="/__schedule__/logo192.png" />
|
||||
<script src="/__schedule__/apiconfig.js"></script>
|
||||
<title>Schedule Dashboard</title>
|
||||
<script defer="defer" src="/__schedule__/static/js/main.326c761f.js"></script>
|
||||
<link href="/__schedule__/static/css/main.765127e9.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div>
|
||||
<script>document.title = window.apiconfig.title</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user