MainView中增加账号异地登录确认和踢出逻辑。

新增UI启动时获取PC信息的逻辑,用于将UI登录信息传递给RT并保存于数据库中。
移除所有ViewModel中的Permission属性和IsPermission属性。
优化WCF诊断输出文件名。
This commit is contained in:
SL 2023-09-20 17:50:00 +08:00
parent 1705bc6d9d
commit 095dd1b03b
12 changed files with 188 additions and 176 deletions

View File

@ -62,10 +62,11 @@
<sharedListeners>
<add name="xml"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="Error.svclog" />
initializeData="SicRT_WCF_Error.svclog" />
</sharedListeners>
</system.diagnostics>
<system.serviceModel>
<bindings>
<netTcpBinding>

View File

@ -51,7 +51,7 @@ using System.Windows;
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
// 方法是按如下所示使用“*”: :
[assembly: AssemblyVersion("1.1.18.47")]
[assembly: AssemblyVersion("2023.09.18.01")]
[assembly: AssemblyInformationalVersion("手动通用版无EFEM")]

View File

@ -4,7 +4,7 @@
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a" />
</configSections>
<system.diagnostics>
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
@ -28,7 +28,7 @@
<sharedListeners>
<add name="xml"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="Error.svclog" />
initializeData="SicUI_WCF_Error.svclog" />
</sharedListeners>
</system.diagnostics>
<system.serviceModel>

View File

@ -9,6 +9,7 @@ using System.Linq;
using System.ServiceModel.Configuration;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Linq;
using Aitex.Core.RT.Log;
@ -140,7 +141,7 @@ namespace SicUI.Client
#endregion
#endif
Task tLoadSysInfo;
try
{
@ -182,6 +183,16 @@ namespace SicUI.Client
#endregion
_splashScreen?.SetMessage1("Initialize logging system ...");
Singleton<Aitex.Core.RT.Log.LogManager>.Instance.Initialize();
BaseApp.Instance = new ClientApp();
tLoadSysInfo = Task.Run(() =>
{
BaseApp.Instance.LoadSystemInfo();
});
_splashScreen?.SetMessage1(
$"Connecting to SicRT ({hostEndpoint.Address.Host}), please wait ...");
@ -208,7 +219,6 @@ namespace SicUI.Client
}
}
if (!EventClient.Instance.ConnectRT())
{
_splashScreen?.Complete();
@ -227,11 +237,10 @@ namespace SicUI.Client
return;
}
_splashScreen?.SetMessage1("Initialize logging system ...");
Singleton<Aitex.Core.RT.Log.LogManager>.Instance.Initialize();
BaseApp.Instance.Initialize();
_splashScreen?.SetMessage1("Initialize sub modules ...");
BaseApp.Instance = new ClientApp();
_splashScreen?.SetMessage1("Preparing Environment ...");
Task.WaitAll(tLoadSysInfo);
_splashScreen?.SetMessage1("Loading the window ...");

View File

@ -30,7 +30,33 @@
</Window.Resources>
<Grid Background="{DynamicResource MainArea_BG}">
<Grid Panel.ZIndex="999" x:Name="LoginPart" Background="{StaticResource Login_BG}">
<Grid x:Name="LoginPart" Panel.ZIndex="99" Background="{StaticResource Login_BG}">
<Grid.Style>
<Style>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsLogin}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Grid.Visibility" Value="Hidden" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsLogin}" Value="False"/>
<Condition Binding="{Binding IsReadOnlyMode}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Grid.Visibility" Value="Hidden" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsLogin}" Value="False"/>
<Condition Binding="{Binding IsReadOnlyMode}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Grid.Visibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Ellipse
MaxWidth="1000"
MaxHeight="800"
@ -48,24 +74,6 @@
BorderThickness="1"
CornerRadius="3">
<Grid>
<userControls:BusyIndicator
Panel.ZIndex="99"
Width="Auto"
Height="Auto"
Padding="20,10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{StaticResource Tab_BG}"
BorderBrush="Gray"
BorderThickness="3"
Message="{Binding RequestLoginIndicatorText}"
Visibility="{Binding IsShowLoginWaiter, Converter={StaticResource BoolVisibilityConverter}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Canceled">
<cal:ActionMessage MethodName="CancelRequestLogin" />
</i:EventTrigger>
</i:Interaction.Triggers>
</userControls:BusyIndicator>
<Path
Width="590"
Height="252.338"
@ -340,15 +348,6 @@
</Grid>
</Border>
</Grid>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsLogin}" Value="True">
<Setter Property="Grid.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
<Border
@ -358,6 +357,27 @@
BorderThickness="{Binding IsEngMode, Converter={StaticResource BdThicknessConverter}}"
CornerRadius="4">
<Grid x:Name="MainPage">
<Grid.Style>
<Style>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsLogin}" Value="False"/>
<Condition Binding="{Binding IsReadOnlyMode}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Grid.Visibility" Value="Visible" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsLogin}" Value="False"/>
<Condition Binding="{Binding IsReadOnlyMode}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Grid.Visibility" Value="Hidden" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="120" />
<RowDefinition />
@ -1166,15 +1186,6 @@
</Menu>
</Grid>
</Border>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsLogin}" Value="False">
<Setter Property="Grid.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</Border>
</Grid>

View File

@ -25,9 +25,11 @@ using System.Windows.Input;
using System.Windows.Threading;
using Sicentury.Core.Collections;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using Caliburn.Micro.Core;
using MECF.Framework.Common.MECF.Framework.Common.Account.Extends;
using MECF.Framework.UI.Client.ClientBase.Dialog;
using SciChart.Core.Extensions;
namespace SicUI.Client
{
@ -339,7 +341,6 @@ namespace SicUI.Client
private readonly IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
BaseApp.Instance.Initialize();
((ClientApp)BaseApp.Instance).ViewModelSwitcher = this;
_models = new Dictionary<Type, BaseModel>();
_eventAggregator = eventAggregator;
@ -408,37 +409,6 @@ namespace SicUI.Client
}
break;
case EventType.LoginBySameUser_Notify:
if (Guid.TryParse(evt.Description, out var token) && evt.Tag is Credential requestingCred)
{
if (token == BaseApp.Instance.UserContext.Token)
{
Execute.OnUIThread(()=>
{
var ret = DialogBox.ShowDialog(
DialogButton.Yes | DialogButton.No,
DialogType.INFO,
"Some users is requesting to login the system, you will be forced to switch to read-only mode, do you agree?");
if (ret == DialogButton.Yes)
{
AccountClient.Instance.Service.ConfirmLoginRequest(requestingCred);
Logoff();
// 降级为仅查看模式
BaseApp.Instance.UserContext.Credential = Credential.ReadOnlyOne;
IsReadOnlyMode = true;
}
else
{
AccountClient.Instance.Service.RejectLoginRequest(requestingCred);
}
});
}
}
break;
case EventType.Sound_Notify:
break;
case EventType.UIMessage_Notify:
@ -464,79 +434,57 @@ namespace SicUI.Client
RequestLogin(loginName, password, role);
}
private bool _isShowLoginWaiter;
public bool IsShowLoginWaiter
{
get=>_isShowLoginWaiter;
set
{
_isShowLoginWaiter = value;
NotifyOfPropertyChange();
}
}
private string _requestLoginIndicatorText;
public string RequestLoginIndicatorText
{
get => _requestLoginIndicatorText;
set
{
_requestLoginIndicatorText = value;
NotifyOfPropertyChange();
}
}
private Task<LoginResult> _loginTask;
private CancellationTokenSource _ctsWaitingTask;
private string _lastLoginUserName = "";
private LoginRequestWaitDialog _loginWaitDialog = null;
public void CancelRequestLogin()
/// <summary>
/// 显示登录确认等待框。
/// </summary>
/// <param name="cts"></param>
/// <returns></returns>
private Task ShowRequestLoginWaiter(CancellationTokenSource cts)
{
if(string.IsNullOrEmpty(_lastLoginUserName) == false)
AccountClient.Instance.Service.CancelLoginRequest(_lastLoginUserName);
}
var ct = cts.Token;
public Task ShowRequestLoginIndicator()
{
return Task.Run(() =>
{
try
{
_ctsWaitingTask = new CancellationTokenSource();
var token = _ctsWaitingTask.Token;
var endTime = DateTime.Now.AddSeconds(30);
/*bool? waitResult = null;*/
// 如果超过一定时间没有从RT返回确认信息则显示登录中的提示
// 如果超过一定时间没有从RT返回确认信息则显示登录中的提示
Thread.Sleep(500);
if (ct.IsCancellationRequested)
return;
Execute.OnUIThread(() =>
{
_loginWaitDialog = new LoginRequestWaitDialog();
_loginWaitDialog.ShowDialog();
});
/*while (true)
{
Thread.Sleep(500);
if (token.IsCancellationRequested)
return;
// 等待对话框关闭不关心返回的DialogResult
// 关闭原因:
// 1. 点击Cancel按钮
// 2. 超时
// 3. 远端接受或拒绝请求
if (waitResult.HasValue)
break;
Execute.OnUIThread(() => IsShowLoginWaiter = true);
while (true)
{
var remained = (endTime - DateTime.Now).TotalSeconds;
if (remained <= 0)
break;
Execute.OnUIThread(() =>
RequestLoginIndicatorText =
$"Waiting for RT to confirm login request, {remained:0.0}s remained");
Thread.Sleep(100);
if (token.IsCancellationRequested)
break;
}
if (ct.IsCancellationRequested)
break;
}
finally
// 强制关闭等待对话框
Execute.OnUIThread(() =>
{
Execute.OnUIThread(() =>
{
RequestLoginIndicatorText = "Waiting for RT to confirm login request, 0s remained";
IsShowLoginWaiter = false;
});
}
});
loginWaitDialog?.Close();
});*/
}, ct);
}
public async void RequestLogin(string loginName, PasswordBox password, Role role)
@ -544,30 +492,45 @@ namespace SicUI.Client
if (_loginTask is { Status: TaskStatus.Running })
return;
_lastLoginUserName = loginName;
#region Validate Parameters
var myInfo = new LoginClientInfo();
myInfo.HostName = Dns.GetHostName();
myInfo.HostIP = (await Dns.GetHostEntryAsync(myInfo.HostName)).AddressList[0];
if (string.IsNullOrEmpty(loginName))
{
DialogBox.ShowError("User Name can not be empty.");
return;
}
if (string.IsNullOrEmpty(password.Password))
{
DialogBox.ShowError("Password can not be empty.");
return;
}
if (role == null || string.IsNullOrEmpty(role.RoleId))
{
DialogBox.ShowError("Role can not be empty.");
return;
}
#endregion
// 向RT请求登录
try
{
var taskWaiting = ShowRequestLoginIndicator();
_loginTask = AccountClient.Instance.Service.LoginEx(loginName, password.Password, role.RoleId, myInfo);
var ctsWaitingTask = new CancellationTokenSource();
var taskWaiting = ShowRequestLoginWaiter(ctsWaitingTask);
_loginTask = AccountClient.Instance.Service.LoginEx(loginName, password.Password, role?.RoleId??"",
BaseApp.Instance.ClientInfo);
// 等待确认超时,或已被接受/拒绝登录
await Task.WhenAny(_loginTask, taskWaiting);
_ctsWaitingTask.Cancel();
if (taskWaiting.Status == TaskStatus.RanToCompletion)
{
// 等待超时
DialogBox.ShowError("Login failed, timeout of waiting login request confirmation.");
AccountClient.Instance.Service.CancelLoginRequest(loginName);
}
else if (_loginTask.Status == TaskStatus.RanToCompletion)
if (_loginTask.Status == TaskStatus.RanToCompletion)
{
// 被接受或拒绝登录
ctsWaitingTask.Cancel();
_loginWaitDialog?.Close();
switch (_loginTask.Result.Result)
{
case LoginRequestResults.Error:
@ -603,7 +566,7 @@ namespace SicUI.Client
//Load menu by role
//filer menu if necessary...
BaseApp.Instance.MenuManager.LoadMenu(
RoleAccountProvider.Instance.GetMenusByRole(role.RoleId));
RoleAccountProvider.Instance.GetAccessibleMenusByRole(role.RoleId));
IsAutoLogout = role.IsAutoLogout;
LogoutTime = role.LogoutTime;
@ -625,6 +588,17 @@ namespace SicUI.Client
}
}
else if (taskWaiting.Status == TaskStatus.RanToCompletion)
{
// 等待确认超时
// 注意该请求在RT的存活时间比UI超时时间长因此需要显式取消请求否则下次登录请求需要等CredentialManager超时后才能重新发起
AccountClient.Instance.Service.CancelLoginRequest(loginName);
}
else
{
// 两个任务均失败显式调用下列方法强制RT清理凭据。
AccountClient.Instance.Service.CancelLoginRequest(loginName);
}
}
catch (InvalidOperationException ex)
{
@ -681,10 +655,10 @@ namespace SicUI.Client
else
{
Logoff();
IsReadOnlyMode = false;
}
}
public void Logoff()
{
BaseApp.Instance.UserMode = UserMode.Logoff;
@ -693,10 +667,9 @@ namespace SicUI.Client
try
{
AccountClient.Instance.Service.LogoutEx(BaseApp.Instance.UserContext.Token);
BaseApp.Instance.UserContext.Clear();
LOG.Info(
$"{BaseApp.Instance.UserContext.LoginName} logoff as {BaseApp.Instance.UserContext.Role.RoleName}");
BaseApp.Instance.UserContext.Clear();
}
catch (Exception exp)
{
@ -944,13 +917,39 @@ namespace SicUI.Client
else
{
// keep credential alive
var ret = AccountClient.Instance.Service.KeepAlive(BaseApp.Instance.UserContext.Token);
if (ret == CredentialKeepAliveResults.NotFound)
var retKeepAlive = AccountClient.Instance.Service.KeepAlive(BaseApp.Instance.UserContext.Token);
if (retKeepAlive.State == CredentialKeepAliveStates.NotFound)
{
Logoff();
Execute.OnUIThread(() => { DialogBox.ShowError("You are kicked by RT."); });
}
else if (retKeepAlive.State == CredentialKeepAliveStates.RequestingLogin)
{
// 当前用户在其它地方请求登录
Execute.OnUIThread(() =>
{
var dlgConfirm = new LoginRequestConfirmationDialog(retKeepAlive.RequestingCredential);
var retDlg = dlgConfirm.ShowDialog();
if (retDlg == true)
{
AccountClient.Instance.Service.ConfirmLoginRequest(retKeepAlive.RequestingCredential.AccountInfo.LoginName);
Logoff();
IsReadOnlyMode = true;
// 降级为仅查看模式
BaseApp.Instance.UserContext.Role = role;
BaseApp.Instance.UserContext.Credential = Credential.ReadOnlyOne;
}
else
{
AccountClient.Instance.Service.RejectLoginRequest(retKeepAlive.RequestingCredential.AccountInfo.LoginName);
}
});
// 等待RT处理凭据否则有可能反复触发请求登录确认窗口.
Thread.Sleep(1000);
}
}
}

View File

@ -15,6 +15,8 @@ using Aitex.Core.RT.Event;
using System.Dynamic;
using Caliburn.Micro.Core;
using MECF.Framework.Common.Account;
using MECF.Framework.Common.Account.Permissions;
using MECF.Framework.UI.Core.Accounts;
namespace SicUI.Models.Operations.Overviews
{

View File

@ -9,7 +9,6 @@ namespace SicUI.Models.PMs
{
public class PMAlarmViewModel : SicModuleUIViewModelBase, ISupportMultipleSystem
{
public bool IsPermission { get => this.Permission == 3; }
[IgnorePropertyChange]
public List<AlarmInfoUi> AlarmEvents { get; set; }

View File

@ -53,8 +53,8 @@ namespace SicUI.Models.PMs
public Visibility TipsVisble => IsConfinementRingUp && !IsOnline ? Visibility.Hidden : Visibility.Visible;
public bool IsPermission { get => this.Permission == 3; }
public bool IsActionEnable => IsConfinementRingUp && !IsOnline;
public bool IsActionEnable1 => false;
[Subscription("ConfinementRing.RingUpFaceback")]
@ -296,8 +296,6 @@ namespace SicUI.Models.PMs
#endregion
#region TC2
[Subscription("TC2.L1WorkingOPFeedBack")]
public float L1WorkingOP2 { get; set; }
@ -493,10 +491,6 @@ namespace SicUI.Models.PMs
#endregion
#endregion
[Subscription("TempOmron.ActualTemp")]

View File

@ -60,7 +60,6 @@ namespace SicUI.Models.PMs
public Visibility TipsVisble => RingUpSensor ? Visibility.Hidden : Visibility.Visible;
public bool IsPermission { get => this.Permission == 3; }
public bool IsActionEnable => RingUpSensor && !IsOnline;
public bool IsActionEnable1 => false;

View File

@ -1006,8 +1006,6 @@ namespace SicUI.Models.PMs
}
}
public bool IsPermission { get => this.Permission == 3; }
public string Module => SystemName;
@ -1548,7 +1546,7 @@ namespace SicUI.Models.PMs
base.InitPM();
//权限
string roleID = BaseApp.Instance.UserContext.Role.RoleId;
var roleID = BaseApp.Instance.UserContext.Role.RoleId;
reactorStatusEnable = RoleAccountProvider.Instance.GetMenuPermission(roleID, "PM1.Main.ReactorStatus") == 3;
reactorServiceEnable = RoleAccountProvider.Instance.GetMenuPermission(roleID, "PM1.Main.ReactorService") == 3;

View File

@ -54,6 +54,6 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.1.18.47")]
[assembly: AssemblyVersion("2023.09.18.01")]
[assembly: AssemblyInformationalVersion("手动通用版无EFEM")]