mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-30 07:03:59 +08:00
@@ -7,7 +7,7 @@
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\*.json" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -40,36 +40,54 @@ public class TimeTick
|
||||
/// <param name="currentTime">当前时间</param>
|
||||
/// <returns>是否触发时间刻度</returns>
|
||||
public bool IsTickHappen(DateTime currentTime)
|
||||
{
|
||||
var nextTime = GetNextTime(currentTime);
|
||||
bool result = SetLastTime(currentTime, nextTime);
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool SetLastTime(DateTime currentTime, DateTime nextTime)
|
||||
{
|
||||
var diffMilliseconds = (currentTime - nextTime).TotalMilliseconds;
|
||||
|
||||
var result = diffMilliseconds >= 0;
|
||||
if (result)
|
||||
{
|
||||
if (diffMilliseconds > _intervalMilliseconds)
|
||||
LastTime = currentTime;
|
||||
else
|
||||
LastTime = nextTime;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = true)
|
||||
{
|
||||
// 在没有 Cron 表达式的情况下,使用固定间隔
|
||||
if (cron == null)
|
||||
{
|
||||
var nextTime = LastTime.AddMilliseconds(_intervalMilliseconds);
|
||||
var diffMilliseconds = (currentTime - nextTime).TotalMilliseconds;
|
||||
if (setLastTime)
|
||||
SetLastTime(currentTime, nextTime);
|
||||
|
||||
var result = diffMilliseconds >= 0;
|
||||
if (result)
|
||||
{
|
||||
if (diffMilliseconds > _intervalMilliseconds)
|
||||
LastTime = currentTime;
|
||||
else
|
||||
LastTime = nextTime;
|
||||
}
|
||||
return result;
|
||||
return nextTime;
|
||||
}
|
||||
// 使用 Cron 表达式
|
||||
else
|
||||
{
|
||||
var nextTime = cron.GetNext(LastTime);
|
||||
if (currentTime >= nextTime)
|
||||
{
|
||||
LastTime = nextTime;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (setLastTime)
|
||||
SetLastTime(currentTime, nextTime);
|
||||
return nextTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public DateTime GetNextTime(bool setLastTime = true) => GetNextTime(DateTime.UtcNow, setLastTime);
|
||||
|
||||
/// <summary>
|
||||
/// 是否到达设置的时间间隔
|
||||
/// </summary>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.4.7</PluginVersion>
|
||||
<ProPluginVersion>10.4.7</ProPluginVersion>
|
||||
<PluginVersion>10.4.8</PluginVersion>
|
||||
<ProPluginVersion>10.4.8</ProPluginVersion>
|
||||
<AuthenticationVersion>2.1.7</AuthenticationVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -83,16 +83,8 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
//await _connectLock.WaitAsync().ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Closed{msg}");
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -114,11 +106,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
{
|
||||
//await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -153,22 +141,35 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
|
||||
protected override async Task OnSerialClosed(ClosedEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await base.OnSerialClosed(e).ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
Logger?.Info($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await base.OnSerialClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnSerialConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override async Task OnSerialConnected(ConnectedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
await base.OnSerialConnected(e).ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
|
||||
@@ -82,11 +82,6 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
if (Online)
|
||||
{
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Closed{msg}");
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -106,14 +101,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
//await _connectLock.WaitAsync(token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
//await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -136,11 +124,22 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
return $"{IP}:{Port}";
|
||||
}
|
||||
|
||||
|
||||
protected override async Task OnTcpClosed(ClosedEventArgs e)
|
||||
{
|
||||
|
||||
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
|
||||
await base.OnTcpClosed(e).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
Logger?.Info($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await base.OnTcpClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
@@ -148,11 +147,19 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnTcpConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override async Task OnTcpConnected(ConnectedEventArgs e)
|
||||
{
|
||||
Logger?.Info($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
|
||||
await base.OnTcpConnected(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
|
||||
@@ -129,28 +129,28 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpClosed(TClient socketClient, ClosedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
Logger?.Info($"{socketClient} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
return base.OnTcpClosed(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpClosing(TClient socketClient, ClosingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
Logger?.Info($"{socketClient} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
return base.OnTcpClosing(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpConnected(TClient socketClient, ConnectedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Connected");
|
||||
Logger?.Info($"{socketClient} Connected");
|
||||
return base.OnTcpConnected(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpConnecting(TClient socketClient, ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Connecting");
|
||||
Logger?.Trace($"{socketClient} Connecting");
|
||||
return base.OnTcpConnecting(socketClient, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
return $"{ChannelOptions.BindUrl} {ChannelOptions.RemoteUrl}";
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnUdpReceived(UdpReceivedDataEventArgs e)
|
||||
{
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.3" />
|
||||
<PackageReference Include="TouchSocket" Version="3.0.23" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.23" />
|
||||
<PackageReference Include="TouchSocket" Version="3.0.24" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.24" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -136,28 +136,11 @@ public abstract class BusinessBase : DriverBase
|
||||
// 获取设备连接状态并更新设备活动时间
|
||||
if (IsConnected())
|
||||
{
|
||||
// 如果不是采集设备,则直接更新设备状态为当前时间
|
||||
if (IsCollectDevice == false)
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则,更新设备活动时间
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now);
|
||||
}
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果设备未连接,则更新设备状态为断开
|
||||
if (!IsConnected())
|
||||
{
|
||||
// 如果不是采集设备,则直接更新设备状态为当前时间
|
||||
if (IsCollectDevice == false)
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, true);
|
||||
}
|
||||
}
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, true);
|
||||
}
|
||||
|
||||
// 再次检查取消操作是否被请求
|
||||
|
||||
@@ -168,7 +168,76 @@ public abstract class CollectBase : DriverBase
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
/// <summary>
|
||||
/// 循环任务
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消操作的令牌。</param>
|
||||
/// <returns>表示异步操作结果的枚举。</returns>
|
||||
internal override async ValueTask<ThreadRunReturnTypeEnum> ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果取消操作被请求,则返回中断状态
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 如果标志为停止,则暂停执行
|
||||
if (Pause)
|
||||
{
|
||||
// 暂停
|
||||
return ThreadRunReturnTypeEnum.Continue;
|
||||
}
|
||||
|
||||
// 再次检查取消操作是否被请求
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 获取设备连接状态并更新设备活动时间
|
||||
if (IsConnected())
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now);
|
||||
}
|
||||
|
||||
// 再次检查取消操作是否被请求
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 执行任务操作
|
||||
await ProtectedExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 再次检查取消操作是否被请求
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 正常返回None状态
|
||||
return ThreadRunReturnTypeEnum.None;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
// 记录异常信息,并更新设备状态为异常
|
||||
LogMessage?.LogError(ex, "Execute");
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, true, ex.Message);
|
||||
return ThreadRunReturnTypeEnum.None;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 执行读取等方法,如果插件不支持读取,而是自更新值的话,需重写此方法
|
||||
/// </summary>
|
||||
|
||||
@@ -26,7 +26,7 @@ public abstract class CollectPropertyBase : DriverPropertyBase
|
||||
/// 离线后恢复运行的间隔时间
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public virtual int ReIntervalTime { get; set; } = 30000;
|
||||
public virtual int ReIntervalTime { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 失败重试次数,默认3
|
||||
|
||||
@@ -310,92 +310,7 @@ public abstract class DriverBase : DisposableObject, IDriver
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消操作的令牌。</param>
|
||||
/// <returns>表示异步操作结果的枚举。</returns>
|
||||
internal virtual async ValueTask<ThreadRunReturnTypeEnum> ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果取消操作被请求,则返回中断状态
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 如果标志为停止,则暂停执行
|
||||
if (Pause)
|
||||
{
|
||||
// 暂停
|
||||
return ThreadRunReturnTypeEnum.Continue;
|
||||
}
|
||||
|
||||
// 再次检查取消操作是否被请求
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 获取设备连接状态并更新设备活动时间
|
||||
if (IsConnected())
|
||||
{
|
||||
// 如果不是采集设备,则直接更新设备状态为当前时间
|
||||
if (IsCollectDevice == false)
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则,更新设备活动时间
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果设备未连接,则更新设备状态为断开
|
||||
if (!IsConnected())
|
||||
{
|
||||
// 如果不是采集设备,则直接更新设备状态为当前时间
|
||||
if (IsCollectDevice == false)
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 再次检查取消操作是否被请求
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 执行任务操作
|
||||
await ProtectedExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 再次检查取消操作是否被请求
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
|
||||
// 正常返回None状态
|
||||
return ThreadRunReturnTypeEnum.None;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return ThreadRunReturnTypeEnum.Break;
|
||||
// 记录异常信息,并更新设备状态为异常
|
||||
LogMessage?.LogError(ex, "Execute");
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, true, ex.Message);
|
||||
return ThreadRunReturnTypeEnum.None;
|
||||
}
|
||||
}
|
||||
internal abstract ValueTask<ThreadRunReturnTypeEnum> ExecuteAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 已停止循环任务,释放插件
|
||||
|
||||
@@ -54,7 +54,7 @@ public class Device : BaseDataEntity, IValidatableObject
|
||||
/// 通道
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "通道", Length = 200)]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = false, Sortable = false)]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
[IgnoreExcel]
|
||||
[MinValue(1)]
|
||||
[Required]
|
||||
|
||||
@@ -31,6 +31,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public PluginInfo? PluginInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -41,11 +42,13 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
/// <summary>
|
||||
/// 是否采集
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public bool? IsCollect => PluginInfo == null ? null : PluginInfo?.PluginType == PluginTypeEnum.Collect;
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public WaitLock WaitLock { get; private set; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -77,6 +80,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public TouchSocketConfig Config { get; set; } = new();
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
@@ -104,8 +108,10 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public IDeviceThreadManage? DeviceThreadManage { get; internal set; }
|
||||
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public string LogPath => Name.GetChannelLogPath();
|
||||
|
||||
|
||||
|
||||
@@ -42,11 +42,13 @@ public class DeviceRuntime : Device, IDisposable
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public virtual PluginTypeEnum? PluginType => ChannelRuntime?.PluginInfo?.PluginType;
|
||||
|
||||
/// <summary>
|
||||
/// 是否采集
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public bool? IsCollect => PluginType == null ? null : PluginType == PluginTypeEnum.Collect;
|
||||
|
||||
/// <summary>
|
||||
@@ -55,12 +57,14 @@ public class DeviceRuntime : Device, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public ChannelRuntime? ChannelRuntime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道名称
|
||||
/// </summary>
|
||||
public string? ChannelName => ChannelRuntime?.Name;
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public string LogPath => Name.GetDeviceLogPath();
|
||||
|
||||
/// <summary>
|
||||
@@ -113,6 +117,7 @@ public class DeviceRuntime : Device, IDisposable
|
||||
/// <summary>
|
||||
/// 设备属性数量
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public int PropertysCount { get => DevicePropertys == null ? 0 : DevicePropertys.Count; }
|
||||
|
||||
/// <summary>
|
||||
@@ -158,6 +163,7 @@ public class DeviceRuntime : Device, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public List<VariableMethod>? ReadVariableMethods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -171,6 +177,7 @@ public class DeviceRuntime : Device, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public List<VariableSourceRead>? VariableSourceReads { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -179,6 +186,7 @@ public class DeviceRuntime : Device, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public List<VariableScriptRead>? VariableScriptReads { get; set; }
|
||||
|
||||
|
||||
@@ -213,6 +221,7 @@ public class DeviceRuntime : Device, IDisposable
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public IDriver? Driver { get; internal set; }
|
||||
|
||||
|
||||
|
||||
@@ -589,13 +589,19 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
// 如果驱动处于离线状态且为采集驱动,则根据配置的间隔时间进行延迟
|
||||
if (driver.CurrentDevice.DeviceStatus == DeviceStatusEnum.OffLine && IsCollectChannel == true)
|
||||
{
|
||||
driver.CurrentDevice.CheckEnable = false;
|
||||
await Task.Delay(Math.Max(Math.Min(((CollectBase)driver).CollectProperties.ReIntervalTime, ManageHelper.ChannelThreadOptions.CheckInterval / 2) - CycleInterval, 3000), token).ConfigureAwait(false);
|
||||
driver.CurrentDevice.CheckEnable = true;
|
||||
var collectBase = (CollectBase)driver;
|
||||
if (collectBase.CollectProperties.ReIntervalTime > 0)
|
||||
{
|
||||
await Task.Delay(Math.Max(Math.Min(collectBase.CollectProperties.ReIntervalTime, ManageHelper.ChannelThreadOptions.CheckInterval / 2) - CycleInterval, 3000), token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(CycleInterval, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(CycleInterval, token).ConfigureAwait(false); // 默认延迟一段时间后再继续执行
|
||||
await Task.Delay(CycleInterval, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (result == ThreadRunReturnTypeEnum.Continue)
|
||||
@@ -621,7 +627,6 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -857,7 +862,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
if (driver.CurrentDevice != null)
|
||||
{
|
||||
//线程卡死/初始化失败检测
|
||||
if (((driver.IsStarted && driver.CurrentDevice.ActiveTime != DateTime.UnixEpoch.ToLocalTime() && driver.CurrentDevice.ActiveTime.AddMinutes(ManageHelper.ChannelThreadOptions.CheckInterval) <= DateTime.Now)
|
||||
if (((driver.IsStarted && driver.CurrentDevice.ActiveTime != DateTime.UnixEpoch.ToLocalTime() && driver.CurrentDevice.ActiveTime.AddMilliseconds(ManageHelper.ChannelThreadOptions.CheckInterval) <= DateTime.Now)
|
||||
|| (driver.IsInitSuccess == false)) && !driver.DisposedValue)
|
||||
{
|
||||
//如果线程处于暂停状态,跳过
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.6" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.23" />
|
||||
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.23" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.24" />
|
||||
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.24" />
|
||||
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
@@ -67,8 +67,7 @@
|
||||
"ThingsGateway.Gateway.Razor.VariableRuntimeInfo": {
|
||||
"WriteVariable": "WriteVariable",
|
||||
"WriteValue": "WriteValue",
|
||||
"ExcelVariable": "ExcelVariable",
|
||||
"ImportExcel": "ImportExcel",
|
||||
|
||||
"TestVariableCount": "TestVariableCount",
|
||||
"TestDeviceCount": "TestDeviceCount",
|
||||
"SlaveUrl": "SlaveUrlUrl",
|
||||
@@ -94,8 +93,10 @@
|
||||
},
|
||||
|
||||
"ThingsGateway.Gateway.Razor.ShowTypeEnum": {
|
||||
"Variable": "Variable",
|
||||
"LogInfo": "Info"
|
||||
"VariableTable": "VariableTable",
|
||||
"LogInfo": "LogInfo",
|
||||
"ChannelTable": "ChannelTable",
|
||||
"DeviceTable": "DeviceTable"
|
||||
},
|
||||
|
||||
"ThingsGateway.Gateway.Razor.VariableEditComponent": {
|
||||
@@ -105,6 +106,10 @@
|
||||
|
||||
|
||||
"ThingsGateway.Gateway.Razor._Imports": {
|
||||
|
||||
"ExcelVariable": "ExcelVariable",
|
||||
"ImportExcel": "ImportExcel",
|
||||
|
||||
"CopyVariableNamePrefix": "CopyVariableNamePrefix",
|
||||
"CopyVariableNameSuffixNumber": "CopyVariableNameSuffixNumber",
|
||||
"CopyChannelNamePrefix": "CopyChannelNamePrefix",
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
"ThingsGateway.Gateway.Razor.VariableRuntimeInfo": {
|
||||
"WriteVariable": "写入",
|
||||
"WriteValue": "写入值",
|
||||
"ImportExcel": "导入变量",
|
||||
"ExcelVariable": "在线excel编辑变量",
|
||||
"TestVariableCount": "变量数量",
|
||||
"TestDeviceCount": "采集设备数量",
|
||||
"SlaveUrl": "服务端Url",
|
||||
@@ -73,8 +71,10 @@
|
||||
},
|
||||
|
||||
"ThingsGateway.Gateway.Razor.ShowTypeEnum": {
|
||||
"Variable": "变量页面",
|
||||
"LogInfo": "日志页面"
|
||||
"VariableTable": "变量页面",
|
||||
"LogInfo": "日志页面",
|
||||
"ChannelTable": "通道页面",
|
||||
"DeviceTable": "设备页面"
|
||||
},
|
||||
|
||||
"ThingsGateway.Gateway.Razor.SavePlugin": {
|
||||
@@ -98,6 +98,9 @@
|
||||
},
|
||||
|
||||
"ThingsGateway.Gateway.Razor._Imports": {
|
||||
"ExcelVariable": "在线excel编辑变量",
|
||||
"ImportExcel": "导入变量",
|
||||
|
||||
"CopyVariableNamePrefix": "变量名称前缀",
|
||||
"CopyVariableNameSuffixNumber": "变量名称后缀开始序号",
|
||||
"CopyChannelNamePrefix": "通道名称前缀",
|
||||
|
||||
@@ -11,112 +11,124 @@
|
||||
{
|
||||
<ValidateForm Model="Model" OnValidSubmit="ValidSubmit">
|
||||
|
||||
@renderFragment
|
||||
|
||||
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=2 LabelWidth=200 Model="Model">
|
||||
<div class="form-footer">
|
||||
|
||||
<FieldItems>
|
||||
<EditorItem TValue="string" TModel="Channel" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["BasicInformation"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.Name" Readonly=BatchEditEnable />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.PluginName">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-6">
|
||||
<Select @bind-Value="@value.PluginName"
|
||||
Items="@PluginNames" IsDisabled=BatchEditEnable
|
||||
ShowSearch="true">
|
||||
<ItemTemplate Context="name">
|
||||
@if (PluginDcit.TryGetValue(name.Value, out var pluginOutput))
|
||||
{
|
||||
if (pluginOutput.EducationPlugin)
|
||||
{
|
||||
<div class="d-flex">
|
||||
<span>@name.Text</span>
|
||||
<div style="flex-grow: 1;"></div>
|
||||
<Tag Color="Color.Primary">PRO</Tag>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@name.Text</span>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@name.Value</span>
|
||||
}
|
||||
</ItemTemplate>
|
||||
</Select>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.Enable" />
|
||||
<EditorItem @bind-Field="@context.LogEnable" />
|
||||
<EditorItem @bind-Field="@context.LogLevel" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Channel" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Connection"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.ChannelType">
|
||||
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-sm-6 col-md-6">
|
||||
<Select SkipValidate="true" @bind-Value="@value.ChannelType" OnSelectedItemChanged=@((a)=>
|
||||
{
|
||||
return InvokeAsync(StateHasChanged);
|
||||
}) />
|
||||
</div>
|
||||
</EditTemplate>
|
||||
|
||||
</EditorItem>
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.RemoteUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.BindUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession&&context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
|
||||
<EditorItem @bind-Field="@context.PortName" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.BaudRate" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.DataBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.Parity" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.StopBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType==ChannelTypeEnum.UdpSession||context.ChannelType==ChannelTypeEnum.Other) />
|
||||
<EditorItem @bind-Field="@context.ConnectTimeout" Ignore=@(context.ChannelType==ChannelTypeEnum.UdpSession||context.ChannelType==ChannelTypeEnum.TcpService||context.ChannelType==ChannelTypeEnum.Other) />
|
||||
<EditorItem @bind-Field="@context.MaxConcurrentCount" Ignore=@(context.ChannelType==ChannelTypeEnum.Other) />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.MaxClientCount" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
<EditorItem @bind-Field="@context.CheckClearTime" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
<EditorItem @bind-Field="@context.Heartbeat" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService&&context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.HeartbeatTime" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.DtuId" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.DtuSeviceType" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
|
||||
|
||||
</FieldItems>
|
||||
<Buttons>
|
||||
<Button ButtonType="ButtonType.Submit" Icon="fa-solid fa-floppy-disk" IsAsync Text=@RazorLocalizer["Save"] />
|
||||
</Buttons>
|
||||
</EditorForm>
|
||||
<Button ButtonType="ButtonType.Submit" Icon="fa-solid fa-floppy-disk" IsAsync Text=@RazorLocalizer["Save"] />
|
||||
</div>
|
||||
|
||||
</ValidateForm>
|
||||
|
||||
|
||||
}
|
||||
</div>
|
||||
else
|
||||
{
|
||||
@renderFragment
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@code {
|
||||
RenderFragment renderFragment =>
|
||||
@<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=2 LabelWidth=200 Model="Model">
|
||||
|
||||
<FieldItems>
|
||||
<EditorItem TValue="string" TModel="Channel" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["BasicInformation"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.Name" Readonly=BatchEditEnable />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.PluginName">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-6">
|
||||
<Select @bind-Value="@value.PluginName"
|
||||
Items="@PluginNames" IsDisabled=BatchEditEnable
|
||||
ShowSearch="true">
|
||||
<ItemTemplate Context="name">
|
||||
@if (PluginDcit.TryGetValue(name.Value, out var pluginOutput))
|
||||
{
|
||||
if (pluginOutput.EducationPlugin)
|
||||
{
|
||||
<div class="d-flex">
|
||||
<span>@name.Text</span>
|
||||
<div style="flex-grow: 1;"></div>
|
||||
<Tag Color="Color.Primary">PRO</Tag>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@name.Text</span>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@name.Value</span>
|
||||
}
|
||||
</ItemTemplate>
|
||||
</Select>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.Enable" />
|
||||
<EditorItem @bind-Field="@context.LogEnable" />
|
||||
<EditorItem @bind-Field="@context.LogLevel" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Channel" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Connection"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.ChannelType">
|
||||
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-sm-6 col-md-6">
|
||||
<Select SkipValidate="true" @bind-Value="@value.ChannelType" OnSelectedItemChanged=@((a)=>
|
||||
{
|
||||
return InvokeAsync(StateHasChanged);
|
||||
}) />
|
||||
</div>
|
||||
</EditTemplate>
|
||||
|
||||
</EditorItem>
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.RemoteUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.BindUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession&&context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
|
||||
<EditorItem @bind-Field="@context.PortName" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.BaudRate" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.DataBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.Parity" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.StopBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType==ChannelTypeEnum.UdpSession||context.ChannelType==ChannelTypeEnum.Other) />
|
||||
<EditorItem @bind-Field="@context.ConnectTimeout" Ignore=@(context.ChannelType==ChannelTypeEnum.UdpSession||context.ChannelType==ChannelTypeEnum.TcpService||context.ChannelType==ChannelTypeEnum.Other) />
|
||||
<EditorItem @bind-Field="@context.MaxConcurrentCount" Ignore=@(context.ChannelType==ChannelTypeEnum.Other) />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.MaxClientCount" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
<EditorItem @bind-Field="@context.CheckClearTime" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
<EditorItem @bind-Field="@context.Heartbeat" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService&&context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.HeartbeatTime" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.DtuId" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.DtuSeviceType" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpService&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
|
||||
</FieldItems>
|
||||
|
||||
</EditorForm>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Debug
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@inherits ComponentDefault
|
||||
|
||||
<AdminTable @ref=table
|
||||
TItem="ChannelRuntime"
|
||||
EditDialogSize="Size.ExtraLarge"
|
||||
AutoGenerateColumns="true"
|
||||
ShowAdvancedSearch=false
|
||||
ScrollingDialogContent=false
|
||||
AllowResizing="true"
|
||||
OnAdd="OnAdd"
|
||||
IsFixedHeader=true
|
||||
IsMultipleSelect=true
|
||||
SearchMode=SearchMode.Top
|
||||
ShowExtendButtons=true
|
||||
ShowToolbar="true"
|
||||
ShowExportButton
|
||||
ShowDefaultButtons=true
|
||||
ShowSearch=false
|
||||
ShowExtendEditButton="true"
|
||||
ShowExtendDeleteButton="true"
|
||||
ExtendButtonColumnWidth=220
|
||||
OnSaveAsync="Save"
|
||||
OnDeleteAsync="Delete"
|
||||
OnQueryAsync="OnQueryAsync"
|
||||
IsPagination=true>
|
||||
<TableColumns>
|
||||
<TableColumn @bind-Field="@context.Name" ShowTips=true Filterable=true Sortable=true Visible=true>
|
||||
|
||||
<Template Context="value">
|
||||
@value.Row?.ToString()
|
||||
</Template>
|
||||
|
||||
</TableColumn>
|
||||
<TableColumn @bind-Field="@context.PluginName" ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn @bind-Field="@context.Enable" Filterable=true Sortable=true Visible="true" />
|
||||
<TableColumn @bind-Field="@context.LogEnable" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.LogLevel" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.ChannelType" Filterable=true Sortable=true Visible="false" />
|
||||
|
||||
<TableColumn Field="@context.CacheTimeout" FieldExpression=@(()=>context.CacheTimeout) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.CheckClearTime" FieldExpression=@(()=>context.CheckClearTime) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.ConnectTimeout" FieldExpression=@(()=>context.ConnectTimeout) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.Heartbeat" FieldExpression=@(()=>context.Heartbeat) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.HeartbeatTime" FieldExpression=@(()=>context.HeartbeatTime) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.DtuSeviceType" FieldExpression=@(()=>context.DtuSeviceType) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.DtuId" FieldExpression=@(()=>context.DtuId) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
|
||||
<TableColumn Field="@context.PluginType" FieldExpression=@(()=>context.PluginType) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.PortName" FieldExpression=@(()=>context.PortName) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.RemoteUrl" FieldExpression=@(()=>context.RemoteUrl) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.BindUrl" FieldExpression=@(()=>context.BindUrl) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.MaxClientCount" FieldExpression=@(()=>context.MaxClientCount) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.MaxConcurrentCount" FieldExpression=@(()=>context.MaxConcurrentCount) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
|
||||
<TableColumn @bind-Field="@context.Id" Filterable=true Sortable=true Visible="false" DefaultSort=true DefaultSortOrder="SortOrder.Asc" />
|
||||
|
||||
</TableColumns>
|
||||
|
||||
<EditTemplate Context="context">
|
||||
<ChannelEditComponent Model=@(context) ValidateEnable=false BatchEditEnable=false PluginType="ChannelDeviceHelpers.GetPluginType( SelectModel)"></ChannelEditComponent>
|
||||
</EditTemplate>
|
||||
|
||||
|
||||
|
||||
<ExportButtonDropdownTemplate Context="ExportContext">
|
||||
<Button class="dropdown-item" OnClick="() => ExcelExportAsync(ExportContext,true)" IsDisabled=@(!AuthorizeButton("导出"))>
|
||||
<i class="fas fa-file-export"></i>
|
||||
<span>@RazorLocalizer["ExportAll"]</span>
|
||||
</Button>
|
||||
<Button class="dropdown-item" OnClick="() => ExcelExportAsync(ExportContext)" IsDisabled=@(!AuthorizeButton("导出"))>
|
||||
<i class="fas fa-file-export"></i>
|
||||
<span>@RazorLocalizer["TablesExportButtonExcelText"]</span>
|
||||
</Button>
|
||||
<Button class="dropdown-item" OnClick="() => ExcelImportAsync(ExportContext)" IsDisabled=@(!AuthorizeButton("导入"))>
|
||||
<i class="fas fa-file-import"></i>
|
||||
<span>@RazorLocalizer["TablesImportButtonExcelText"]</span>
|
||||
</Button>
|
||||
<Button class="dropdown-item" OnClick="() => ExcelChannelAsync(ExportContext)" IsDisabled=@(!AuthorizeButton("导入"))>
|
||||
<i class="fas fa-file-import"></i>
|
||||
<span>@GatewayLocalizer["ExcelChannel"]</span>
|
||||
</Button>
|
||||
</ExportButtonDropdownTemplate>
|
||||
<TableToolbarTemplate>
|
||||
|
||||
<TableToolbarButton TItem="ChannelRuntime" IsDisabled=@(!AuthorizeButton(AdminOperConst.Add))
|
||||
Color=Color.Success Text="@RazorLocalizer["Copy"]"
|
||||
OnClickCallback=@(Copy) />
|
||||
|
||||
<TableToolbarButton TItem="ChannelRuntime" IsDisabled=@(!AuthorizeButton(AdminOperConst.Edit))
|
||||
Color=Color.Info Text="@RazorLocalizer["BatchEdit"]"
|
||||
OnClickCallback=@(BatchEdit) />
|
||||
|
||||
<TableToolbarPopConfirmButton TItem="ChannelRuntime"
|
||||
Color=Color.Warning Text="@RazorLocalizer["Clear"]" IsDisabled=@(!AuthorizeButton(AdminOperConst.Delete))
|
||||
IsAsync OnConfirm=@(ClearAsync) />
|
||||
|
||||
</TableToolbarTemplate>
|
||||
</AdminTable>
|
||||
|
||||
@code {
|
||||
AdminTable<ChannelRuntime> table;
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class ChannelTable : IDisposable
|
||||
{
|
||||
|
||||
public bool Disposed { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<ChannelRuntime>? Items { get; set; } = Enumerable.Empty<ChannelRuntime>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
|
||||
private async Task RunTimerAsync()
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (table != null)
|
||||
await table.QueryAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 查询
|
||||
|
||||
private QueryPageOptions _option = new();
|
||||
private Task<QueryData<ChannelRuntime>> OnQueryAsync(QueryPageOptions options)
|
||||
{
|
||||
var data = Items
|
||||
.WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText))
|
||||
.GetQueryData(options);
|
||||
_option = options;
|
||||
return Task.FromResult(data);
|
||||
}
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 编辑
|
||||
|
||||
#region 修改
|
||||
private async Task Copy(IEnumerable<ChannelRuntime> channels)
|
||||
{
|
||||
|
||||
if (!channels.Any())
|
||||
{
|
||||
await ToastService.Warning(null, RazorLocalizer["PleaseSelect"]);
|
||||
return;
|
||||
}
|
||||
|
||||
Channel oneModel = null;
|
||||
Dictionary<Device, List<Variable>> deviceDict = new();
|
||||
var channelRuntime = channels.FirstOrDefault();
|
||||
oneModel = channelRuntime.Adapt<Channel>();
|
||||
oneModel.Id = 0;
|
||||
|
||||
deviceDict = channelRuntime.ReadDeviceRuntimes.ToDictionary(a => a.Value.Adapt<Device>(), a => a.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).Adapt<List<Variable>>());
|
||||
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = RazorLocalizer["Copy"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ChannelCopyComponent.OnSave), async (List<Channel> channels,Dictionary<Device,List<Variable>> devices) =>
|
||||
{
|
||||
|
||||
await Task.Run(() =>GlobalData.ChannelRuntimeService.CopyAsync(channels,devices,AutoRestartThread, default));
|
||||
await table.QueryAsync();
|
||||
|
||||
}},
|
||||
{nameof(ChannelCopyComponent.Model),oneModel },
|
||||
{nameof(ChannelCopyComponent.Devices),deviceDict },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async Task BatchEdit(IEnumerable<Channel> changedModels)
|
||||
{
|
||||
var oldModel = changedModels.FirstOrDefault();//默认值显示第一个
|
||||
if (oldModel == null)
|
||||
{
|
||||
await ToastService.Warning(null, RazorLocalizer["PleaseSelect"]);
|
||||
return;
|
||||
}
|
||||
|
||||
var oneModel = oldModel.Adapt<Channel>();//默认值显示第一个
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = RazorLocalizer["BatchEdit"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelEditComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ChannelEditComponent.OnValidSubmit), async () =>
|
||||
{
|
||||
await Task.Run(() => GlobalData.ChannelRuntimeService.BatchEditAsync(changedModels, oldModel, oneModel,AutoRestartThread));
|
||||
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
|
||||
} },
|
||||
{nameof(ChannelEditComponent.Model),oneModel },
|
||||
{nameof(ChannelEditComponent.ValidateEnable),true },
|
||||
{nameof(ChannelEditComponent.BatchEditEnable),true },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private async Task<bool> Delete(IEnumerable<Channel> channels)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
return await GlobalData.ChannelRuntimeService.DeleteChannelAsync(channels.Select(a => a.Id), AutoRestartThread, default);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> Save(Channel channel, ItemChangedType itemChangedType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(() => GlobalData.ChannelRuntimeService.SaveChannelAsync(channel, itemChangedType, AutoRestartThread));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 修改
|
||||
|
||||
private Task<ChannelRuntime> OnAdd()
|
||||
{
|
||||
return Task.FromResult(ChannelDeviceHelpers.GetChannelModel(ItemChangedType.Add, SelectModel).Adapt<ChannelRuntime>());
|
||||
}
|
||||
|
||||
#region 导出
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IGatewayExportService? GatewayExportService { get; set; }
|
||||
|
||||
private async Task ExcelExportAsync(ITableExportContext<ChannelRuntime> tableExportContext, bool all = false)
|
||||
{
|
||||
if (all)
|
||||
{
|
||||
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() });
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (SelectModel.ChannelDevicePluginType)
|
||||
{
|
||||
|
||||
case ChannelDevicePluginTypeEnum.PluginName:
|
||||
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName });
|
||||
break;
|
||||
case ChannelDevicePluginTypeEnum.Channel:
|
||||
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id });
|
||||
break;
|
||||
case ChannelDevicePluginTypeEnum.Device:
|
||||
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType });
|
||||
break;
|
||||
default:
|
||||
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 返回 true 时自动弹出提示框
|
||||
await ToastService.Default();
|
||||
}
|
||||
|
||||
async Task ExcelChannelAsync(ITableExportContext<ChannelRuntime> tableExportContext)
|
||||
{
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraExtraLarge,
|
||||
Title = GatewayLocalizer["ExcelChannel"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
var option = _option;
|
||||
option.IsPage = false;
|
||||
var models = Items
|
||||
.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(option.SearchText)).GetData(option, out var total).ToList();
|
||||
if (models.Count > 50000)
|
||||
{
|
||||
await ToastService.Warning("online Excel max data count 50000");
|
||||
return;
|
||||
}
|
||||
var uSheetDatas = ChannelServiceHelpers.ExportChannel(models);
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<USheet>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(USheet.OnSave), async (USheetDatas data) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(async ()=>
|
||||
{
|
||||
var importData=await ChannelServiceHelpers.ImportAsync(data);
|
||||
await GlobalData.ChannelRuntimeService.ImportChannelAsync(importData,AutoRestartThread);
|
||||
})
|
||||
;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await InvokeAsync( async ()=>
|
||||
{
|
||||
|
||||
await table.QueryAsync();
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}},
|
||||
{nameof(USheet.Model),uSheetDatas },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task ExcelImportAsync(ITableExportContext<ChannelRuntime> tableExportContext)
|
||||
{
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = true,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = GatewayLocalizer["ImportChannel"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
OnCloseAsync = async () =>
|
||||
{
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
},
|
||||
};
|
||||
|
||||
Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> preview = (a => GlobalData.ChannelRuntimeService.PreviewAsync(a));
|
||||
Func<Dictionary<string, ImportPreviewOutputBase>, Task> import = (value => GlobalData.ChannelRuntimeService.ImportChannelAsync(value, AutoRestartThread));
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ImportExcel.Import),import },
|
||||
{nameof(ImportExcel.Preview),preview },
|
||||
});
|
||||
await DialogService.Show(op);
|
||||
|
||||
}
|
||||
|
||||
#endregion 导出
|
||||
|
||||
#region 清空
|
||||
|
||||
private async Task ClearAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
|
||||
await GlobalData.ChannelRuntimeService.DeleteChannelAsync(Items.Select(a => a.Id), AutoRestartThread, default);
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await ToastService.Default();
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
[Parameter]
|
||||
public bool AutoRestartThread { get; set; }
|
||||
[Parameter]
|
||||
public ChannelDeviceTreeItem SelectModel { get; set; }
|
||||
[Inject]
|
||||
[NotNull]
|
||||
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports>? GatewayLocalizer { get; set; }
|
||||
#endregion
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.text-h6 {
|
||||
/* Headline 6 */
|
||||
font-family: Roboto !important;
|
||||
font-style: normal !important;
|
||||
font-weight: bold !important;
|
||||
font-size: 1rem !important;
|
||||
line-height: 1.875rem !important;
|
||||
/* identical to box height */
|
||||
letter-spacing: 0.01em !important;
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
/* Caption-说明 */
|
||||
font-family: Roboto !important;
|
||||
font-style: normal !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 0.75rem !important;
|
||||
line-height: 1.125rem !important;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Mapster;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor
|
||||
{
|
||||
internal static class ChannelDeviceHelpers
|
||||
{
|
||||
|
||||
internal static Channel GetChannelModel(ItemChangedType itemChangedType, ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||
{
|
||||
Channel oneModel = null;
|
||||
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
oneModel = channelRuntime.Adapt<Channel>();
|
||||
if (itemChangedType == ItemChangedType.Add)
|
||||
{
|
||||
oneModel.Id = 0;
|
||||
oneModel.Name = $"{oneModel.Name}-Copy";
|
||||
}
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
|
||||
{
|
||||
oneModel = deviceRuntime.ChannelRuntime?.Adapt<Channel>() ?? new();
|
||||
if (itemChangedType == ItemChangedType.Add)
|
||||
{
|
||||
oneModel.Id = 0;
|
||||
oneModel.Name = $"{oneModel.Name}-Copy";
|
||||
}
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
|
||||
{
|
||||
oneModel = new();
|
||||
oneModel.PluginName = pluginName;
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginType(out var pluginType))
|
||||
{
|
||||
oneModel = new();
|
||||
}
|
||||
return oneModel;
|
||||
}
|
||||
internal static Device GetDeviceModel(ItemChangedType itemChangedType, ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||
{
|
||||
Device oneModel = null;
|
||||
if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
|
||||
{
|
||||
oneModel = deviceRuntime.Adapt<Device>();
|
||||
if (itemChangedType == ItemChangedType.Add)
|
||||
{
|
||||
oneModel.Id = 0;
|
||||
oneModel.Name = $"{oneModel.Name}-Copy";
|
||||
}
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
oneModel = new();
|
||||
oneModel.Id = 0;
|
||||
oneModel.ChannelId = channelRuntime.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
oneModel = new();
|
||||
oneModel.Id = 0;
|
||||
}
|
||||
return oneModel;
|
||||
}
|
||||
|
||||
internal static PluginTypeEnum? GetPluginType(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||
{
|
||||
PluginTypeEnum? pluginTypeEnum = null;
|
||||
if (channelDeviceTreeItem.TryGetPluginType(out var pluginType))
|
||||
{
|
||||
pluginTypeEnum = pluginType;
|
||||
}
|
||||
return pluginTypeEnum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal static async Task ShowCopy(Channel oneModel, Dictionary<Device, List<Variable>> deviceDict, string text, bool autoRestart, Func<Task> onsave, DialogService dialogService)
|
||||
{
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = text,
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ChannelCopyComponent.OnSave), async (List<Channel> channels,Dictionary<Device,List<Variable>> devices) =>
|
||||
{
|
||||
|
||||
await Task.Run(() =>GlobalData.ChannelRuntimeService.CopyAsync(channels,devices,autoRestart, default));
|
||||
if(onsave!=null)
|
||||
await onsave();
|
||||
|
||||
}},
|
||||
{nameof(ChannelCopyComponent.Model),oneModel },
|
||||
{nameof(ChannelCopyComponent.Devices),deviceDict },
|
||||
});
|
||||
|
||||
await dialogService.Show(op);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@inherits ThingsGatewayModuleComponentBase
|
||||
@attribute [JSModuleAutoLoader("Pages/GatewayMonitorPage/ChannelDeviceTree.razor.js")]
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
|
||||
@@ -117,39 +117,17 @@ public partial class ChannelDeviceTree
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
PluginTypeEnum? pluginTypeEnum = null;
|
||||
Channel oneModel = null;
|
||||
if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return;
|
||||
|
||||
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
oneModel = channelRuntime.Adapt<Channel>();
|
||||
if (itemChangedType == ItemChangedType.Add)
|
||||
{
|
||||
oneModel.Id = 0;
|
||||
oneModel.Name = $"{oneModel.Name}-Copy";
|
||||
}
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
|
||||
{
|
||||
oneModel = new();
|
||||
oneModel.PluginName = pluginName;
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginType(out var pluginType))
|
||||
{
|
||||
oneModel = new();
|
||||
pluginTypeEnum = pluginType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return;
|
||||
PluginTypeEnum? pluginTypeEnum = ChannelDeviceHelpers.GetPluginType(channelDeviceTreeItem);
|
||||
var oneModel = ChannelDeviceHelpers.GetChannelModel(itemChangedType, channelDeviceTreeItem);
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelEditComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ChannelEditComponent.OnValidSubmit), async () =>
|
||||
{
|
||||
await Task.Run(() =>GlobalData.ChannelRuntimeService.SaveChannelAsync(oneModel,itemChangedType,AutoRestartThread));
|
||||
await Notify();
|
||||
}},
|
||||
{nameof(ChannelEditComponent.Model),oneModel },
|
||||
{nameof(ChannelEditComponent.ValidateEnable),true },
|
||||
@@ -163,15 +141,7 @@ public partial class ChannelDeviceTree
|
||||
|
||||
async Task CopyChannel(ContextMenuItem item, object value)
|
||||
{
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = item.Text,
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
Channel oneModel = null;
|
||||
Dictionary<Device, List<Variable>> deviceDict = new();
|
||||
if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return;
|
||||
@@ -188,25 +158,6 @@ public partial class ChannelDeviceTree
|
||||
return;
|
||||
}
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ChannelCopyComponent.OnSave), async (List<Channel> channels,Dictionary<Device,List<Variable>> devices) =>
|
||||
{
|
||||
|
||||
await Task.Run(() =>GlobalData.ChannelRuntimeService.CopyAsync(channels,devices,AutoRestartThread, default));
|
||||
|
||||
}},
|
||||
{nameof(ChannelCopyComponent.Model),oneModel },
|
||||
{nameof(ChannelCopyComponent.Devices),deviceDict },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
}
|
||||
|
||||
|
||||
async Task BatchEditChannel(ContextMenuItem item, object value)
|
||||
{
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
@@ -218,6 +169,30 @@ public partial class ChannelDeviceTree
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ChannelCopyComponent.OnSave), async (List<Channel> channels,Dictionary<Device,List<Variable>> devices) =>
|
||||
{
|
||||
|
||||
await Task.Run(() =>GlobalData.ChannelRuntimeService.CopyAsync(channels,devices,AutoRestartThread, default));
|
||||
await Notify();
|
||||
|
||||
}},
|
||||
{nameof(ChannelCopyComponent.Model),oneModel },
|
||||
{nameof(ChannelCopyComponent.Devices),deviceDict },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
async Task BatchEditChannel(ContextMenuItem item, object value)
|
||||
{
|
||||
|
||||
|
||||
|
||||
Channel oldModel = null;
|
||||
Channel oneModel = null;
|
||||
IEnumerable<Channel>? changedModels = null;
|
||||
@@ -257,21 +232,31 @@ public partial class ChannelDeviceTree
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = item.Text,
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelEditComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ChannelEditComponent.OnValidSubmit), async () =>
|
||||
{
|
||||
Spinner.SetRun(true);
|
||||
Spinner.SetRun(true);
|
||||
await Task.Run(() => GlobalData.ChannelRuntimeService.BatchEditAsync(changedModels, oldModel, oneModel,AutoRestartThread));
|
||||
await InvokeAsync( ()=>
|
||||
{
|
||||
|
||||
Spinner.SetRun(false);
|
||||
StateHasChanged();
|
||||
await Notify();
|
||||
await InvokeAsync(() =>
|
||||
{
|
||||
|
||||
Spinner.SetRun(false);
|
||||
});
|
||||
}},
|
||||
|
||||
} },
|
||||
{nameof(ChannelEditComponent.Model),oneModel },
|
||||
{nameof(ChannelEditComponent.ValidateEnable),true },
|
||||
{nameof(ChannelEditComponent.BatchEditEnable),true },
|
||||
@@ -279,6 +264,7 @@ public partial class ChannelDeviceTree
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -315,11 +301,11 @@ public partial class ChannelDeviceTree
|
||||
}
|
||||
finally
|
||||
{
|
||||
await InvokeAsync( ()=>
|
||||
await Notify();
|
||||
await InvokeAsync( ()=>
|
||||
{
|
||||
|
||||
Spinner.SetRun(false);
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -433,10 +419,10 @@ finally
|
||||
Spinner.SetRun(true);
|
||||
|
||||
await Task.Run(() => GlobalData.ChannelRuntimeService.DeleteChannelAsync(modelIds.Select(a => a.Id), AutoRestartThread, default));
|
||||
await Notify();
|
||||
await InvokeAsync(() =>
|
||||
{
|
||||
Spinner.SetRun(false);
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -477,10 +463,10 @@ finally
|
||||
|
||||
var key = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
await Task.Run(() => GlobalData.ChannelRuntimeService.DeleteChannelAsync(key.Select(a => a.Id), AutoRestartThread, default));
|
||||
await Notify();
|
||||
await InvokeAsync(() =>
|
||||
{
|
||||
Spinner.SetRun(false);
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -599,10 +585,10 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
|
||||
});
|
||||
await Task.Run(() => GlobalData.ChannelRuntimeService.ImportChannelAsync(value, AutoRestartThread));
|
||||
await Notify();
|
||||
await InvokeAsync(() =>
|
||||
{
|
||||
Spinner.SetRun(false);
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -654,6 +640,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
{
|
||||
|
||||
await Task.Run(() =>GlobalData.DeviceRuntimeService.CopyAsync(devices,AutoRestartThread, default));
|
||||
await Notify();
|
||||
|
||||
}},
|
||||
{nameof(DeviceCopyComponent.Model),oneModel },
|
||||
@@ -703,6 +690,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
{nameof(DeviceEditComponent.OnValidSubmit), async () =>
|
||||
{
|
||||
await Task.Run(() =>GlobalData.DeviceRuntimeService.SaveDeviceAsync(oneModel,itemChangedType, AutoRestartThread));
|
||||
await Notify();
|
||||
}},
|
||||
{nameof(DeviceEditComponent.Model),oneModel },
|
||||
{nameof(DeviceEditComponent.AutoRestartThread),AutoRestartThread },
|
||||
@@ -836,11 +824,11 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Notify();
|
||||
await InvokeAsync( ()=>
|
||||
{
|
||||
|
||||
Spinner.SetRun(false);
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1139,7 +1127,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
await Notify();
|
||||
await InvokeAsync(() =>
|
||||
{
|
||||
|
||||
Spinner.SetRun(false);
|
||||
});
|
||||
|
||||
@@ -1157,13 +1144,13 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
[Inject]
|
||||
SwalService SwalService { get; set; }
|
||||
[Inject]
|
||||
ToastService ToastService { get; set; }
|
||||
|
||||
|
||||
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public ChannelDeviceTreeItem Value { get; set; }
|
||||
@@ -1309,6 +1296,10 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
{
|
||||
if (Disposed) return;
|
||||
await OnClickSearch(SearchText);
|
||||
if (ChannelDeviceChanged != null)
|
||||
{
|
||||
await ChannelDeviceChanged.Invoke(Value);
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
export function getShowType() {
|
||||
return JSON.parse(localStorage.getItem('showType'))??0;
|
||||
}
|
||||
export function saveShowType(showType) {
|
||||
if (localStorage) {
|
||||
localStorage.setItem('showType', JSON.stringify(showType));
|
||||
}
|
||||
}
|
||||
@@ -10,125 +10,7 @@
|
||||
@if (ValidateEnable)
|
||||
{
|
||||
<ValidateForm Model="Model" OnValidSubmit="ValidSubmit">
|
||||
<Tab>
|
||||
|
||||
<TabItem Text=@GatewayLocalizer["DeviceInformation"]>
|
||||
|
||||
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=2 LabelWidth=200 Model="Model">
|
||||
|
||||
<FieldItems>
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["BasicInformation"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.Name" Readonly=BatchEditEnable />
|
||||
|
||||
<EditorItem @bind-Field="@context.Description" />
|
||||
|
||||
<EditorItem @bind-Field="@context.Enable" />
|
||||
<EditorItem @bind-Field="@context.LogEnable" />
|
||||
<EditorItem @bind-Field="@context.LogLevel" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Connection"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.ChannelId">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-6 ">
|
||||
<BootstrapInputGroup>
|
||||
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyChannels.TryGetValue(value.ChannelId,out var channelRuntime)?channelRuntime.Name:string.Empty) @bind-Value="@value.ChannelId" IsDisabled=BatchEditEnable Items="@_channelItems" OnSelectedItemChanged=OnChannelChanged ShowSearch="true" ShowLabel="true" />
|
||||
<Button IsDisabled=BatchEditEnable class="text-end" Icon="fa-solid fa-plus" OnClick="AddChannel"></Button>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.IntervalTime" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Description">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Redundant"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.RedundantEnable" Readonly=BatchEditEnable />
|
||||
<EditorItem @bind-Field="@context.RedundantDeviceId">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-6">
|
||||
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyIdDevices.TryGetValue(value.RedundantDeviceId??0,out var deviceRuntime)?deviceRuntime.Name:string.Empty) @bind-Value="@value.RedundantDeviceId" class="w-100"
|
||||
OnQueryAsync="(a)=>OnRedundantDevicesQuery(a,value)" IsDisabled="BatchEditEnable"
|
||||
ShowSearch="true" IsClearable OnClearAsync=@(()=>
|
||||
{
|
||||
value.RedundantDeviceId = default;
|
||||
return Task.CompletedTask;
|
||||
})>
|
||||
<DisplayTemplate Context="display">
|
||||
@{
|
||||
string device = "none";
|
||||
if (value.RedundantDeviceId != null)
|
||||
{
|
||||
if (value.RedundantDeviceId.HasValue)
|
||||
if (GlobalData.ReadOnlyIdDevices.TryGetValue(value.RedundantDeviceId.Value, out var deviceRuntime))
|
||||
device = deviceRuntime?.Name ?? device;
|
||||
}
|
||||
@device
|
||||
}
|
||||
</DisplayTemplate>
|
||||
</Select>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.RedundantSwitchType" />
|
||||
<EditorItem @bind-Field="@context.RedundantScanIntervalTime" />
|
||||
<EditorItem @bind-Field="@context.RedundantScript" Rows="1" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Description">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Remark"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.Remark1" />
|
||||
<EditorItem @bind-Field="@context.Remark2" />
|
||||
<EditorItem @bind-Field="@context.Remark3" />
|
||||
<EditorItem @bind-Field="@context.Remark4" />
|
||||
<EditorItem @bind-Field="@context.Remark5" />
|
||||
|
||||
</FieldItems>
|
||||
|
||||
</EditorForm>
|
||||
</TabItem>
|
||||
@if (!BatchEditEnable)
|
||||
{
|
||||
<TabItem Text=@GatewayLocalizer["PluginInformation"]>
|
||||
@if (PluginPropertyModel != null && PluginPropertyEditorItems != null)
|
||||
{
|
||||
if (PluginPropertyRenderFragment == null)
|
||||
{
|
||||
<PropertyComponent Model="PluginPropertyModel" PluginPropertyEditorItems="PluginPropertyEditorItems" Id=@(Model.Id.ToString()) CanWrite="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@PluginPropertyRenderFragment
|
||||
}
|
||||
}
|
||||
</TabItem>
|
||||
}
|
||||
|
||||
</Tab>
|
||||
@renderFragment
|
||||
|
||||
<div class="form-footer">
|
||||
|
||||
@@ -137,4 +19,132 @@
|
||||
</ValidateForm>
|
||||
|
||||
}
|
||||
</div>
|
||||
else
|
||||
{
|
||||
@renderFragment
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
RenderFragment renderFragment =>
|
||||
@<Tab>
|
||||
|
||||
<TabItem Text=@GatewayLocalizer["DeviceInformation"]>
|
||||
|
||||
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=2 LabelWidth=200 Model="Model">
|
||||
|
||||
<FieldItems>
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["BasicInformation"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.Name" Readonly=BatchEditEnable />
|
||||
|
||||
<EditorItem @bind-Field="@context.Description" />
|
||||
|
||||
<EditorItem @bind-Field="@context.Enable" />
|
||||
<EditorItem @bind-Field="@context.LogEnable" />
|
||||
<EditorItem @bind-Field="@context.LogLevel" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Name">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Connection"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.ChannelId">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-6 ">
|
||||
<BootstrapInputGroup>
|
||||
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyChannels.TryGetValue(value.ChannelId,out var channelRuntime)?channelRuntime.Name:string.Empty) @bind-Value="@value.ChannelId" IsDisabled=BatchEditEnable Items="@_channelItems" OnSelectedItemChanged=OnChannelChanged ShowSearch="true" ShowLabel="true" />
|
||||
<Button IsDisabled=BatchEditEnable class="text-end" Icon="fa-solid fa-plus" OnClick="AddChannel"></Button>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.IntervalTime" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Description">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Redundant"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.RedundantEnable" Readonly=BatchEditEnable />
|
||||
<EditorItem @bind-Field="@context.RedundantDeviceId">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-6">
|
||||
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyIdDevices.TryGetValue(value.RedundantDeviceId??0,out var deviceRuntime)?deviceRuntime.Name:string.Empty) @bind-Value="@value.RedundantDeviceId" class="w-100"
|
||||
OnQueryAsync="(a)=>OnRedundantDevicesQuery(a,value)" IsDisabled="BatchEditEnable"
|
||||
ShowSearch="true" IsClearable OnClearAsync=@(()=>
|
||||
{
|
||||
value.RedundantDeviceId = default;
|
||||
return Task.CompletedTask;
|
||||
})>
|
||||
<DisplayTemplate Context="display">
|
||||
@{
|
||||
string device = "none";
|
||||
if (value.RedundantDeviceId != null)
|
||||
{
|
||||
if (value.RedundantDeviceId.HasValue)
|
||||
if (GlobalData.ReadOnlyIdDevices.TryGetValue(value.RedundantDeviceId.Value, out var deviceRuntime))
|
||||
device = deviceRuntime?.Name ?? device;
|
||||
}
|
||||
@device
|
||||
}
|
||||
</DisplayTemplate>
|
||||
</Select>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.RedundantSwitchType" />
|
||||
<EditorItem @bind-Field="@context.RedundantScanIntervalTime" />
|
||||
<EditorItem @bind-Field="@context.RedundantScript" Rows="1" />
|
||||
|
||||
<EditorItem TValue="string" TModel="Device" @bind-Field="@context.Description">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6>@GatewayLocalizer["Remark"]</h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.Remark1" />
|
||||
<EditorItem @bind-Field="@context.Remark2" />
|
||||
<EditorItem @bind-Field="@context.Remark3" />
|
||||
<EditorItem @bind-Field="@context.Remark4" />
|
||||
<EditorItem @bind-Field="@context.Remark5" />
|
||||
|
||||
</FieldItems>
|
||||
|
||||
</EditorForm>
|
||||
</TabItem>
|
||||
@if (!BatchEditEnable)
|
||||
{
|
||||
<TabItem Text=@GatewayLocalizer["PluginInformation"]>
|
||||
@if (PluginPropertyModel != null && PluginPropertyEditorItems != null)
|
||||
{
|
||||
if (PluginPropertyRenderFragment == null)
|
||||
{
|
||||
<PropertyComponent Model="PluginPropertyModel" PluginPropertyEditorItems="PluginPropertyEditorItems" Id=@(Model.Id.ToString()) CanWrite="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@PluginPropertyRenderFragment
|
||||
}
|
||||
}
|
||||
</TabItem>
|
||||
}
|
||||
|
||||
</Tab>;
|
||||
|
||||
}
|
||||
@@ -132,7 +132,7 @@ public partial class DeviceEditComponent
|
||||
try
|
||||
{
|
||||
var pluginName = GlobalData.ReadOnlyChannels.TryGetValue(selectedItem.Value.ToLong(), out var channel) ? channel.PluginName : string.Empty;
|
||||
|
||||
if (pluginName.IsNullOrEmpty()) return;
|
||||
var data = GlobalData.PluginService.GetDriverPropertyTypes(pluginName);
|
||||
PluginPropertyModel = new ModelValueValidateForm() { Value = data.Model };
|
||||
PluginPropertyEditorItems = data.EditorItems;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Debug
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@inherits ComponentDefault
|
||||
|
||||
<AdminTable @ref=table
|
||||
TItem="DeviceRuntime"
|
||||
EditDialogSize="Size.ExtraLarge"
|
||||
AutoGenerateColumns="false"
|
||||
ShowAdvancedSearch=false
|
||||
ScrollingDialogContent=false
|
||||
AllowResizing="true"
|
||||
OnAdd="OnAdd"
|
||||
IsFixedHeader=true
|
||||
IsMultipleSelect=true
|
||||
SearchMode=SearchMode.Top
|
||||
ShowExtendButtons=true
|
||||
ShowToolbar="true"
|
||||
ShowExportButton
|
||||
ShowDefaultButtons=true
|
||||
ShowSearch=false
|
||||
ShowExtendEditButton="true"
|
||||
ShowExtendDeleteButton="true"
|
||||
ExtendButtonColumnWidth=220
|
||||
OnSaveAsync="Save"
|
||||
OnDeleteAsync="Delete"
|
||||
OnQueryAsync="OnQueryAsync"
|
||||
IsPagination=true>
|
||||
<TableColumns>
|
||||
|
||||
<TableColumn Field="@context.Name" FieldExpression=@(()=>context.Name) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn Field="@context.Description" FieldExpression=@(()=>context.Description) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn Field="@context.IntervalTime" FieldExpression=@(()=>context.IntervalTime) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.ChannelName" FieldExpression=@(()=>context.ChannelName) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn @bind-Field="@context.Enable" Filterable=true Sortable=true Visible="true" />
|
||||
<TableColumn @bind-Field="@context.LogEnable" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.LogLevel" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.Remark1" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.Remark2" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.Remark3" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.Remark4" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.Remark5" Filterable=true Sortable=true Visible="false" />
|
||||
|
||||
<TableColumn Field="@context.ActiveTime" FieldExpression=@(()=>context.ActiveTime) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn Field="@context.DeviceStatus" FieldExpression=@(()=>context.DeviceStatus) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
|
||||
<TableColumn Field="@context.Pause" FieldExpression=@(()=>context.Pause) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.LastErrorMessage" FieldExpression=@(()=>context.LastErrorMessage) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
|
||||
<TableColumn Field="@context.PluginName" FieldExpression=@(()=>context.PluginName) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
|
||||
<TableColumn Field="@context.DeviceVariableCount" FieldExpression=@(()=>context.DeviceVariableCount) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn Field="@context.MethodVariableCount" FieldExpression=@(()=>context.MethodVariableCount) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn Field="@context.SourceVariableCount" FieldExpression=@(()=>context.SourceVariableCount) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
|
||||
<TableColumn Field="@context.ChannelId" FieldExpression=@(()=>context.ChannelId) ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.RedundantEnable" FieldExpression=@(()=>context.RedundantEnable) Ignore="true" ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.RedundantType" FieldExpression=@(()=>context.RedundantType) Ignore="true" ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.RedundantDeviceId" FieldExpression=@(()=>context.RedundantDeviceId) Ignore="true" ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.RedundantScanIntervalTime" FieldExpression=@(()=>context.RedundantScanIntervalTime) Ignore="true" ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.RedundantScript" FieldExpression=@(()=>context.RedundantScript) Ignore="true" ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
<TableColumn Field="@context.RedundantSwitchType" FieldExpression=@(()=>context.RedundantSwitchType) Ignore="true" ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||
|
||||
|
||||
<TableColumn @bind-Field="@context.Id" Filterable=true Sortable=true Visible="false" DefaultSort=true DefaultSortOrder="SortOrder.Asc" />
|
||||
|
||||
</TableColumns>
|
||||
|
||||
<EditTemplate Context="context">
|
||||
<DeviceEditComponent Model=@(context) ValidateEnable=false BatchEditEnable=false AutoRestartThread=AutoRestartThread ></DeviceEditComponent>
|
||||
</EditTemplate>
|
||||
|
||||
|
||||
|
||||
<ExportButtonDropdownTemplate Context="ExportContext">
|
||||
<Button class="dropdown-item" OnClick="() => ExcelExportAsync(ExportContext,true)" IsDisabled=@(!AuthorizeButton("导出"))>
|
||||
<i class="fas fa-file-export"></i>
|
||||
<span>@RazorLocalizer["ExportAll"]</span>
|
||||
</Button>
|
||||
<Button class="dropdown-item" OnClick="() => ExcelExportAsync(ExportContext)" IsDisabled=@(!AuthorizeButton("导出"))>
|
||||
<i class="fas fa-file-export"></i>
|
||||
<span>@RazorLocalizer["TablesExportButtonExcelText"]</span>
|
||||
</Button>
|
||||
<Button class="dropdown-item" OnClick="() => ExcelImportAsync(ExportContext)" IsDisabled=@(!AuthorizeButton("导入"))>
|
||||
<i class="fas fa-file-import"></i>
|
||||
<span>@RazorLocalizer["TablesImportButtonExcelText"]</span>
|
||||
</Button>
|
||||
<Button class="dropdown-item" OnClick="() => ExcelDeviceAsync(ExportContext)" IsDisabled=@(!AuthorizeButton("导入"))>
|
||||
<i class="fas fa-file-import"></i>
|
||||
<span>@GatewayLocalizer["ExcelDevice"]</span>
|
||||
</Button>
|
||||
</ExportButtonDropdownTemplate>
|
||||
<TableToolbarTemplate>
|
||||
|
||||
<TableToolbarButton TItem="DeviceRuntime" IsDisabled=@(!AuthorizeButton(AdminOperConst.Add))
|
||||
Color=Color.Success Text="@RazorLocalizer["Copy"]"
|
||||
OnClickCallback=@(Copy) />
|
||||
|
||||
<TableToolbarButton TItem="DeviceRuntime" IsDisabled=@(!AuthorizeButton(AdminOperConst.Edit))
|
||||
Color=Color.Info Text="@RazorLocalizer["BatchEdit"]"
|
||||
OnClickCallback=@(BatchEdit) />
|
||||
|
||||
<TableToolbarPopConfirmButton TItem="DeviceRuntime"
|
||||
Color=Color.Warning Text="@RazorLocalizer["Clear"]" IsDisabled=@(!AuthorizeButton(AdminOperConst.Delete))
|
||||
IsAsync OnConfirm=@(ClearAsync) />
|
||||
|
||||
</TableToolbarTemplate>
|
||||
</AdminTable>
|
||||
|
||||
@code {
|
||||
AdminTable<DeviceRuntime> table;
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class DeviceTable : IDisposable
|
||||
{
|
||||
|
||||
public bool Disposed { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<DeviceRuntime>? Items { get; set; } = Enumerable.Empty<DeviceRuntime>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
|
||||
private async Task RunTimerAsync()
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (table != null)
|
||||
await table.QueryAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 查询
|
||||
|
||||
private QueryPageOptions _option = new();
|
||||
private Task<QueryData<DeviceRuntime>> OnQueryAsync(QueryPageOptions options)
|
||||
{
|
||||
var data = Items
|
||||
.WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText))
|
||||
.GetQueryData(options);
|
||||
_option = options;
|
||||
return Task.FromResult(data);
|
||||
}
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 编辑
|
||||
|
||||
#region 修改
|
||||
private async Task Copy(IEnumerable<DeviceRuntime> devices)
|
||||
{
|
||||
|
||||
if (!devices.Any())
|
||||
{
|
||||
await ToastService.Warning(null, RazorLocalizer["PleaseSelect"]);
|
||||
return;
|
||||
}
|
||||
|
||||
Device oneModel = null;
|
||||
List<Variable> variables = new();
|
||||
var deviceRuntime = devices.FirstOrDefault();
|
||||
oneModel = deviceRuntime.Adapt<Device>();
|
||||
oneModel.Id = 0;
|
||||
|
||||
variables = deviceRuntime.ReadOnlyVariableRuntimes.Select(a => a.Value).Adapt<List<Variable>>();
|
||||
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = RazorLocalizer["Copy"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<DeviceCopyComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(DeviceCopyComponent.OnSave), async (Dictionary<Device,List<Variable>> devices) =>
|
||||
{
|
||||
|
||||
await Task.Run(() =>GlobalData.DeviceRuntimeService.CopyAsync(devices,AutoRestartThread, default));
|
||||
await table.QueryAsync();
|
||||
|
||||
}},
|
||||
{nameof(DeviceCopyComponent.Model),oneModel },
|
||||
{nameof(DeviceCopyComponent.Variables),variables },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async Task BatchEdit(IEnumerable<Device> changedModels)
|
||||
{
|
||||
var oldModel = changedModels.FirstOrDefault();//默认值显示第一个
|
||||
if (oldModel == null)
|
||||
{
|
||||
await ToastService.Warning(null, RazorLocalizer["PleaseSelect"]);
|
||||
return;
|
||||
}
|
||||
|
||||
var oneModel = oldModel.Adapt<Device>();//默认值显示第一个
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = RazorLocalizer["BatchEdit"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<DeviceEditComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(DeviceEditComponent.OnValidSubmit), async () =>
|
||||
{
|
||||
await Task.Run(() => GlobalData.DeviceRuntimeService.BatchEditAsync(changedModels, oldModel, oneModel,AutoRestartThread));
|
||||
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
|
||||
} },
|
||||
{nameof(DeviceEditComponent.Model),oneModel },
|
||||
{nameof(DeviceEditComponent.ValidateEnable),true },
|
||||
{nameof(DeviceEditComponent.BatchEditEnable),true },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private async Task<bool> Delete(IEnumerable<Device> devices)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
return await GlobalData.DeviceRuntimeService.DeleteDeviceAsync(devices.Select(a => a.Id), AutoRestartThread, default);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> Save(Device device, ItemChangedType itemChangedType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(() => GlobalData.DeviceRuntimeService.SaveDeviceAsync(device, itemChangedType, AutoRestartThread));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 修改
|
||||
|
||||
private Task<DeviceRuntime> OnAdd()
|
||||
{
|
||||
return Task.FromResult(ChannelDeviceHelpers.GetDeviceModel(ItemChangedType.Add, SelectModel).Adapt<DeviceRuntime>());
|
||||
}
|
||||
|
||||
|
||||
#region 导出
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IGatewayExportService? GatewayExportService { get; set; }
|
||||
|
||||
private async Task ExcelExportAsync(ITableExportContext<DeviceRuntime> tableExportContext, bool all = false)
|
||||
{
|
||||
if (all)
|
||||
{
|
||||
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() });
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (SelectModel.ChannelDevicePluginType)
|
||||
{
|
||||
|
||||
case ChannelDevicePluginTypeEnum.PluginName:
|
||||
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName });
|
||||
break;
|
||||
case ChannelDevicePluginTypeEnum.Channel:
|
||||
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id });
|
||||
break;
|
||||
case ChannelDevicePluginTypeEnum.Device:
|
||||
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType });
|
||||
break;
|
||||
default:
|
||||
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 返回 true 时自动弹出提示框
|
||||
await ToastService.Default();
|
||||
}
|
||||
|
||||
async Task ExcelDeviceAsync(ITableExportContext<DeviceRuntime> tableExportContext)
|
||||
{
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraExtraLarge,
|
||||
Title = GatewayLocalizer["ExcelDevice"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
|
||||
var option = _option;
|
||||
option.IsPage = false;
|
||||
var models = Items
|
||||
.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(option.SearchText)).GetData(option, out var total).ToList();
|
||||
if (models.Count > 50000)
|
||||
{
|
||||
await ToastService.Warning("online Excel max data count 50000");
|
||||
return;
|
||||
}
|
||||
var uSheetDatas = await DeviceServiceHelpers.ExportDeviceAsync(models);
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<USheet>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(USheet.OnSave), async (USheetDatas data) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(async ()=>
|
||||
{
|
||||
var importData=await DeviceServiceHelpers.ImportAsync(data);
|
||||
await GlobalData.DeviceRuntimeService.ImportDeviceAsync(importData,AutoRestartThread);
|
||||
})
|
||||
;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await InvokeAsync( async ()=>
|
||||
{
|
||||
|
||||
await table.QueryAsync();
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}},
|
||||
{nameof(USheet.Model),uSheetDatas },
|
||||
});
|
||||
|
||||
await DialogService.Show(op);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task ExcelImportAsync(ITableExportContext<DeviceRuntime> tableExportContext)
|
||||
{
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = true,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = GatewayLocalizer["ImportDevice"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
OnCloseAsync = async () =>
|
||||
{
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
},
|
||||
};
|
||||
|
||||
Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> preview = (a => GlobalData.DeviceRuntimeService.PreviewAsync(a));
|
||||
Func<Dictionary<string, ImportPreviewOutputBase>, Task> import = (value => GlobalData.DeviceRuntimeService.ImportDeviceAsync(value, AutoRestartThread));
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ImportExcel.Import),import },
|
||||
{nameof(ImportExcel.Preview),preview },
|
||||
});
|
||||
await DialogService.Show(op);
|
||||
|
||||
}
|
||||
|
||||
#endregion 导出
|
||||
|
||||
#region 清空
|
||||
|
||||
private async Task ClearAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
|
||||
await GlobalData.DeviceRuntimeService.DeleteDeviceAsync(Items.Select(a => a.Id), AutoRestartThread, default);
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await ToastService.Default();
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
[Parameter]
|
||||
public bool AutoRestartThread { get; set; }
|
||||
[Parameter]
|
||||
public ChannelDeviceTreeItem SelectModel { get; set; }
|
||||
[Inject]
|
||||
[NotNull]
|
||||
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports>? GatewayLocalizer { get; set; }
|
||||
#endregion
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.text-h6 {
|
||||
/* Headline 6 */
|
||||
font-family: Roboto !important;
|
||||
font-style: normal !important;
|
||||
font-weight: bold !important;
|
||||
font-size: 1rem !important;
|
||||
line-height: 1.875rem !important;
|
||||
/* identical to box height */
|
||||
letter-spacing: 0.01em !important;
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
/* Caption-说明 */
|
||||
font-family: Roboto !important;
|
||||
font-style: normal !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 0.75rem !important;
|
||||
line-height: 1.125rem !important;
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
|
||||
|
||||
@if (ShowType == ShowTypeEnum.Variable)
|
||||
@if (ShowType == ShowTypeEnum.VariableTable)
|
||||
{
|
||||
<VariableRuntimeInfo Items="VariableRuntimes" SelectModel="SelectModel" AutoRestartThread="AutoRestartThread" />
|
||||
}
|
||||
else
|
||||
else if (ShowType == ShowTypeEnum.LogInfo)
|
||||
{
|
||||
if (GlobalData.ReadOnlyIdDevices.TryGetValue(ShowDeviceRuntime, out var device))
|
||||
{
|
||||
@@ -20,3 +20,12 @@ else
|
||||
<ChannelRuntimeInfo ChannelRuntime="channel" />
|
||||
}
|
||||
}
|
||||
else if (ShowType == ShowTypeEnum.ChannelTable)
|
||||
{
|
||||
<ChannelTable SelectModel="SelectModel" Items="ChannelRuntimes" AutoRestartThread=AutoRestartThread />
|
||||
}
|
||||
|
||||
else if (ShowType == ShowTypeEnum.DeviceTable)
|
||||
{
|
||||
<DeviceTable SelectModel="SelectModel" Items="DeviceRuntimes" AutoRestartThread=AutoRestartThread />
|
||||
}
|
||||
@@ -15,11 +15,16 @@ namespace ThingsGateway.Gateway.Razor;
|
||||
public partial class GatewayInfo
|
||||
{
|
||||
[Parameter]
|
||||
public ChannelDeviceTreeItem SelectModel { get; set; } = new() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.PluginType, PluginType = PluginTypeEnum.Collect };
|
||||
public ChannelDeviceTreeItem SelectModel { get; set; } = new() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.PluginType, PluginType = null };
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<VariableRuntime> VariableRuntimes { get; set; } = Enumerable.Empty<VariableRuntime>();
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
|
||||
[Parameter]
|
||||
public long ShowChannelRuntime { get; set; }
|
||||
[Parameter]
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
<FirstPaneTemplate>
|
||||
|
||||
|
||||
<Card IsShadow=true class="h-100" Color="Color.Primary">
|
||||
<Card IsShadow=true class="h-100 me-1" Color="Color.Primary">
|
||||
<BodyTemplate>
|
||||
<ChannelDeviceTree @bind-ShowType=ShowType AutoRestartThread="AutoRestartThread" ChannelDeviceChanged="TreeChangedAsync" Value="SelectModel"></ChannelDeviceTree>
|
||||
<ChannelDeviceTree @bind-ShowType=ShowType AutoRestartThread="AutoRestartThread"
|
||||
ChannelDeviceChanged="TreeChangedAsync" Value="SelectModel"></ChannelDeviceTree>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
@@ -23,7 +24,9 @@
|
||||
</FirstPaneTemplate>
|
||||
<SecondPaneTemplate>
|
||||
|
||||
<GatewayInfo AutoRestartThread=AutoRestartThread SelectModel=SelectModel ShowChannelRuntime=ShowChannelRuntime ShowDeviceRuntime=ShowDeviceRuntime ShowType=ShowType VariableRuntimes=VariableRuntimes />
|
||||
<div class="h-100 ms-1">
|
||||
<GatewayInfo AutoRestartThread=AutoRestartThread SelectModel=SelectModel ShowChannelRuntime=ShowChannelRuntime ShowDeviceRuntime=ShowDeviceRuntime ShowType=ShowType VariableRuntimes=VariableRuntimes ChannelRuntimes="ChannelRuntimes" DeviceRuntimes="DeviceRuntimes" />
|
||||
</div>
|
||||
|
||||
</SecondPaneTemplate>
|
||||
</Split>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class GatewayMonitorPage
|
||||
{
|
||||
private ChannelDeviceTreeItem SelectModel { get; set; } = new() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.PluginType, PluginType = PluginTypeEnum.Collect };
|
||||
private ChannelDeviceTreeItem SelectModel { get; set; } = new() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.PluginType, PluginType = null };
|
||||
|
||||
#region 查询
|
||||
|
||||
@@ -24,9 +24,13 @@ public partial class GatewayMonitorPage
|
||||
ShowChannelRuntime = 0;
|
||||
ShowDeviceRuntime = 0;
|
||||
SelectModel = channelDeviceTreeItem;
|
||||
var variables = await GlobalData.GetCurrentUserIdVariables().ConfigureAwait(false);
|
||||
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
|
||||
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
ShowChannelRuntime = channelRuntime.Id;
|
||||
|
||||
if (channelRuntime.IsCollect == true)
|
||||
{
|
||||
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.SelectMany(a => a.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).Where(a => a != null));
|
||||
@@ -35,7 +39,8 @@ public partial class GatewayMonitorPage
|
||||
{
|
||||
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.Where(a => a.Value?.Driver?.IdVariableRuntimes != null).SelectMany(a => a.Value?.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)?.Select(a => a.Value)).Where(a => a != null);
|
||||
}
|
||||
|
||||
ChannelRuntimes = Enumerable.Repeat(channelRuntime, 1);
|
||||
DeviceRuntimes = channelRuntime.ReadDeviceRuntimes.Select(a => a.Value);
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
|
||||
{
|
||||
@@ -49,26 +54,43 @@ public partial class GatewayMonitorPage
|
||||
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
|
||||
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
|
||||
}
|
||||
|
||||
ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
|
||||
DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
|
||||
{
|
||||
var pluginType = GlobalData.PluginService.GetList().FirstOrDefault(a => a.FullName == pluginName)?.PluginType;
|
||||
if (pluginType == PluginTypeEnum.Collect)
|
||||
{
|
||||
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
VariableRuntimes = channels.Where(a => a.PluginName == pluginName).SelectMany(a => a.ReadDeviceRuntimes).SelectMany(a => a.Value.ReadOnlyVariableRuntimes).Select(a => a.Value).Where(a => a != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
VariableRuntimes = channels.Where(a => a.PluginName == pluginName).SelectMany(a => a.ReadDeviceRuntimes).Where(a => a.Value.Driver?.IdVariableRuntimes != null).SelectMany(a => a.Value.Driver?.IdVariableRuntimes).Select(a => a.Value);
|
||||
}
|
||||
|
||||
|
||||
ChannelRuntimes = channels.Where(a => a.PluginName == pluginName);
|
||||
DeviceRuntimes = devices.Where(a => a.PluginName == pluginName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var variables = await GlobalData.GetCurrentUserIdVariables().ConfigureAwait(false);
|
||||
VariableRuntimes = variables.Where(a => a != null);
|
||||
|
||||
if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
|
||||
{
|
||||
if (pluginTypeEnum != null)
|
||||
{
|
||||
ChannelRuntimes = channels.Where(a => a.PluginType == pluginTypeEnum);
|
||||
DeviceRuntimes = devices.Where(a => a.PluginType == pluginTypeEnum);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChannelRuntimes = channels;
|
||||
DeviceRuntimes = devices;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
@@ -82,6 +104,10 @@ public partial class GatewayMonitorPage
|
||||
}
|
||||
public IEnumerable<VariableRuntime> VariableRuntimes { get; set; } = Enumerable.Empty<VariableRuntime>();
|
||||
|
||||
public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
|
||||
public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
|
||||
|
||||
|
||||
private long ShowChannelRuntime { get; set; }
|
||||
private long ShowDeviceRuntime { get; set; }
|
||||
public ShowTypeEnum? ShowType { get; set; }
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public enum ShowTypeEnum
|
||||
{
|
||||
Variable,
|
||||
LogInfo
|
||||
LogInfo,
|
||||
ChannelTable,
|
||||
DeviceTable,
|
||||
VariableTable,
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
<TableColumn @bind-Field="@context.Index" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn @bind-Field="@context.Id" Filterable=true Sortable=true Visible="false" DefaultSort=true DefaultSortOrder="SortOrder.Asc" />
|
||||
|
||||
|
||||
|
||||
</TableColumns>
|
||||
<RowButtonTemplate>
|
||||
@@ -94,12 +94,12 @@
|
||||
</Button>
|
||||
<Button class="dropdown-item" OnClick="() => ExcelVariableAsync(ExportContext)" IsDisabled=@(!AuthorizeButton("导入"))>
|
||||
<i class="fas fa-file-import"></i>
|
||||
<span>@Localizer["ExcelVariable"]</span>
|
||||
<span>@GatewayLocalizer["ExcelVariable"]</span>
|
||||
</Button>
|
||||
</ExportButtonDropdownTemplate>
|
||||
<TableToolbarTemplate>
|
||||
|
||||
<TableToolbarButton TItem="VariableRuntime" IsDisabled=@(!AuthorizeButton(AdminOperConst.Add))
|
||||
<TableToolbarButton TItem="VariableRuntime" IsDisabled=@(!AuthorizeButton(AdminOperConst.Add))
|
||||
Color=Color.Success Text="@RazorLocalizer["Copy"]"
|
||||
OnClickCallback=@(Copy) />
|
||||
|
||||
@@ -108,19 +108,22 @@
|
||||
OnClickCallback=@(BatchEdit) />
|
||||
|
||||
<TableToolbarPopConfirmButton TItem="VariableRuntime"
|
||||
Color=Color.Warning Text="@RazorLocalizer["Clear"]" IsDisabled=@(!AuthorizeButton("清空"))
|
||||
IsAsync OnConfirm=@(ClearVariableAsync) />
|
||||
Color=Color.Warning Text="@RazorLocalizer["Clear"]" IsDisabled=@(!AuthorizeButton(AdminOperConst.Delete))
|
||||
IsAsync OnConfirm=@(ClearAsync) />
|
||||
|
||||
<PopConfirmButton Color=Color.Warning Text="@Localizer["Test"]" IsDisabled=@(!AuthorizeButton(AdminOperConst.Add))
|
||||
IsAsync OnConfirm=@(InsertTestDataAsync)>
|
||||
@if (WebsiteOption.Value.Demo)
|
||||
{
|
||||
<PopConfirmButton Color=Color.Warning Text="@Localizer["Test"]" IsDisabled=@(!AuthorizeButton(AdminOperConst.Add))
|
||||
IsAsync OnConfirm=@(InsertTestDataAsync)>
|
||||
|
||||
<BodyTemplate>
|
||||
<BootstrapInput @bind-Value=TestVariableCount ShowLabel="true" ShowLabelTooltip="true" />
|
||||
<BootstrapInput @bind-Value=TestDeviceCount ShowLabel="true" ShowLabelTooltip="true" />
|
||||
<BootstrapInput @bind-Value=SlaveUrl ShowLabel="true" ShowLabelTooltip="true" />
|
||||
</BodyTemplate>
|
||||
<BodyTemplate>
|
||||
<BootstrapInput @bind-Value=TestVariableCount ShowLabel="true" ShowLabelTooltip="true" />
|
||||
<BootstrapInput @bind-Value=TestDeviceCount ShowLabel="true" ShowLabelTooltip="true" />
|
||||
<BootstrapInput @bind-Value=SlaveUrl ShowLabel="true" ShowLabelTooltip="true" />
|
||||
</BodyTemplate>
|
||||
|
||||
</PopConfirmButton>
|
||||
</PopConfirmButton>
|
||||
}
|
||||
|
||||
</TableToolbarTemplate>
|
||||
</AdminTable>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
@@ -22,6 +23,8 @@ namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class VariableRuntimeInfo : IDisposable
|
||||
{
|
||||
[Inject]
|
||||
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
|
||||
public bool Disposed { get; set; }
|
||||
|
||||
[Parameter]
|
||||
@@ -169,19 +172,19 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
var oldmodel = variables.FirstOrDefault();//默认值显示第一个
|
||||
if (oldmodel == null)
|
||||
var oldModel = variables.FirstOrDefault();//默认值显示第一个
|
||||
if (oldModel == null)
|
||||
{
|
||||
await ToastService.Warning(null, RazorLocalizer["PleaseSelect"]);
|
||||
return;
|
||||
}
|
||||
|
||||
var model = oldmodel.Adapt<Variable>();//默认值显示第一个
|
||||
var model = oldModel.Adapt<Variable>();//默认值显示第一个
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<VariableEditComponent>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(VariableEditComponent.OnValidSubmit), async () =>
|
||||
{
|
||||
await Task.Run(()=> GlobalData. VariableRuntimeService.BatchEditAsync(variables,oldmodel,model, AutoRestartThread,default));
|
||||
await Task.Run(()=> GlobalData. VariableRuntimeService.BatchEditAsync(variables,oldModel,model, AutoRestartThread,default));
|
||||
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
}},
|
||||
@@ -194,13 +197,13 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
}
|
||||
|
||||
|
||||
private async Task<bool> Delete(IEnumerable<Variable> devices)
|
||||
private async Task<bool> Delete(IEnumerable<Variable> variables)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
return await GlobalData.VariableRuntimeService.DeleteVariableAsync(devices.Select(a => a.Id), AutoRestartThread, default);
|
||||
return await GlobalData.VariableRuntimeService.DeleteVariableAsync(variables.Select(a => a.Id), AutoRestartThread, default);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -294,7 +297,7 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraExtraLarge,
|
||||
Title = Localizer["ExcelVariable"],
|
||||
Title = GatewayLocalizer["ExcelVariable"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
};
|
||||
@@ -348,7 +351,7 @@ finally
|
||||
IsScrolling = true,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraLarge,
|
||||
Title = Localizer["ImportExcel"],
|
||||
Title = GatewayLocalizer["ImportVariable"],
|
||||
ShowFooter = false,
|
||||
ShowCloseButton = false,
|
||||
OnCloseAsync = async () =>
|
||||
@@ -373,7 +376,7 @@ finally
|
||||
|
||||
#region 清空
|
||||
|
||||
private async Task ClearVariableAsync()
|
||||
private async Task ClearAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -398,8 +401,7 @@ finally
|
||||
|
||||
}
|
||||
#endregion
|
||||
[Inject]
|
||||
MaskService MaskService { get; set; }
|
||||
|
||||
private async Task InsertTestDataAsync()
|
||||
{
|
||||
try
|
||||
@@ -434,6 +436,8 @@ finally
|
||||
public bool AutoRestartThread { get; set; }
|
||||
[Parameter]
|
||||
public ChannelDeviceTreeItem SelectModel { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports>? GatewayLocalizer { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -34,14 +34,7 @@ public interface IRulesService
|
||||
/// 从缓存/数据库获取全部信息
|
||||
/// </summary>
|
||||
/// <returns>规则列表</returns>
|
||||
List<Rules> GetAll();
|
||||
|
||||
/// <summary>
|
||||
/// 通过ID获取规则
|
||||
/// </summary>
|
||||
/// <param name="id">规则ID</param>
|
||||
/// <returns>规则对象</returns>
|
||||
Rules? GetRulesById(long id);
|
||||
Task<List<Rules>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 报表查询
|
||||
|
||||
@@ -58,7 +58,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
if (rules.Status)
|
||||
{
|
||||
var data = Init(rules);
|
||||
await Start(data.rulesLog, data.blazorDiagram, default).ConfigureAwait(false);
|
||||
await Start(data.rulesLog, data.blazorDiagram, TokenSource.Token).ConfigureAwait(false);
|
||||
var service = App.GetService<IDispatchService<Rules>>();
|
||||
service.Dispatch(new());
|
||||
}
|
||||
@@ -108,23 +108,15 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
|
||||
return result;
|
||||
}
|
||||
private static async Task Start(RulesLog rulesLog, BlazorDiagram item, CancellationToken cancellationToken)
|
||||
private static Task Start(RulesLog rulesLog, BlazorDiagram item, CancellationToken cancellationToken)
|
||||
{
|
||||
rulesLog.Log.Trace("Start");
|
||||
var startNodes = item.Nodes.Where(a => a is StartNode);
|
||||
startNodes.ForEach(a =>
|
||||
{
|
||||
if (a is INode node)
|
||||
{
|
||||
node.Logger = rulesLog.Log;
|
||||
node.RulesEngineName = rulesLog.Rules.Name;
|
||||
}
|
||||
}
|
||||
);
|
||||
foreach (var link in startNodes.SelectMany(a => a.PortLinks))
|
||||
{
|
||||
rulesLog.Log.Trace("Start");
|
||||
await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput(), rulesLog, cancellationToken).ConfigureAwait(false);
|
||||
_ = Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput(), rulesLog, cancellationToken);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static async Task Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken)
|
||||
@@ -192,11 +184,11 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
|
||||
#region worker服务
|
||||
|
||||
private async Task BefortStart(CancellationToken cancellationToken)
|
||||
private async Task StartAll(CancellationToken cancellationToken)
|
||||
{
|
||||
AfterStop();
|
||||
Clear();
|
||||
|
||||
Rules = App.GetService<IRulesService>().GetAll();
|
||||
Rules = await App.GetService<IRulesService>().GetAllAsync().ConfigureAwait(false);
|
||||
BlazorDiagrams = new();
|
||||
foreach (var rules in Rules.Where(a => a.Status))
|
||||
{
|
||||
@@ -223,29 +215,10 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
|
||||
}
|
||||
|
||||
private void AfterStop()
|
||||
{
|
||||
foreach (var item in BlazorDiagrams.Values)
|
||||
{
|
||||
foreach (var nodeModel in item.Nodes)
|
||||
{
|
||||
nodeModel.TryDispose();
|
||||
}
|
||||
}
|
||||
BlazorDiagrams.Clear();
|
||||
}
|
||||
|
||||
private CancellationTokenSource? TokenSource { get; set; }
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
if (TokenSource != null)
|
||||
{
|
||||
TokenSource.Cancel();
|
||||
TokenSource.Dispose();
|
||||
TokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal async Task StartAsync()
|
||||
{
|
||||
@@ -253,7 +226,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
{
|
||||
await RestartLock.WaitAsync().ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||||
TokenSource ??= new CancellationTokenSource();
|
||||
await BefortStart(TokenSource.Token).ConfigureAwait(false);
|
||||
await StartAll(TokenSource.Token).ConfigureAwait(false);
|
||||
_logger.LogInformation(Localizer["RulesEngineTaskStart"]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -272,7 +245,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
{
|
||||
await RestartLock.WaitAsync().ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||||
Cancel();
|
||||
AfterStop();
|
||||
Clear();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -284,6 +257,28 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
if (TokenSource != null)
|
||||
{
|
||||
TokenSource.Cancel();
|
||||
TokenSource.Dispose();
|
||||
TokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
foreach (var item in BlazorDiagrams.Values)
|
||||
{
|
||||
foreach (var nodeModel in item.Nodes)
|
||||
{
|
||||
nodeModel.TryDispose();
|
||||
}
|
||||
}
|
||||
BlazorDiagrams.Clear();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -42,7 +42,7 @@ internal sealed class RulesService : BaseService<Rules>, IRulesService
|
||||
|
||||
using var db = GetDB();
|
||||
|
||||
var data = GetAll()
|
||||
var data = (await GetAllAsync().ConfigureAwait(false))
|
||||
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
.Select(a => a.Id).ToList();
|
||||
@@ -80,25 +80,19 @@ internal sealed class RulesService : BaseService<Rules>, IRulesService
|
||||
/// 从缓存/数据库获取全部信息
|
||||
/// </summary>
|
||||
/// <returns>列表</returns>
|
||||
public List<Rules> GetAll()
|
||||
public async Task<List<Rules>> GetAllAsync()
|
||||
{
|
||||
var key = Cache_Rules;
|
||||
var channels = App.CacheService.Get<List<Rules>>(key);
|
||||
if (channels == null)
|
||||
{
|
||||
using var db = GetDB();
|
||||
channels = db.Queryable<Rules>().ToList();
|
||||
channels = await db.Queryable<Rules>().ToListAsync().ConfigureAwait(false);
|
||||
App.CacheService.Set(key, channels);
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
|
||||
public Rules? GetRulesById(long id)
|
||||
{
|
||||
var data = GetAll();
|
||||
return data?.FirstOrDefault(x => x.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 报表查询
|
||||
/// </summary>
|
||||
@@ -142,10 +136,5 @@ internal sealed class RulesService : BaseService<Rules>, IRulesService
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<!--打包复制-->
|
||||
<Import Project="..\ThingsGateway.Server\targets\PluginPublish.targets" />
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
<CustomTargetFramework>$(TargetFramework)</CustomTargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<!--打包复制-->
|
||||
<Import Project="targets\PluginPublish.targets" />
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
<CustomTargetFramework>$(TargetFramework)</CustomTargetFramework>
|
||||
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
|
||||
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.23" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.24" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>10.4.7</Version>
|
||||
<Version>10.4.8</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user