适配远程管理客户端

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)" />
<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>
<!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -45,7 +45,7 @@
</app>
<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>
<!-- PWA Service Worker -->

View File

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

View File

@@ -10,7 +10,7 @@
using Microsoft.JSInterop;
namespace ThingsGateway.Common.Extension;
namespace ThingsGateway.Razor.Extension;
/// <summary>
/// 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>
<PropertyGroup>
<PluginVersion>10.10.11</PluginVersion>
<ProPluginVersion>10.10.11</ProPluginVersion>
<DefaultVersion>10.10.14</DefaultVersion>
<PluginVersion>10.10.12</PluginVersion>
<ProPluginVersion>10.10.12</ProPluginVersion>
<DefaultVersion>10.10.15</DefaultVersion>
<AuthenticationVersion>10.10.1</AuthenticationVersion>
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version>

View File

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

View File

@@ -177,6 +177,15 @@ public static class GlobalData
}
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)
{
Dictionary<IDeviceThreadManage, List<DeviceRuntime>> deviceThreadManages = new();

View File

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

View File

@@ -23,27 +23,15 @@ namespace ThingsGateway.Gateway.Application;
/// <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>
/// 是否采集
/// </summary>
public PluginTypeEnum? PluginType => PluginInfo?.PluginType;
/// <summary>
/// 是否采集
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public bool? IsCollect => PluginInfo == null ? null : PluginInfo?.PluginType == PluginTypeEnum.Collect;
#if !Management
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
@@ -105,14 +93,67 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
[Newtonsoft.Json.JsonIgnore]
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]
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public IDeviceThreadManage? DeviceThreadManage { get; internal set; }
[AutoGenerateColumn(Ignore = true)]
public string LogPath => Name.GetChannelLogPath();
public void Init()
{
@@ -135,14 +176,8 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
DeviceThreadManage = null;
GC.SuppressFinalize(this);
}
public override string ToString()
{
if (ChannelType == ChannelTypeEnum.Other)
{
return Name;
}
return $"{Name}[{base.ToString()}]";
}
public IChannel GetChannel(TouchSocketConfig config)
{
@@ -192,4 +227,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
return ichannel;
}
}
#endif
}

View File

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

View File

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

View File

@@ -8,9 +8,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Newtonsoft.Json.Linq;
using Riok.Mapperly.Abstractions;
#if !Management
using ThingsGateway.Gateway.Application.Extensions;
#endif
using ThingsGateway.NewLife.DictionaryExtensions;
using ThingsGateway.NewLife.Json.Extension;
@@ -19,8 +25,194 @@ namespace ThingsGateway.Gateway.Application;
/// <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;
@@ -33,15 +225,19 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
private bool _isOnlineChanged;
private bool _valueInited;
private string _lastErrorMessage;
private object _value;
private object lastSetValue;
private object rawValue;
#if !Management
#pragma warning disable CS0649
private string _lastErrorMessage;
#pragma warning restore CS0649
private DeviceRuntime? deviceRuntime;
private IVariableSource? variableSource;
private VariableMethod? variableMethod;
private IThingsGatewayBitConverter? thingsGatewayBitConverter;
/// <summary>
/// 设置变量值与时间/质量戳
/// </summary>
@@ -224,4 +420,10 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
{
_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
{
private ILogger _logger;
public ChannelRuntimeService(ILogger<ChannelRuntimeService> logger)
private Microsoft.Extensions.Logging.ILogger _logger;
public ChannelRuntimeService(Microsoft.Extensions.Logging.ILogger<ChannelRuntimeService> logger)
{
_logger = logger;
}
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)
{
try
@@ -204,6 +281,31 @@ public class ChannelRuntimeService : IChannelRuntimeService
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)
{
try

View File

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

View File

@@ -18,6 +18,23 @@ namespace ThingsGateway.Gateway.Application;
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)
{
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;
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>
/// 保存通道
@@ -32,25 +25,13 @@ public interface IChannelRuntimeService
/// <param name="restart">重启</param>
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>
Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart);
Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter);
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile);
Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data);

View File

@@ -102,4 +102,6 @@ internal interface IChannelService
Task UpdateLogAsync(long channelId, TouchSocket.Core.LogLevel logLevel);
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<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)
{
#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)
{
@@ -459,6 +465,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
var deviceProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name), a => (a, a.IsNullableType()));
// 遍历每一行数据
rows.ForEach(item =>
{
@@ -471,7 +479,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (device == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return;
}
@@ -488,7 +496,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
// 如果找不到对应的冗余设备,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["RedundantDeviceError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, RedundantDeviceError));
return;
}
}
@@ -498,7 +506,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (device.RedundantEnable)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["RedundantDeviceError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, RedundantDeviceError));
return;
}
}
@@ -512,7 +520,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
// 如果找不到对应的通道信息,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ChannelError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ChannelError));
return;
}
}
@@ -520,7 +528,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
// 如果未提供通道信息,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ChannelError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ChannelError));
return;
}
@@ -653,7 +661,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (propertys.Item1 == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["PluginNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, PluginNotNull));
continue;
}
@@ -661,7 +669,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (!item.TryGetValue(ExportString.DeviceName, out var deviceName))
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["DeviceNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
continue;
}
@@ -684,7 +692,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (pluginProp == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return;
}

View File

@@ -47,7 +47,7 @@ public partial class ManagementTask : AsyncDisposableObject
{
_logger?.Log_Out(logLevel, source, message, exception);
}
private bool success = true;
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!_managementOptions.Enable) return;
@@ -65,10 +65,14 @@ public partial class ManagementTask : AsyncDisposableObject
try
{
await EnsureChannelOpenAsync(cancellationToken).ConfigureAwait(false);
success = true;
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, "Start");
if (success)
LogMessage?.LogWarning(ex, "Start");
success = false;
}
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)
{
string ImportNullError = Localizer["ImportNullError"];
string RedundantDeviceError = Localizer["RedundantDeviceError"];
string PluginNotNull = Localizer["PluginNotNull"];
string DeviceNotNull = Localizer["DeviceNotNull"];
string VariableNotNull = Localizer["VariableNotNull"];
// 变量页处理
if (sheetName == ExportString.VariableName)
{
@@ -742,7 +750,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (alarm == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return;
}
@@ -754,13 +762,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (collectDevName == null || collectDevice == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["DeviceNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
return;
}
if (variableNameObj == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["VariableNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, VariableNotNull));
return;
}
@@ -798,7 +806,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
else
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["VariableNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, VariableNotNull));
return;
}
}
@@ -863,7 +871,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (propertys.Item3?.Count == null || propertys.Item1 == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return;
}
@@ -874,7 +882,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (pluginProp == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return;
}
@@ -889,13 +897,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (businessDevName == null || businessDevice == null || collectDevName == null || collectDevice == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["DeviceNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
return;
}
if (variableNameObj == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["VariableNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, VariableNotNull));
return;
}

View File

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

View File

@@ -10,14 +10,7 @@
export function getAutoRestartThread() {
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) {
if (localStorage) {
localStorage.setItem('autoRestartThread', JSON.stringify(autoRestartThread));

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.DB;
namespace ThingsGateway.Gateway.Razor;
public partial class ChannelCopyComponent
@@ -17,16 +15,10 @@ public partial class ChannelCopyComponent
[Inject]
IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
[Parameter]
[EditorRequired]
public Channel Model { get; set; }
[Parameter]
[EditorRequired]
public Dictionary<Device, List<Variable>> Devices { get; set; }
[Parameter]
public Func<List<Channel>, Dictionary<Device, List<Variable>>, Task> OnSave { get; set; }
public Func<int, string, int, string, int, Task> OnSave { get; set; }
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
@@ -44,38 +36,8 @@ public partial class ChannelCopyComponent
{
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)
await OnSave(channels, devices);
await OnSave(CopyCount, CopyChannelNamePrefix, CopyChannelNameSuffixNumber, CopyDeviceNamePrefix, CopyDeviceNameSuffixNumber);
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();

View File

@@ -56,9 +56,12 @@ public partial class ChannelEditComponent
[Parameter]
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);
PluginNames = plugins.BuildPluginSelectList();

View File

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

View File

@@ -14,4 +14,27 @@ public partial class ChannelRuntimeInfo
{
[Parameter, EditorRequired]
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>
<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>

View File

@@ -17,14 +17,12 @@ public partial class ChannelRuntimeInfo1 : IDisposable
[Parameter, EditorRequired]
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()
{
if (ChannelRuntime.DeviceThreadManage?.ChannelThreadManage != null)
await Task.Run(() => ChannelRuntime.DeviceThreadManage.ChannelThreadManage.RestartChannelAsync(ChannelRuntime));
else
await Task.Run(() => GlobalData.ChannelThreadManage.RestartChannelAsync(ChannelRuntime));
await ChannelPageService.RestartChannelAsync(ChannelRuntime.Id);
}
protected override void OnInitialized()

View File

@@ -100,6 +100,9 @@ public partial class ChannelTable : IDisposable
#region
[Inject]
IChannelPageService ChannelPageService { get; set; }
#region
private async Task Copy(IEnumerable<ChannelRuntime> channels)
{
@@ -110,13 +113,6 @@ public partial class ChannelTable : IDisposable
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()
{
IsScrolling = false,
@@ -129,13 +125,11 @@ public partial class ChannelTable : IDisposable
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);
}},
{nameof(ChannelCopyComponent.Model),oneModel },
{nameof(ChannelCopyComponent.Devices),deviceDict },
});
await DialogService.Show(op);
@@ -168,7 +162,7 @@ public partial class ChannelTable : IDisposable
{
{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);
} },
@@ -185,7 +179,7 @@ public partial class ChannelTable : IDisposable
{
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)
{
@@ -199,7 +193,7 @@ public partial class ChannelTable : IDisposable
try
{
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)
{
@@ -286,7 +280,9 @@ public partial class ChannelTable : IDisposable
await Task.Run(async ()=>
{
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 GlobalData.ChannelRuntimeService.DeleteChannelAsync(Items.Select(a => a.Id), AutoRestartThread, default);
await ChannelPageService.DeleteChannelAsync(Items.Select(a => a.Id), AutoRestartThread, default);
await InvokeAsync(async () =>
{
await ToastService.Default();

View File

@@ -80,35 +80,6 @@ namespace ThingsGateway.Gateway.Razor
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
@attribute [JSModuleAutoLoader("Pages/GatewayMonitorPage/ChannelDeviceTree.razor.js", AutoInvokeDispose = false)]
@namespace ThingsGateway.Gateway.Razor
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application

View File

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

View File

@@ -15,7 +15,7 @@
<link href="/_content/ThingsGateway.Razor/css/site.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/culture.js"></script>
<script src="/_content/ThingsGateway.Razor/js/localStorageUtil.js"></script>
</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": {
"Enable": false,
"Name": "ThingsGateway",
"ServerUri": "127.0.0.1:7999",
"ServerUri": "127.0.0.1:8299",
"VerifyToken": "ThingsGateway",
"HeartbeatInterval": 3000
},
"RemoteServerManagement": {
"Enable": true,
"Enable": false,
"Name": "ThingsGateway",
"ServerUri": "0.0.0.0:7999",
"VerifyToken": "ThingsGateway",

View File

@@ -47,7 +47,7 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<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>
<!-- PWA Service Worker -->
<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)" />
<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>
<!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -48,7 +48,7 @@
</app>
<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>
<!-- PWA Service Worker -->