适配远程管理客户端

This commit is contained in:
2248356998 qq.com
2025-08-08 18:01:24 +08:00
parent 7499162c1a
commit 6660ce3e34
44 changed files with 762 additions and 450 deletions

View File

@@ -39,7 +39,7 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script> <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js?v={this.GetType().Assembly.GetName().Version}")></script> <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<!-- PWA Service Worker --> <!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -45,7 +45,7 @@
</app> </app>
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script> <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.server.js"></script> <script src="_framework/blazor.server.js"></script>
<!-- PWA Service Worker --> <!-- PWA Service Worker -->

View File

@@ -8,8 +8,8 @@
// QQ群605534569 // QQ群605534569
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using ThingsGateway.Common.Extension;
using ThingsGateway.NewLife; using ThingsGateway.NewLife;
using ThingsGateway.Razor.Extension;
namespace ThingsGateway.Razor; namespace ThingsGateway.Razor;

View File

@@ -10,7 +10,7 @@
using Microsoft.JSInterop; using Microsoft.JSInterop;
namespace ThingsGateway.Common.Extension; namespace ThingsGateway.Razor.Extension;
/// <summary> /// <summary>
/// JSRuntime扩展方法 /// JSRuntime扩展方法
@@ -49,4 +49,28 @@ public static class JSRuntimeExtensions
{ {
} }
} }
public static async ValueTask<T> GetLocalStorage<T>(this IJSRuntime jsRuntime, string name)
{
try
{
return await jsRuntime.InvokeAsync<T>("getLocalStorage", name).ConfigureAwait(false);
}
catch
{
return default;
}
}
public static async ValueTask SetLocalStorage<T>(this IJSRuntime jsRuntime, string name, T data)
{
try
{
await jsRuntime.InvokeVoidAsync("setLocalStorage", name, data).ConfigureAwait(false);
}
catch
{
}
}
} }

View File

@@ -1,9 +0,0 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}

View File

@@ -0,0 +1,18 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}
function getLocalStorage(name) {
return JSON.parse(localStorage.getItem(name)) ?? 0;
}
function setLocalStorage(name, data) {
if (localStorage) {
localStorage.setItem(name, JSON.stringify(data));
}
}

View File

@@ -1,9 +1,9 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<PluginVersion>10.10.11</PluginVersion> <PluginVersion>10.10.12</PluginVersion>
<ProPluginVersion>10.10.11</ProPluginVersion> <ProPluginVersion>10.10.12</ProPluginVersion>
<DefaultVersion>10.10.14</DefaultVersion> <DefaultVersion>10.10.15</DefaultVersion>
<AuthenticationVersion>10.10.1</AuthenticationVersion> <AuthenticationVersion>10.10.1</AuthenticationVersion>
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion> <SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version> <NET8Version>8.0.19</NET8Version>

View File

@@ -33,6 +33,8 @@ public class AsyncReadWriteLock
{ {
Interlocked.Increment(ref _readerCount); Interlocked.Increment(ref _readerCount);
// 第一个读者需要获取写入锁,防止写操作 // 第一个读者需要获取写入锁,防止写操作
await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
@@ -63,11 +65,16 @@ public class AsyncReadWriteLock
private object lockObject = new(); private object lockObject = new();
private void ReleaseWriter() private void ReleaseWriter()
{ {
var writerCount = Interlocked.Decrement(ref _writerCount); var writerCount = Interlocked.Decrement(ref _writerCount);
// 每次释放写时,总是唤醒至少一个读
_readerLock.Set();
if (writerCount == 0) if (writerCount == 0)
{ {
var resetEvent = _readerLock; var resetEvent = _readerLock;
_readerLock = new(false); //_readerLock = new(false);
Interlocked.Exchange(ref _writeSinceLastReadCount, 0); Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
resetEvent.SetAll(); resetEvent.SetAll();
} }
@@ -83,12 +90,12 @@ public class AsyncReadWriteLock
if (count >= _writeReadRatio) if (count >= _writeReadRatio)
{ {
Interlocked.Exchange(ref _writeSinceLastReadCount, 0); Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
_readerLock.Set(); //_readerLock.Set();
} }
} }
else else
{ {
_readerLock.Set(); //_readerLock.Set();
} }
} }

View File

@@ -177,6 +177,15 @@ public static class GlobalData
} }
return GlobalData.ChannelThreadManage.DeviceThreadManages.TryGetValue(deviceRuntime.ChannelId, out deviceThreadManage); return GlobalData.ChannelThreadManage.DeviceThreadManages.TryGetValue(deviceRuntime.ChannelId, out deviceThreadManage);
} }
public static IChannelThreadManage GetChannelThreadManage(ChannelRuntime channelRuntime)
{
if (channelRuntime.DeviceThreadManage?.ChannelThreadManage != null)
return channelRuntime.DeviceThreadManage.ChannelThreadManage;
else
return GlobalData.ChannelThreadManage;
}
public static Dictionary<IDeviceThreadManage, List<DeviceRuntime>> GetDeviceThreadManages(IEnumerable<DeviceRuntime> deviceRuntimes) public static Dictionary<IDeviceThreadManage, List<DeviceRuntime>> GetDeviceThreadManages(IEnumerable<DeviceRuntime> deviceRuntimes)
{ {
Dictionary<IDeviceThreadManage, List<DeviceRuntime>> deviceThreadManages = new(); Dictionary<IDeviceThreadManage, List<DeviceRuntime>> deviceThreadManages = new();

View File

@@ -83,5 +83,8 @@ public partial class AlarmRuntimePropertys
public DateTime EventTime { get; set; } = DateTime.UnixEpoch.ToLocalTime(); public DateTime EventTime { get; set; } = DateTime.UnixEpoch.ToLocalTime();
internal object AlarmLockObject = new(); internal object AlarmLockObject = new();
#if !Management
internal bool AlarmConfirm; internal bool AlarmConfirm;
#endif
} }

View File

@@ -23,27 +23,15 @@ namespace ThingsGateway.Gateway.Application;
/// <summary> /// <summary>
/// 业务设备运行状态 /// 业务设备运行状态
/// </summary> /// </summary>
public class ChannelRuntime : Channel, IChannelOptions, IDisposable public class ChannelRuntime : Channel
#if !Management
,
IChannelOptions,
IDisposable
#endif
{ {
/// <summary>
/// 插件信息
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public PluginInfo? PluginInfo { get; set; }
/// <summary> #if !Management
/// 是否采集
/// </summary>
public PluginTypeEnum? PluginType => PluginInfo?.PluginType;
/// <summary>
/// 是否采集
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public bool? IsCollect => PluginInfo == null ? null : PluginInfo?.PluginType == PluginTypeEnum.Collect;
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
@@ -105,14 +93,67 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
public int? DeviceRuntimeCount => DeviceRuntimes?.Count; public int? DeviceRuntimeCount => DeviceRuntimes?.Count;
public bool Started => DeviceThreadManage != null;
#else
/// <inheritdoc/>
[MinValue(1)]
public override int MaxConcurrentCount { get; set; }
/// <summary>
/// 设备数量
/// </summary>
public int? DeviceRuntimeCount { get; set; }
public bool Started { get; set; }
#endif
/// <summary>
/// 插件信息
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public PluginInfo? PluginInfo { get; set; }
/// <summary>
/// 是否采集
/// </summary>
public PluginTypeEnum? PluginType => PluginInfo?.PluginType;
/// <summary>
/// 是否采集
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public bool? IsCollect => PluginInfo == null ? null : PluginInfo?.PluginType == PluginTypeEnum.Collect;
public override string ToString()
{
if (ChannelType == ChannelTypeEnum.Other)
{
return Name;
}
return $"{Name}[{base.ToString()}]";
}
[AutoGenerateColumn(Ignore = true)]
public string LogPath => Name.GetChannelLogPath();
#if !Management
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
[MapperIgnore] [MapperIgnore]
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
public IDeviceThreadManage? DeviceThreadManage { get; internal set; } public IDeviceThreadManage? DeviceThreadManage { get; internal set; }
[AutoGenerateColumn(Ignore = true)]
public string LogPath => Name.GetChannelLogPath();
public void Init() public void Init()
{ {
@@ -135,14 +176,8 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
DeviceThreadManage = null; DeviceThreadManage = null;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public override string ToString()
{
if (ChannelType == ChannelTypeEnum.Other)
{
return Name;
}
return $"{Name}[{base.ToString()}]";
}
public IChannel GetChannel(TouchSocketConfig config) public IChannel GetChannel(TouchSocketConfig config)
{ {
@@ -192,4 +227,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
return ichannel; return ichannel;
} }
} }
#endif
} }

View File

@@ -23,12 +23,17 @@ namespace ThingsGateway.Gateway.Application;
/// <summary> /// <summary>
/// 业务设备运行状态 /// 业务设备运行状态
/// </summary> /// </summary>
public class DeviceRuntime : Device, IDisposable public class DeviceRuntime : Device
#if !Management
, IDisposable
#endif
{ {
protected volatile DeviceStatusEnum _deviceStatus = DeviceStatusEnum.Default; protected volatile DeviceStatusEnum _deviceStatus = DeviceStatusEnum.Default;
private string? _lastErrorMessage; private string? _lastErrorMessage;
private readonly object _lockObject = new object();
/// <summary> /// <summary>
/// 设备活跃时间 /// 设备活跃时间
/// </summary> /// </summary>
@@ -67,11 +72,15 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
public string LogPath => Name.GetDeviceLogPath(); public string LogPath => Name.GetDeviceLogPath();
#if !Management
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
[MapperIgnore] [MapperIgnore]
public DateTime DeviceStatusChangeTime = DateTime.UnixEpoch.ToLocalTime(); public DateTime DeviceStatusChangeTime = DateTime.UnixEpoch.ToLocalTime();
/// <summary> /// <summary>
/// 设备状态 /// 设备状态
/// </summary> /// </summary>
@@ -98,6 +107,58 @@ public class DeviceRuntime : Device, IDisposable
} }
} }
/// <summary>
/// 设备变量数量
/// </summary>
public int DeviceVariableCount { get => Driver == null ? VariableRuntimes?.Count ?? 0 : Driver.IdVariableRuntimes.Count; }
/// <summary>
/// 设备读取打包数量
/// </summary>
public int SourceVariableCount => VariableSourceReads?.Count ?? 0;
#else
/// <summary>
/// 设备状态
/// </summary>
public virtual DeviceStatusEnum DeviceStatus
{
get
{
if (!Pause)
return _deviceStatus;
else
return DeviceStatusEnum.Pause;
}
set
{
lock (_lockObject)
{
if (_deviceStatus != value)
{
_deviceStatus = value;
}
}
}
}
/// <summary>
/// 设备变量数量
/// </summary>
public int DeviceVariableCount { get; set; }
/// <summary>
/// 设备读取打包数量
/// </summary>
public int SourceVariableCount { get; set; }
#endif
/// <summary> /// <summary>
/// 暂停 /// 暂停
/// </summary> /// </summary>
@@ -130,12 +191,17 @@ public class DeviceRuntime : Device, IDisposable
/// </summary> /// </summary>
public RedundantTypeEnum? RedundantType { get; set; } = null; public RedundantTypeEnum? RedundantType { get; set; } = null;
/// <summary>
/// 设备变量数量
/// </summary>
public int DeviceVariableCount { get => Driver == null ? VariableRuntimes?.Count ?? 0 : Driver.IdVariableRuntimes.Count; }
#region
/// <summary>
/// 特殊方法数量
/// </summary>
public int MethodVariableCount { get; set; }
#if !Management
/// <summary> /// <summary>
/// 设备变量 /// 设备变量
@@ -154,11 +220,6 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; } = new(Environment.ProcessorCount, 1000); internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; } = new(Environment.ProcessorCount, 1000);
/// <summary>
/// 特殊方法数量
/// </summary>
public int MethodVariableCount { get; set; }
/// <summary> /// <summary>
/// 特殊方法变量 /// 特殊方法变量
/// </summary> /// </summary>
@@ -168,11 +229,6 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
public List<VariableMethod>? ReadVariableMethods { get; set; } public List<VariableMethod>? ReadVariableMethods { get; set; }
/// <summary>
/// 设备读取打包数量
/// </summary>
public int SourceVariableCount => VariableSourceReads?.Count ?? 0;
/// <summary> /// <summary>
/// 打包变量 /// 打包变量
/// </summary> /// </summary>
@@ -191,10 +247,11 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
public List<VariableScriptRead>? VariableScriptReads { get; set; } public List<VariableScriptRead>? VariableScriptReads { get; set; }
public volatile bool CheckEnable; #endif
private readonly object _lockObject = new object();
#endregion
#if !Management
/// <summary> /// <summary>
/// 传入设备的状态信息 /// 传入设备的状态信息
@@ -218,6 +275,7 @@ public class DeviceRuntime : Device, IDisposable
LastErrorMessage = lastErrorMessage; LastErrorMessage = lastErrorMessage;
} }
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
[MapperIgnore] [MapperIgnore]
@@ -230,6 +288,7 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
public IRpcDriver? RpcDriver { get; set; } public IRpcDriver? RpcDriver { get; set; }
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
@@ -263,4 +322,7 @@ public class DeviceRuntime : Device, IDisposable
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#endif
} }

View File

@@ -17,12 +17,13 @@ namespace ThingsGateway.Gateway.Application;
/// </summary> /// </summary>
public class PluginInfo public class PluginInfo
{ {
#if !Management
/// <summary> /// <summary>
/// 插件文件名称.插件类型名称 /// 插件文件名称.插件类型名称
/// </summary> /// </summary>
[AutoGenerateColumn(Ignore = true)] [AutoGenerateColumn(Ignore = true)]
public List<PluginInfo>? Children { get; set; } = new(); public List<PluginInfo>? Children { get; set; } = new();
#endif
/// <summary> /// <summary>
/// 插件文件名称.插件类型名称 /// 插件文件名称.插件类型名称
/// </summary> /// </summary>

View File

@@ -8,9 +8,15 @@
// QQ群605534569 // QQ群605534569
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Riok.Mapperly.Abstractions;
#if !Management
using ThingsGateway.Gateway.Application.Extensions; using ThingsGateway.Gateway.Application.Extensions;
#endif
using ThingsGateway.NewLife.DictionaryExtensions; using ThingsGateway.NewLife.DictionaryExtensions;
using ThingsGateway.NewLife.Json.Extension; using ThingsGateway.NewLife.Json.Extension;
@@ -19,8 +25,194 @@ namespace ThingsGateway.Gateway.Application;
/// <summary> /// <summary>
/// 变量运行态 /// 变量运行态
/// </summary> /// </summary>
public partial class VariableRuntime : Variable, IVariable, IDisposable public partial class VariableRuntime : Variable
#if !Management
,
IVariable,
IDisposable
#endif
{ {
[AutoGenerateColumn(Visible = false)]
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
#region
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[AutoGenerateColumn(Visible = false)]
public int Index { get => index; set => index = value; }
/// <summary>
/// 变化时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime ChangeTime { get => changeTime; set => changeTime = value; }
/// <summary>
/// 采集时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime CollectTime { get => collectTime; set => collectTime = value; }
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = false, Sortable = true)]
[IgnoreExcel]
public override int SortCode { get => sortCode; set => sortCode = value; }
/// <summary>
/// 上次值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object LastSetValue { get => lastSetValue; set => lastSetValue = value; }
/// <summary>
/// 原始值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object RawValue { get => rawValue; set => rawValue = value; }
#if !Management
/// <summary>
/// 所在采集设备
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; set => deviceRuntime = value; }
/// <summary>
/// VariableSource
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public IVariableSource? VariableSource { get => variableSource; set => variableSource = value; }
/// <summary>
/// VariableMethod
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public VariableMethod? VariableMethod { get => variableMethod; set => variableMethod = value; }
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public IThingsGatewayBitConverter? ThingsGatewayBitConverter { get => thingsGatewayBitConverter; set => thingsGatewayBitConverter = value; }
#endif
/// <summary>
/// 是否在线
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public bool IsOnline
{
get
{
return _isOnline;
}
private set
{
if (IsOnline != value)
{
_isOnlineChanged = true;
}
else
{
_isOnlineChanged = false;
}
_isOnline = value;
}
}
#if !Management
/// <summary>
/// 设备名称
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 4)]
public string DeviceName => DeviceRuntime?.Name;
/// <summary>
/// <inheritdoc/>
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public string LastErrorMessage
{
get
{
if (_isOnline == false)
return _lastErrorMessage ?? VariableSource?.LastErrorMessage ?? VariableMethod?.LastErrorMessage;
else
return null;
}
}
/// <summary>
/// 实时值类型
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public string RuntimeType => Value?.GetType()?.ToString();
#else
/// <summary>
/// 设备名称
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 4)]
public string DeviceName { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public string LastErrorMessage { get; set; }
/// <summary>
/// 实时值类型
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public string RuntimeType { get; set; }
#endif
/// <summary>
/// 实时值
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public object Value { get => _value; set => _value = value; }
/// <summary>
/// 报警使能
/// </summary>
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool AlarmEnable
{
get
{
return AlarmPropertys != null && (AlarmPropertys.LAlarmEnable || AlarmPropertys.LLAlarmEnable || AlarmPropertys.HAlarmEnable || AlarmPropertys.HHAlarmEnable || AlarmPropertys.BoolOpenAlarmEnable || AlarmPropertys.BoolCloseAlarmEnable || AlarmPropertys.CustomAlarmEnable);
}
}
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public AlarmRuntimePropertys? AlarmRuntimePropertys { get; set; }
#endregion
private int index; private int index;
@@ -33,15 +225,19 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
private bool _isOnlineChanged; private bool _isOnlineChanged;
private bool _valueInited; private bool _valueInited;
private string _lastErrorMessage;
private object _value; private object _value;
private object lastSetValue; private object lastSetValue;
private object rawValue; private object rawValue;
#if !Management
#pragma warning disable CS0649
private string _lastErrorMessage;
#pragma warning restore CS0649
private DeviceRuntime? deviceRuntime; private DeviceRuntime? deviceRuntime;
private IVariableSource? variableSource; private IVariableSource? variableSource;
private VariableMethod? variableMethod; private VariableMethod? variableMethod;
private IThingsGatewayBitConverter? thingsGatewayBitConverter; private IThingsGatewayBitConverter? thingsGatewayBitConverter;
/// <summary> /// <summary>
/// 设置变量值与时间/质量戳 /// 设置变量值与时间/质量戳
/// </summary> /// </summary>
@@ -224,4 +420,10 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
{ {
_lastErrorMessage = lastErrorMessage; _lastErrorMessage = lastErrorMessage;
} }
#endif
} }

View File

@@ -1,173 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Riok.Mapperly.Abstractions;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 变量运行态
/// </summary>
public partial class VariableRuntime : Variable, IVariable, IDisposable
{
[AutoGenerateColumn(Visible = false)]
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
#region
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[AutoGenerateColumn(Visible = false)]
public int Index { get => index; set => index = value; }
/// <summary>
/// 变化时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime ChangeTime { get => changeTime; set => changeTime = value; }
/// <summary>
/// 采集时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime CollectTime { get => collectTime; set => collectTime = value; }
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = false, Sortable = true)]
[IgnoreExcel]
public override int SortCode { get => sortCode; set => sortCode = value; }
/// <summary>
/// 上次值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object LastSetValue { get => lastSetValue; set => lastSetValue = value; }
/// <summary>
/// 原始值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object RawValue { get => rawValue; set => rawValue = value; }
/// <summary>
/// 所在采集设备
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; set => deviceRuntime = value; }
/// <summary>
/// VariableSource
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public IVariableSource? VariableSource { get => variableSource; set => variableSource = value; }
/// <summary>
/// VariableMethod
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public VariableMethod? VariableMethod { get => variableMethod; set => variableMethod = value; }
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public IThingsGatewayBitConverter? ThingsGatewayBitConverter { get => thingsGatewayBitConverter; set => thingsGatewayBitConverter = value; }
/// <summary>
/// 设备名称
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 4)]
public string DeviceName => DeviceRuntime?.Name;
/// <summary>
/// 是否在线
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public bool IsOnline
{
get
{
return _isOnline;
}
private set
{
if (IsOnline != value)
{
_isOnlineChanged = true;
}
else
{
_isOnlineChanged = false;
}
_isOnline = value;
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public string LastErrorMessage
{
get
{
if (_isOnline == false)
return _lastErrorMessage ?? VariableSource?.LastErrorMessage ?? VariableMethod?.LastErrorMessage;
else
return null;
}
}
/// <summary>
/// 实时值类型
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public string RuntimeType => Value?.GetType()?.ToString();
/// <summary>
/// 实时值
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public object Value { get => _value; set => _value = value; }
/// <summary>
/// 报警使能
/// </summary>
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool AlarmEnable
{
get
{
return AlarmPropertys != null && (AlarmPropertys.LAlarmEnable || AlarmPropertys.LLAlarmEnable || AlarmPropertys.HAlarmEnable || AlarmPropertys.HHAlarmEnable || AlarmPropertys.BoolOpenAlarmEnable || AlarmPropertys.BoolCloseAlarmEnable || AlarmPropertys.CustomAlarmEnable);
}
}
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public AlarmRuntimePropertys? AlarmRuntimePropertys { get; set; }
#endregion
}

View File

@@ -19,13 +19,90 @@ namespace ThingsGateway.Gateway.Application;
public class ChannelRuntimeService : IChannelRuntimeService public class ChannelRuntimeService : IChannelRuntimeService
{ {
private ILogger _logger; private Microsoft.Extensions.Logging.ILogger _logger;
public ChannelRuntimeService(ILogger<ChannelRuntimeService> logger) public ChannelRuntimeService(Microsoft.Extensions.Logging.ILogger<ChannelRuntimeService> logger)
{ {
_logger = logger; _logger = logger;
} }
private WaitLock WaitLock { get; set; } = new WaitLock(nameof(ChannelRuntimeService)); private WaitLock WaitLock { get; set; } = new WaitLock(nameof(ChannelRuntimeService));
public Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id)
{
GlobalData.IdChannels.TryGetValue(id, out var ChannelRuntime);
var data = ChannelRuntime?.DeviceThreadManage?.LogMessage?.LogLevel ?? TouchSocket.Core.LogLevel.Trace;
return Task.FromResult(data);
}
public async Task RestartChannelAsync(long channelId)
{
GlobalData.IdChannels.TryGetValue(channelId, out var channelRuntime);
await GlobalData.GetChannelThreadManage(channelRuntime).RestartChannelAsync(channelRuntime).ConfigureAwait(false);
}
public async Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel)
{
if (GlobalData.IdChannels.TryGetValue(id, out var ChannelRuntime))
{
if (ChannelRuntime.DeviceThreadManage != null)
{
await ChannelRuntime.DeviceThreadManage.SetLogAsync(logLevel).ConfigureAwait(false);
}
}
}
public async Task CopyChannelAsync(int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long channelId, bool AutoRestartThread)
{
if (!GlobalData.IdChannels.TryGetValue(channelId, out var channelRuntime))
{
return;
}
Dictionary<Device, List<Variable>> deviceDict = new();
Channel Model = channelRuntime.AdaptChannel();
Model.Id = 0;
var Devices = channelRuntime.ReadDeviceRuntimes.ToDictionary(a => a.Value.AdaptDevice(), a => a.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable());
List<Channel> channels = new();
Dictionary<Device, List<Variable>> devices = new();
for (int i = 0; i < CopyCount; i++)
{
Channel channel = Model.AdaptChannel();
channel.Id = CommonUtils.GetSingleId();
channel.Name = $"{CopyChannelNamePrefix}{CopyChannelNameSuffixNumber + i}";
int index = 0;
foreach (var item in Devices)
{
Device device = item.Key.AdaptDevice();
device.Id = CommonUtils.GetSingleId();
device.Name = $"{channel.Name}_{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + (index++)}";
device.ChannelId = channel.Id;
List<Variable> variables = new();
foreach (var variable in item.Value)
{
Variable v = variable.AdaptVariable();
v.Id = CommonUtils.GetSingleId();
v.DeviceId = device.Id;
variables.Add(v);
}
devices.Add(device, variables);
}
channels.Add(channel);
}
await GlobalData.ChannelRuntimeService.CopyAsync(channels, devices, AutoRestartThread, default).ConfigureAwait(false);
}
public async Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken) public async Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken)
{ {
try try
@@ -204,6 +281,31 @@ public class ChannelRuntimeService : IChannelRuntimeService
WaitLock.Release(); WaitLock.Release();
} }
} }
public async Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart)
{
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
var result = await GlobalData.ChannelService.ImportAsync(upData, insertData).ConfigureAwait(false);
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false);
RuntimeServiceHelper.Init(newChannelRuntimes);
//根据条件重启通道线程
if (restart)
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
}
finally
{
WaitLock.Release();
}
}
public async Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart) public async Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart)
{ {
try try

View File

@@ -367,21 +367,16 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
/// <inheritdoc/> /// <inheritdoc/>
[OperDesc("ImportChannel", isRecordPar: false, localizerType: typeof(Channel))] [OperDesc("ImportChannel", isRecordPar: false, localizerType: typeof(Channel))]
public async Task<HashSet<long>> ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input) public Task<HashSet<long>> ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input)
{ {
List<Channel>? channels = new List<Channel>(); ChannelServiceHelpers.GetImportChannelData(input, out var upData, out var insertData);
foreach (var item in input) return ImportAsync(upData, insertData);
{ }
if (item.Key == ExportString.ChannelName)
{
var channelImports = ((ImportPreviewListOutput<Channel>)item.Value).Data;
channels = channelImports;
break;
}
}
var upData = channels.Where(a => a.IsUp).ToList();
var insertData = channels.Where(a => !a.IsUp).ToList();
public async Task<HashSet<long>> ImportAsync(List<Channel> upData, List<Channel> insertData)
{
ManageHelper.CheckChannelCount(insertData.Count); ManageHelper.CheckChannelCount(insertData.Count);
using var db = GetDB(); using var db = GetDB();
@@ -396,7 +391,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);
} }
DeleteChannelFromCache(); DeleteChannelFromCache();
return channels.Select(a => a.Id).ToHashSet(); return upData.Select(a => a.Id).Concat(insertData.Select(a => a.Id)).ToHashSet();
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -441,7 +436,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选 // 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var channelProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite) var channelProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name), a => (a, a.IsNullableType())); .ToDictionary(a => type.GetPropertyDisplayName(a.Name), a => (a, a.IsNullableType()));
string unportNull = App.CreateLocalizerByType(typeof(Channel))["ImportNullError"];
rows.ForEach(item => rows.ForEach(item =>
{ {
try try
@@ -450,7 +445,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
if (channel == null) if (channel == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, unportNull));
return; return;
} }

View File

@@ -18,6 +18,23 @@ namespace ThingsGateway.Gateway.Application;
public static class ChannelServiceHelpers public static class ChannelServiceHelpers
{ {
public static void GetImportChannelData(Dictionary<string, ImportPreviewOutputBase> input, out List<Channel> upData, out List<Channel> insertData)
{
List<Channel>? channels = new List<Channel>();
foreach (var item in input)
{
if (item.Key == ExportString.ChannelName)
{
var channelImports = ((ImportPreviewListOutput<Channel>)item.Value).Data;
channels = channelImports;
break;
}
}
upData = channels.Where(a => a.IsUp).ToList();
insertData = channels.Where(a => !a.IsUp).ToList();
}
public static USheetDatas ExportChannel(IEnumerable<Channel> channels) public static USheetDatas ExportChannel(IEnumerable<Channel> channels)
{ {
var rows = ExportRows(channels); // IEnumerable 延迟执行 var rows = ExportRows(channels); // IEnumerable 延迟执行

View File

@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application
{
public interface IChannelPageService
{
Task RestartChannelAsync(long channelId);
Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id);
Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel);
Task CopyChannelAsync(int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long channelId, bool AutoRestartThread);
/// <summary>
/// 保存通道
/// </summary>
/// <param name="input">通道对象</param>
/// <param name="type">保存类型</param>
/// <param name="restart">重启</param>
Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart);
/// <summary>
/// 批量修改
/// </summary>
/// <param name="models">列表</param>
/// <param name="oldModel">旧数据</param>
/// <param name="model">新数据</param>
/// <param name="restart">重启</param>
/// <returns></returns>
Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart);
/// <summary>
/// 删除通道
/// </summary>
Task<bool> DeleteChannelAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken);
Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart);
}
}

View File

@@ -14,15 +14,8 @@ using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Gateway.Application; namespace ThingsGateway.Gateway.Application;
public interface IChannelRuntimeService public interface IChannelRuntimeService : IChannelPageService
{ {
/// <summary>
/// 保存通道
/// </summary>
/// <param name="input">通道对象</param>
/// <param name="type">保存类型</param>
/// <param name="restart">重启</param>
Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart);
/// <summary> /// <summary>
/// 保存通道 /// 保存通道
@@ -32,25 +25,13 @@ public interface IChannelRuntimeService
/// <param name="restart">重启</param> /// <param name="restart">重启</param>
Task<bool> BatchSaveChannelAsync(List<Channel> input, ItemChangedType type, bool restart); Task<bool> BatchSaveChannelAsync(List<Channel> input, ItemChangedType type, bool restart);
/// <summary>
/// 批量修改
/// </summary>
/// <param name="models">列表</param>
/// <param name="oldModel">旧数据</param>
/// <param name="model">新数据</param>
/// <param name="restart">重启</param>
/// <returns></returns>
Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart);
/// <summary>
/// 删除通道
/// </summary>
Task<bool> DeleteChannelAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// 导入通道数据 /// 导入通道数据
/// </summary> /// </summary>
Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart); Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart);
Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter); Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter);
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile); Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile);
Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data); Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data);

View File

@@ -102,4 +102,6 @@ internal interface IChannelService
Task UpdateLogAsync(long channelId, TouchSocket.Core.LogLevel logLevel); Task UpdateLogAsync(long channelId, TouchSocket.Core.LogLevel logLevel);
Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables); Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables);
Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables); Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables);
Task<HashSet<long>> ImportAsync(List<Channel> upData, List<Channel> insertData);
} }

View File

@@ -439,6 +439,12 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
public void SetDeviceData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, IReadOnlyDictionary<string, ChannelRuntime> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ref ImportPreviewOutput<Device> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows) public void SetDeviceData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, IReadOnlyDictionary<string, ChannelRuntime> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ref ImportPreviewOutput<Device> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
{ {
#region sheet #region sheet
string ImportNullError = Localizer["ImportNullError"];
string RedundantDeviceError = Localizer["RedundantDeviceError"];
string ChannelError = Localizer["ChannelError"];
string PluginNotNull = Localizer["PluginNotNull"];
string DeviceNotNull = Localizer["DeviceNotNull"];
if (sheetName == ExportString.DeviceName) if (sheetName == ExportString.DeviceName)
{ {
@@ -459,6 +465,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
var deviceProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite) var deviceProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name), a => (a, a.IsNullableType())); .ToDictionary(a => type.GetPropertyDisplayName(a.Name), a => (a, a.IsNullableType()));
// 遍历每一行数据 // 遍历每一行数据
rows.ForEach(item => rows.ForEach(item =>
{ {
@@ -471,7 +479,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (device == null) if (device == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return; return;
} }
@@ -488,7 +496,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{ {
// 如果找不到对应的冗余设备,则添加错误信息到导入预览结果并返回 // 如果找不到对应的冗余设备,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["RedundantDeviceError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, RedundantDeviceError));
return; return;
} }
} }
@@ -498,7 +506,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (device.RedundantEnable) if (device.RedundantEnable)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["RedundantDeviceError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, RedundantDeviceError));
return; return;
} }
} }
@@ -512,7 +520,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{ {
// 如果找不到对应的通道信息,则添加错误信息到导入预览结果并返回 // 如果找不到对应的通道信息,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ChannelError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ChannelError));
return; return;
} }
} }
@@ -520,7 +528,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{ {
// 如果未提供通道信息,则添加错误信息到导入预览结果并返回 // 如果未提供通道信息,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ChannelError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ChannelError));
return; return;
} }
@@ -653,7 +661,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (propertys.Item1 == null) if (propertys.Item1 == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["PluginNotNull"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, PluginNotNull));
continue; continue;
} }
@@ -661,7 +669,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (!item.TryGetValue(ExportString.DeviceName, out var deviceName)) if (!item.TryGetValue(ExportString.DeviceName, out var deviceName))
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["DeviceNotNull"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
continue; continue;
} }
@@ -684,7 +692,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (pluginProp == null) if (pluginProp == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return; return;
} }

View File

@@ -47,7 +47,7 @@ public partial class ManagementTask : AsyncDisposableObject
{ {
_logger?.Log_Out(logLevel, source, message, exception); _logger?.Log_Out(logLevel, source, message, exception);
} }
private bool success = true;
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
if (!_managementOptions.Enable) return; if (!_managementOptions.Enable) return;
@@ -65,10 +65,14 @@ public partial class ManagementTask : AsyncDisposableObject
try try
{ {
await EnsureChannelOpenAsync(cancellationToken).ConfigureAwait(false); await EnsureChannelOpenAsync(cancellationToken).ConfigureAwait(false);
success = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
LogMessage?.LogWarning(ex, "Start"); if (success)
LogMessage?.LogWarning(ex, "Start");
success = false;
} }
finally finally
{ {

View File

@@ -619,6 +619,14 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
public ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows) public ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
{ {
string ImportNullError = Localizer["ImportNullError"];
string RedundantDeviceError = Localizer["RedundantDeviceError"];
string PluginNotNull = Localizer["PluginNotNull"];
string DeviceNotNull = Localizer["DeviceNotNull"];
string VariableNotNull = Localizer["VariableNotNull"];
// 变量页处理 // 变量页处理
if (sheetName == ExportString.VariableName) if (sheetName == ExportString.VariableName)
{ {
@@ -742,7 +750,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (alarm == null) if (alarm == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return; return;
} }
@@ -754,13 +762,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (collectDevName == null || collectDevice == null) if (collectDevName == null || collectDevice == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["DeviceNotNull"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
return; return;
} }
if (variableNameObj == null) if (variableNameObj == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["VariableNotNull"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, VariableNotNull));
return; return;
} }
@@ -798,7 +806,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
else else
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["VariableNotNull"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, VariableNotNull));
return; return;
} }
} }
@@ -863,7 +871,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (propertys.Item3?.Count == null || propertys.Item1 == null) if (propertys.Item3?.Count == null || propertys.Item1 == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return; return;
} }
@@ -874,7 +882,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (pluginProp == null) if (pluginProp == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return; return;
} }
@@ -889,13 +897,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (businessDevName == null || businessDevice == null || collectDevName == null || collectDevice == null) if (businessDevName == null || businessDevice == null || collectDevName == null || collectDevice == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["DeviceNotNull"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
return; return;
} }
if (variableNameObj == null) if (variableNameObj == null)
{ {
importPreviewOutput.HasError = true; importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["VariableNotNull"])); importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, VariableNotNull));
return; return;
} }

View File

@@ -72,6 +72,7 @@ public class Startup : AppStartup
services.AddSingleton<IChannelThreadManage, ChannelThreadManage>(); services.AddSingleton<IChannelThreadManage, ChannelThreadManage>();
services.AddSingleton<IChannelService, ChannelService>(); services.AddSingleton<IChannelService, ChannelService>();
services.AddSingleton<IChannelPageService>(a => a.GetService<IChannelRuntimeService>());
services.AddSingleton<IChannelRuntimeService, ChannelRuntimeService>(); services.AddSingleton<IChannelRuntimeService, ChannelRuntimeService>();
services.AddSingleton<IVariableService, VariableService>(); services.AddSingleton<IVariableService, VariableService>();
services.AddSingleton<IVariableRuntimeService, VariableRuntimeService>(); services.AddSingleton<IVariableRuntimeService, VariableRuntimeService>();

View File

@@ -10,14 +10,7 @@
export function getAutoRestartThread() { export function getAutoRestartThread() {
return JSON.parse(localStorage.getItem('autoRestartThread'))??true; return JSON.parse(localStorage.getItem('autoRestartThread'))??true;
} }
export function getShowType() {
return JSON.parse(localStorage.getItem('showType'))??0;
}
export function saveShowType(showType) {
if (localStorage) {
localStorage.setItem('showType', JSON.stringify(showType));
}
}
export function saveAutoRestartThread(autoRestartThread) { export function saveAutoRestartThread(autoRestartThread) {
if (localStorage) { if (localStorage) {
localStorage.setItem('autoRestartThread', JSON.stringify(autoRestartThread)); localStorage.setItem('autoRestartThread', JSON.stringify(autoRestartThread));

View File

@@ -8,8 +8,6 @@
// QQ群605534569 // QQ群605534569
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using ThingsGateway.DB;
namespace ThingsGateway.Gateway.Razor; namespace ThingsGateway.Gateway.Razor;
public partial class ChannelCopyComponent public partial class ChannelCopyComponent
@@ -17,16 +15,10 @@ public partial class ChannelCopyComponent
[Inject] [Inject]
IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; } IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
[Parameter]
[EditorRequired]
public Channel Model { get; set; }
[Parameter] [Parameter]
[EditorRequired] public Func<int, string, int, string, int, Task> OnSave { get; set; }
public Dictionary<Device, List<Variable>> Devices { get; set; }
[Parameter]
public Func<List<Channel>, Dictionary<Device, List<Variable>>, Task> OnSave { get; set; }
[CascadingParameter] [CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; } private Func<Task>? OnCloseAsync { get; set; }
@@ -44,38 +36,8 @@ public partial class ChannelCopyComponent
{ {
try try
{ {
List<Channel> channels = new();
Dictionary<Device, List<Variable>> devices = new();
for (int i = 0; i < CopyCount; i++)
{
Channel channel = Model.AdaptChannel();
channel.Id = CommonUtils.GetSingleId();
channel.Name = $"{CopyChannelNamePrefix}{CopyChannelNameSuffixNumber + i}";
int index = 0;
foreach (var item in Devices)
{
Device device = item.Key.AdaptDevice();
device.Id = CommonUtils.GetSingleId();
device.Name = $"{channel.Name}_{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + (index++)}";
device.ChannelId = channel.Id;
List<Variable> variables = new();
foreach (var variable in item.Value)
{
Variable v = variable.AdaptVariable();
v.Id = CommonUtils.GetSingleId();
v.DeviceId = device.Id;
variables.Add(v);
}
devices.Add(device, variables);
}
channels.Add(channel);
}
if (OnSave != null) if (OnSave != null)
await OnSave(channels, devices); await OnSave(CopyCount, CopyChannelNamePrefix, CopyChannelNameSuffixNumber, CopyDeviceNamePrefix, CopyDeviceNameSuffixNumber);
if (OnCloseAsync != null) if (OnCloseAsync != null)
await OnCloseAsync(); await OnCloseAsync();
await ToastService.Default(); await ToastService.Default();

View File

@@ -56,9 +56,12 @@ public partial class ChannelEditComponent
[Parameter] [Parameter]
public PluginTypeEnum? PluginType { get; set; } public PluginTypeEnum? PluginType { get; set; }
protected override void OnInitialized() [Inject]
IPluginPageService PluginService { get; set; }
protected override async Task OnInitializedAsync()
{ {
var plugins = GlobalData.PluginService.GetPluginList(PluginType); var plugins = await PluginService.GetPluginsAsync(PluginType);
PluginDcit = plugins.ToDictionary(a => a.FullName); PluginDcit = plugins.ToDictionary(a => a.FullName);
PluginNames = plugins.BuildPluginSelectList(); PluginNames = plugins.BuildPluginSelectList();

View File

@@ -5,10 +5,10 @@
<ChannelRuntimeInfo1 ChannelRuntime="ChannelRuntime" /> <ChannelRuntimeInfo1 ChannelRuntime="ChannelRuntime" />
<LogConsole HeightString="calc(100% - 270px)" LogLevel=@((ChannelRuntime?.DeviceThreadManage?.LogMessage)?.LogLevel ?? TouchSocket.Core.LogLevel.Trace) <LogConsole HeightString="calc(100% - 270px)" LogLevel=@(LogLevel)
LogLevelChanged="(logLevel)=> LogLevelChanged="async(logLevel)=>
{ {
ChannelRuntime.DeviceThreadManage?.SetLogAsync(logLevel); LogLevel = logLevel;
}" await ChannelPageService.SetChannelLogLevelAsync(ChannelRuntime?.Id ?? 0, logLevel);
}"
LogPath=@ChannelRuntime.LogPath></LogConsole> LogPath=@ChannelRuntime.LogPath></LogConsole>

View File

@@ -14,4 +14,27 @@ public partial class ChannelRuntimeInfo
{ {
[Parameter, EditorRequired] [Parameter, EditorRequired]
public ChannelRuntime ChannelRuntime { get; set; } public ChannelRuntime ChannelRuntime { get; set; }
public TouchSocket.Core.LogLevel LogLevel { get; set; }
[Inject]
IChannelPageService ChannelPageService { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (ChannelRuntime?.Id > 0)
{
var logLevel = await ChannelPageService.ChannelLogLevelAsync(ChannelRuntime.Id);
if (logLevel != LogLevel)
{
LogLevel = logLevel;
await InvokeAsync(StateHasChanged);
}
}
if (firstRender)
await InvokeAsync(StateHasChanged);
await base.OnAfterRenderAsync(firstRender);
}
} }

View File

@@ -12,7 +12,7 @@
<HeaderTemplate> <HeaderTemplate>
<span class=@((ChannelRuntime?.DeviceThreadManage!=null?"enable--text mx-1 text-h6 overflow-y-auto":"disabled--text mx-1 text-h6 overflow-y-auto"))> <span class=@((ChannelRuntime?.Started==true ?"enable--text mx-1 text-h6 overflow-y-auto":"disabled--text mx-1 text-h6 overflow-y-auto"))>
<span class="text-truncate" title=@Name>@(Name)</span> <span class="text-truncate" title=@Name>@(Name)</span>
</span> </span>

View File

@@ -17,14 +17,12 @@ public partial class ChannelRuntimeInfo1 : IDisposable
[Parameter, EditorRequired] [Parameter, EditorRequired]
public ChannelRuntime ChannelRuntime { get; set; } public ChannelRuntime ChannelRuntime { get; set; }
private string Name => $"{ChannelRuntime.ToString()} - {(ChannelRuntime.DeviceThreadManage == null ? "Task cancel" : "Task run")}"; private string Name => $"{ChannelRuntime.ToString()} - {(ChannelRuntime.Started == false ? "Task cancel" : "Task run")}";
[Inject]
IChannelPageService ChannelPageService { get; set; }
private async Task RestartChannelAsync() private async Task RestartChannelAsync()
{ {
if (ChannelRuntime.DeviceThreadManage?.ChannelThreadManage != null) await ChannelPageService.RestartChannelAsync(ChannelRuntime.Id);
await Task.Run(() => ChannelRuntime.DeviceThreadManage.ChannelThreadManage.RestartChannelAsync(ChannelRuntime));
else
await Task.Run(() => GlobalData.ChannelThreadManage.RestartChannelAsync(ChannelRuntime));
} }
protected override void OnInitialized() protected override void OnInitialized()

View File

@@ -100,6 +100,9 @@ public partial class ChannelTable : IDisposable
#region #region
[Inject]
IChannelPageService ChannelPageService { get; set; }
#region #region
private async Task Copy(IEnumerable<ChannelRuntime> channels) private async Task Copy(IEnumerable<ChannelRuntime> channels)
{ {
@@ -110,13 +113,6 @@ public partial class ChannelTable : IDisposable
return; return;
} }
Channel oneModel = null;
Dictionary<Device, List<Variable>> deviceDict = new();
oneModel = channelRuntime.AdaptChannel();
oneModel.Id = 0;
deviceDict = channelRuntime.ReadDeviceRuntimes.ToDictionary(a => a.Value.AdaptDevice(), a => a.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable());
var op = new DialogOption() var op = new DialogOption()
{ {
IsScrolling = false, IsScrolling = false,
@@ -129,13 +125,11 @@ public partial class ChannelTable : IDisposable
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?> op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?>
{ {
{nameof(ChannelCopyComponent.OnSave), async (List<Channel> channels,Dictionary<Device,List<Variable>> devices) => {nameof(ChannelCopyComponent.OnSave), async (int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber) =>
{ {
await Task.Run(() =>GlobalData.ChannelRuntimeService.CopyAsync(channels,devices,AutoRestartThread, default)); await Task.Run(() =>ChannelPageService.CopyChannelAsync(CopyCount,CopyChannelNamePrefix,CopyChannelNameSuffixNumber,CopyDeviceNamePrefix,CopyDeviceNameSuffixNumber,channelRuntime.Id,AutoRestartThread));
await InvokeAsync(table.QueryAsync); await InvokeAsync(table.QueryAsync);
}}, }},
{nameof(ChannelCopyComponent.Model),oneModel },
{nameof(ChannelCopyComponent.Devices),deviceDict },
}); });
await DialogService.Show(op); await DialogService.Show(op);
@@ -168,7 +162,7 @@ public partial class ChannelTable : IDisposable
{ {
{nameof(ChannelEditComponent.OnValidSubmit), async () => {nameof(ChannelEditComponent.OnValidSubmit), async () =>
{ {
await Task.Run(() => GlobalData.ChannelRuntimeService.BatchEditAsync(changedModels, oldModel, oneModel,AutoRestartThread)); await Task.Run(() => ChannelPageService.BatchEditAsync(changedModels, oldModel, oneModel,AutoRestartThread));
await InvokeAsync(table.QueryAsync); await InvokeAsync(table.QueryAsync);
} }, } },
@@ -185,7 +179,7 @@ public partial class ChannelTable : IDisposable
{ {
try try
{ {
return await Task.Run(async () => await GlobalData.ChannelRuntimeService.DeleteChannelAsync(channels.Select(a => a.Id), AutoRestartThread, default)); return await Task.Run(async () => await ChannelPageService.DeleteChannelAsync(channels.Select(a => a.Id), AutoRestartThread, default));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -199,7 +193,7 @@ public partial class ChannelTable : IDisposable
try try
{ {
channel = channel.AdaptChannel(); channel = channel.AdaptChannel();
return await Task.Run(() => GlobalData.ChannelRuntimeService.SaveChannelAsync(channel, itemChangedType, AutoRestartThread)); return await Task.Run(() => ChannelPageService.SaveChannelAsync(channel, itemChangedType, AutoRestartThread));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -286,7 +280,9 @@ public partial class ChannelTable : IDisposable
await Task.Run(async ()=> await Task.Run(async ()=>
{ {
var importData=await ChannelServiceHelpers.ImportAsync(data); var importData=await ChannelServiceHelpers.ImportAsync(data);
await GlobalData.ChannelRuntimeService.ImportChannelAsync(importData,AutoRestartThread); ChannelServiceHelpers. GetImportChannelData(importData, out var upData, out var insertData);
await ChannelPageService.ImportChannelAsync(upData,insertData,AutoRestartThread);
}) })
; ;
} }
@@ -338,7 +334,7 @@ finally
{ {
await Task.Run(async () => await Task.Run(async () =>
{ {
await GlobalData.ChannelRuntimeService.DeleteChannelAsync(Items.Select(a => a.Id), AutoRestartThread, default); await ChannelPageService.DeleteChannelAsync(Items.Select(a => a.Id), AutoRestartThread, default);
await InvokeAsync(async () => await InvokeAsync(async () =>
{ {
await ToastService.Default(); await ToastService.Default();

View File

@@ -80,35 +80,6 @@ namespace ThingsGateway.Gateway.Razor
return pluginTypeEnum; 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);
}
} }
} }

View File

@@ -1,6 +1,4 @@
@inherits ThingsGatewayModuleComponentBase @namespace ThingsGateway.Gateway.Razor
@attribute [JSModuleAutoLoader("Pages/GatewayMonitorPage/ChannelDeviceTree.razor.js", AutoInvokeDispose = false)]
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway.Admin.Application @using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor @using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application @using ThingsGateway.Gateway.Application

View File

@@ -10,16 +10,18 @@
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor; using ThingsGateway.Admin.Razor;
using ThingsGateway.NewLife.Extension; using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Json.Extension; using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Razor.Extension;
using ThingsGateway.SqlSugar; using ThingsGateway.SqlSugar;
namespace ThingsGateway.Gateway.Razor; namespace ThingsGateway.Gateway.Razor;
public partial class ChannelDeviceTree public partial class ChannelDeviceTree : IDisposable
{ {
SpinnerComponent Spinner; SpinnerComponent Spinner;
[Inject] [Inject]
@@ -41,21 +43,29 @@ public partial class ChannelDeviceTree
public EventCallback<ShowTypeEnum?> ShowTypeChanged { get; set; } public EventCallback<ShowTypeEnum?> ShowTypeChanged { get; set; }
[Parameter] [Parameter]
public ShowTypeEnum? ShowType { get; set; } public ShowTypeEnum? ShowType { get; set; }
[Inject]
IJSRuntime JSRuntime { get; set; }
private async Task OnShowTypeChanged(ShowTypeEnum? showType) private async Task OnShowTypeChanged(ShowTypeEnum? showType)
{ {
ShowType = showType; ShowType = showType;
if (showType != null && Module != null) if (showType != null)
await Module!.InvokeVoidAsync("saveShowType", ShowType); await JSRuntime.SetLocalStorage("showType", ShowType);
if (ShowTypeChanged.HasDelegate) if (ShowTypeChanged.HasDelegate)
await ShowTypeChanged.InvokeAsync(showType); await ShowTypeChanged.InvokeAsync(showType);
} }
protected override async Task InvokeInitAsync()
protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
var showType = await Module!.InvokeAsync<ShowTypeEnum>("getShowType"); if (firstRender)
await OnShowTypeChanged(showType); {
var showType = await JSRuntime!.GetLocalStorage<ShowTypeEnum>("showType");
await OnShowTypeChanged(showType);
StateHasChanged();
}
await base.OnAfterRenderAsync(firstRender);
} }
[Parameter] [Parameter]
public bool AutoRestartThread { get; set; } public bool AutoRestartThread { get; set; }
@@ -140,15 +150,10 @@ public partial class ChannelDeviceTree
async Task CopyChannel(string text, ChannelDeviceTreeItem channelDeviceTreeItem) async Task CopyChannel(string text, ChannelDeviceTreeItem channelDeviceTreeItem)
{ {
Channel oneModel = null;
Dictionary<Device, List<Variable>> deviceDict = new();
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime)) if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
{ {
oneModel = channelRuntime.AdaptChannel();
oneModel.Id = 0;
deviceDict = channelRuntime.ReadDeviceRuntimes.ToDictionary(a => a.Value.AdaptDevice(), a => a.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable());
} }
else else
{ {
@@ -167,19 +172,16 @@ public partial class ChannelDeviceTree
op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?> op.Component = BootstrapDynamicComponent.CreateComponent<ChannelCopyComponent>(new Dictionary<string, object?>
{ {
{nameof(ChannelCopyComponent.OnSave), async (List<Channel> channels,Dictionary<Device,List<Variable>> devices) => {nameof(ChannelCopyComponent.OnSave), async (int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber) =>
{ {
await Task.Run(() =>GlobalData.ChannelRuntimeService.CopyAsync(channels,devices,AutoRestartThread, default)); await Task.Run(() =>ChannelPageService.CopyChannelAsync(CopyCount,CopyChannelNamePrefix,CopyChannelNameSuffixNumber,CopyDeviceNamePrefix,CopyDeviceNameSuffixNumber,channelRuntime.Id,AutoRestartThread));
//await Notify();
}}, }},
{nameof(ChannelCopyComponent.Model),oneModel },
{nameof(ChannelCopyComponent.Devices),deviceDict },
}); });
await DialogService.Show(op); await DialogService.Show(op);
} }
[Inject]
IChannelPageService ChannelPageService { get; set; }
Task BatchEditChannel(ContextMenuItem item, object value) Task BatchEditChannel(ContextMenuItem item, object value)
{ {
return BatchEditChannel(item.Text, value as ChannelDeviceTreeItem); return BatchEditChannel(item.Text, value as ChannelDeviceTreeItem);
@@ -1360,11 +1362,10 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
} }
} }
private bool Disposed; private bool Disposed;
protected override ValueTask DisposeAsync(bool disposing) public void Dispose()
{ {
Disposed = true; Disposed = true;
ChannelRuntimeDispatchService.UnSubscribe(Refresh); ChannelRuntimeDispatchService.UnSubscribe(Refresh);
return base.DisposeAsync(disposing);
} }
ChannelDeviceTreeItem? SelectModel = default; ChannelDeviceTreeItem? SelectModel = default;
@@ -1381,4 +1382,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@@ -1,9 +0,0 @@

export function getShowType() {
return JSON.parse(localStorage.getItem('showType'))??0;
}
export function saveShowType(showType) {
if (localStorage) {
localStorage.setItem('showType', JSON.stringify(showType));
}
}

View File

@@ -13,7 +13,7 @@
<link href="ThingsGateway.Debug.Photino.styles.css" rel="stylesheet" /> <link href="ThingsGateway.Debug.Photino.styles.css" rel="stylesheet" />
<link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" /> <link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" />
<script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 --> <script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 -->
<script src="/_content/ThingsGateway.Razor/js/culture.js"></script> <script src="/_content/ThingsGateway.Razor/js/localStorageUtil.js"></script>
</head> </head>

View File

@@ -15,7 +15,7 @@
<link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" /> <link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" />
<link href="/_content/ThingsGateway.Razor/css/devui.css" rel="stylesheet" /> <link href="/_content/ThingsGateway.Razor/css/devui.css" rel="stylesheet" />
<script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 --> <script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 -->
<script src="/_content/ThingsGateway.Razor/js/culture.js"></script> <script src="/_content/ThingsGateway.Razor/js/localStorageUtil.js"></script>
</head> </head>

View File

@@ -0,0 +1,19 @@
{
"$schema": "null",
"RemoteClientManagement": {
"Enable": true,
"Name": "ThingsGateway",
"ServerUri": "127.0.0.1:8299",
"VerifyToken": "ThingsGateway",
"HeartbeatInterval": 3000
},
"RemoteServerManagement": {
"Enable": true,
"Name": "ThingsGateway",
"ServerUri": "0.0.0.0:7999",
"VerifyToken": "ThingsGateway",
"HeartbeatInterval": 3000
}
}

View File

@@ -4,13 +4,13 @@
"RemoteClientManagement": { "RemoteClientManagement": {
"Enable": false, "Enable": false,
"Name": "ThingsGateway", "Name": "ThingsGateway",
"ServerUri": "127.0.0.1:7999", "ServerUri": "127.0.0.1:8299",
"VerifyToken": "ThingsGateway", "VerifyToken": "ThingsGateway",
"HeartbeatInterval": 3000 "HeartbeatInterval": 3000
}, },
"RemoteServerManagement": { "RemoteServerManagement": {
"Enable": true, "Enable": false,
"Name": "ThingsGateway", "Name": "ThingsGateway",
"ServerUri": "0.0.0.0:7999", "ServerUri": "0.0.0.0:7999",
"VerifyToken": "ThingsGateway", "VerifyToken": "ThingsGateway",

View File

@@ -47,7 +47,7 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script> <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<!-- PWA Service Worker --> <!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -43,7 +43,7 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script> <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<!-- PWA Service Worker --> <!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -48,7 +48,7 @@
</app> </app>
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script> <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.server.js"></script> <script src="_framework/blazor.server.js"></script>
<!-- PWA Service Worker --> <!-- PWA Service Worker -->