mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-11-05 10:03:58 +08:00
适配远程管理客户端
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// 设置 culture
|
|
||||||
function setCultureLocalStorage(culture) {
|
|
||||||
localStorage.setItem("culture", culture);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 culture
|
|
||||||
function getCultureLocalStorage() {
|
|
||||||
return localStorage.getItem("culture");
|
|
||||||
}
|
|
||||||
18
src/Admin/ThingsGateway.Razor/wwwroot/js/localStorageUtil.js
Normal file
18
src/Admin/ThingsGateway.Razor/wwwroot/js/localStorageUtil.js
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 延迟执行
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user