using Aitex.Core.RT.Event; using Aitex.Core.RT.Log; using MECF.Framework.Common.DataCenter; using MECF.Framework.Common.Utilities; using MECF.Framework.UI.Client.ClientBase; using OpenSEMI.ClientBase.Command; using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media.Imaging; using Sicentury.Core.Collections; namespace MECF.Framework.UI.Client.CenterViews.DataLogs.Event { public class EventViewModel : BaseModel { #region Variables /// /// 首次查询。需要计算总页码等参数。 /// private const int QUERY_FIRST_TIME = -1; /// /// 一次性查询所有数据,EXCEL导出使用。 /// private const int QUERY_FOR_EXCEL_EXPORT = 0; /// /// 指示函数是否发生了重入。如果发生重入,说明“ALL”由 /// 后台程序勾选,此时不要进行任何逻辑操作,仅设置”ALL“的选择状态。 /// 重入的情况发生在: /// 1. 所有选项全部被选中 /// 2. 此时将“ALL”以外的选项勾掉 /// 3. “ALL”自动取消选择 /// 上述2会触发3,引起函数重入。 /// private bool _isEventSourceFilterSelectionReentered = false; private int _currentPage; private int _totalPage; private int _selectedPage; private int _selectedPaginationCapacity; private bool _isLoading; private bool _isExporting; private string _exportingMessage; /// /// 显示查询到的事件日志列表。 /// private readonly IProgress> _progShowSearchingResult; /// /// 构造Event Source列表。 /// private readonly IProgress> _progConstructEventSourcesList; /// /// 更新分页信息。 /// Tuple.Item1: 当前页号 /// Tuple.Item2: 总页号;设置为-1时表示忽略更新总页号。 /// private readonly IProgress> _progUpdatePaginationInfo; /// /// 更新查询状态。 /// private readonly IProgress _progUpdateQueryStatus; /// /// 更新日志导出状态。 /// Tuple.Item1: 导出进度,0~100 /// Tuple.Item2: 消息 /// private readonly IProgress> _progUpdateExportingState; #endregion #region Constructors public EventViewModel() { DisplayName = "Event"; QueryEventList = () => { var result = new List(); foreach (var eventName in Enum.GetNames(typeof(EventEnum))) result.Add(eventName); return result; }; SearchedResult = new ObservableRangeCollection(); FilterEventSources = new ObservableRangeCollection(); SelectedFilterEventSource = new ObservableRangeCollection(); PaginationSource = new ObservableRangeCollection(); SearchKeyWords = string.Empty; SearchAlarmEvent = true; SearchWarningEvent = true; SearchInfoEvent = true; SearchOpeLog = false; SearchPMA = false; SearchPMB = false; SearchPMC = false; SearchPMD = false; SearchTM = false; SearchLL = false; SearchSystem = false; tbLoadPort1SelectionChangedCommand = new BaseCommand(tbLoadPort1Selection); // 分页支持的每页Log数量 //! 最小每页条目数使用30,以保证正好显示一页而不出现纵向滚动条。 PaginationCapacity = new List { 30, 300, 3000 }; _selectedPaginationCapacity = PaginationCapacity.Last(); _totalPage = 1; _currentPage = 1; NavigateCommand = new BaseCommand(Navigate, (o) => true); #region 显示查询到的EventLog列表 _progShowSearchingResult = new Progress>((queryRet => { if (SearchedResult == null) SearchedResult = new ObservableRangeCollection(); else SearchedResult.Clear(); SearchedResult.AddRange(queryRet.Select(x => new SystemLogItem() { Time = (x.OccuringTime).ToString("yyyy/MM/dd HH:mm:ss.fff"), LogType = x.Level.ToString(), Detail = x.Description, TargetChamber = x.Source, Initiator = "", Icon = new BitmapImage(new Uri( $"pack://application:,,,/MECF.Framework.UI.Core;component/Resources/SystemLog/{x.Level}.png", UriKind.Absolute)) } )); })); #endregion #region 构造Event Sources列表 _progConstructEventSourcesList = new Progress>((list => { SelectedFilterEventSource.Clear(); FilterEventSources.Clear(); SelectedFilterEventSource.Add("ALL"); FilterEventSources.Add("ALL"); SelectedFilterEventSource.AddRange(list); FilterEventSources.AddRange(list); })); #endregion #region 更新分页信息 _progUpdatePaginationInfo = new Progress>(pageInfo => { _currentPage = pageInfo.Item1; if (pageInfo.Item2 > 0) { _totalPage = pageInfo.Item2; PaginationSource.Clear(); PaginationSource.AddRange(Enumerable.Range(1, _totalPage)); NotifyOfPropertyChange(nameof(PaginationSource)); } NotifyOfPropertyChange(nameof(PageInfo)); _selectedPage = _currentPage; NotifyOfPropertyChange(nameof(SelectedPage)); }); #endregion #region 更新查询状态 _progUpdateQueryStatus = new Progress(isBusy => { IsLoading = isBusy; }); #endregion #region 更新日志导出进度信息 _progUpdateExportingState = new Progress>(state => { var progress = state.Item1; var message = state.Item2; switch (progress) { case 0: IsExporting = true; break; case 100: IsExporting = false; break; } ExportingMessage = message; }); #endregion } #endregion #region Properties public bool SearchAlarmEvent { get; set; } public bool SearchWarningEvent { get; set; } public bool SearchInfoEvent { get; set; } public bool SearchOpeLog { get; set; } public bool SearchPMA { get; set; } public bool SearchPMB { get; set; } public bool SearchPMC { get; set; } public bool SearchPMD { get; set; } //public bool SearchCoolDown { get; set; } public bool SearchTM { get; set; } public bool SearchLL { get; set; } //public bool SearchBuf1 { get; set; } public bool SearchSystem { get; set; } public string SearchKeyWords { get; set; } public string FilterKeyWords { get; set; } public DateTime SearchBeginTime { get => ((EventView)view).wfTimeFrom.Value; set { ((EventView)view).wfTimeFrom.Value = value; NotifyOfPropertyChange(nameof(SearchBeginTime)); } } public DateTime SearchEndTime { get => ((EventView)view).wfTimeTo.Value; set { ((EventView)view).wfTimeTo.Value = value; NotifyOfPropertyChange(nameof(SearchEndTime)); } } public List EventList { get; set; } public string SelectedEvent { get; set; } public ObservableRangeCollection SearchedResult { get; private set; } public Func> QueryEventList { get; set; } public bool IsPermission => Permission == 3; private EventView view; public ObservableRangeCollection FilterEventSources { get; set; } public ObservableRangeCollection SelectedFilterEventSource { get; set; } public ICommand tbLoadPort1SelectionChangedCommand { get; set; } //public ICommand tbLoadPort2SelectionChangedCommand { get; set; } public ICommand NavigateCommand { get; set; } /// /// 返回受支持的分页每页容量。 /// public List PaginationCapacity { get; } /// /// 页码列表,用于快速跳转页面下拉框数据源。 /// public ObservableRangeCollection PaginationSource { get; } public int SelectedPage { get => _selectedPage; set { _selectedPage = value; Query(_selectedPage); } } /// /// 返回选择的分页每页容量。 /// public int SelectedPaginationCapacity { get => _selectedPaginationCapacity; set { _selectedPaginationCapacity = value; Query(); } } /// /// 页码信息。 /// public string PageInfo => $"{_currentPage}/{_totalPage}"; /// /// 返回是否正在加载数据。 /// public bool IsLoading { get => _isLoading; set { _isLoading = value; NotifyOfPropertyChange(nameof(IsLoading)); } } /// /// 是否正在导出数据。 /// public bool IsExporting { get => _isExporting; private set { _isExporting = value; NotifyOfPropertyChange(nameof(IsExporting)); } } /// /// 导出进度消息。 /// public string ExportingMessage { get => _exportingMessage; private set { _exportingMessage = value; NotifyOfPropertyChange(nameof(ExportingMessage)); } } #endregion #region Methods public void Navigate(string args) { switch (args) { case "first": if (_currentPage == 1) return; _currentPage = 1; break; case "previous": if(_currentPage > 1) _currentPage--; break; case "next": if(_currentPage < _totalPage) _currentPage++; break; case "last": if (_currentPage == _totalPage) return; _currentPage = _totalPage; break; default: return; } Query(_currentPage); } protected override void OnViewLoaded(object view) { base.OnViewLoaded(view); // 默认的查询时间设置为当天 this.view = (EventView)view; SearchBeginTime = DateTime.Today; SearchEndTime = DateTime.Today.AddDays(1).AddTicks(-1); Preload(); } public void Preload() { EventList = new List { "All" }; if (QueryEventList != null) { var evList = QueryEventList(); foreach (var ev in evList) EventList.Add(ev); } SelectedEvent = "All"; Query(); } /// /// 打开YALV日志管理器。 /// public void OpenDetailedLogViewer() { try { // 检查YALV是否已经打开 var yalv = Process.GetProcesses().FirstOrDefault(p => p.ProcessName.ToLower().Contains("yalv")); if (yalv != null) throw new InvalidOperationException("日志管理器已经打开。"); const string PATH_YALV = @"yalv\yalv.exe"; var yalvPath = Path.Combine(Environment.CurrentDirectory, PATH_YALV); // check the existence of the YALV if (File.Exists(yalvPath) == false) { throw new FileNotFoundException("日志管理器可执行文件不存在。"); } var logFile = Path.Combine(Environment.CurrentDirectory, $"logs\\log{DateTime.Now:yyyyMMdd}.xlog"); var proc = new Process() { StartInfo = new ProcessStartInfo { FileName = yalvPath, Arguments = $"\"{logFile}\"" } }; proc.Start(); } catch (Exception ex) { LOG.Write(ex); MessageBox.Show($"打开日志管理器失败,{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// Event Source中全选和全不选逻辑实现。 /// /// /// public void tbLoadPort1Selection(object o) { if (_isEventSourceFilterSelectionReentered) return; _isEventSourceFilterSelectionReentered = true; if (o is ItemSelectionData item) { if (item.SelectItem == "ALL") { //if (isAllSelectedInBackend) // return; if (item.IsSelect) { SelectedFilterEventSource.Clear(); SelectedFilterEventSource.AddRange(FilterEventSources); //FilterEventSources.ToList().ForEach(sp => //{ // if (!SelectedFilterEventSource.Contains(sp)) // SelectedFilterEventSource.Add(sp); //}); } else { SelectedFilterEventSource.Clear(); } } else { // 判断是否需要取消或选中“ALL” var list = FilterEventSources .Except(new[] {"ALL"}) .Except(SelectedFilterEventSource.Except(new [] {"ALL"})) .ToList(); //isAllSelectedInBackend = true; if (list.Any()) { if (SelectedFilterEventSource.Contains("ALL")) SelectedFilterEventSource.Remove("ALL"); } else { SelectedFilterEventSource.Clear(); SelectedFilterEventSource.AddRange(FilterEventSources); //FilterEventSources.ToList().ForEach(sp => //{ // if (!SelectedFilterEventSource.Contains(sp)) // SelectedFilterEventSource.Add(sp); //}); } //isAllSelectedInBackend = false; } } _isEventSourceFilterSelectionReentered = false; } private string GetSourceWhere() { return ""; } #region 数据库查询相关函数 #region SQL 语句构造函数 /// /// 获取EventLog查询语句中select部分表达式。 /// private string GetEventLogQuerySql() { return "SELECT \"event_id\", \"event_enum\", \"type\", \"occur_time\", \"level\",\"source\" , \"description\" FROM \"event_data\"" + GetWhereTimeRangeExpression(); } /// /// 获取EventLog行数查询语句中select部分表达式。 /// private string GetEventLogCountSql() { return "SELECT COUNT(1) FROM \"event_data\"" + GetWhereTimeRangeExpression(); ; } /// /// 获取EventLog行数查询语句中select部分表达式。 /// private string GetChamberListSql() { return "SELECT DISTINCT \"source\" FROM \"event_data\"" + GetWhereTimeRangeExpression(); } /// /// 获取WHERE条件中的“时间范围”子句。 /// 注意:所有Select语句构造时必须包含TimeRange条件。 /// /// private string GetWhereTimeRangeExpression() { var sql = new StringBuilder( $" WHERE \"occur_time\" BETWEEN '{SearchBeginTime:yyyy/MM/dd HH:mm:ss}' AND '{SearchEndTime:yyyy/MM/dd HH:mm:ss}'"); return sql.ToString(); } /// /// 获取WHERE条件中的“Log Level”子句。 /// /// private string GetWhereLevelExpression() { var sqlLevelConditions = new List(); if (SearchInfoEvent) sqlLevelConditions.Add("'Information'"); if (SearchWarningEvent) sqlLevelConditions.Add("'Warning'"); if (SearchAlarmEvent) sqlLevelConditions.Add("'Alarm'"); return $" AND (\"level\" in ({string.Join(",", sqlLevelConditions)}))"; } /// /// 获取WHERE条件中的“关键字”子句。 /// /// private string GetWhereQueryKeyWordsExpression() { return string.IsNullOrEmpty(SearchKeyWords) ? "" : $" AND (lower(\"description\") like lower('%{SearchKeyWords}%'))"; } /// /// 获取WHERE条件中的“事件类型过滤条件“子句。 /// /// private string GetWhereFilterEventSourceExpression() { if (SelectedFilterEventSource == null || SelectedFilterEventSource.Count <= 0) throw new ArgumentOutOfRangeException(nameof(SelectedFilterEventSource), "no event source(s) in filter are selected."); if (SelectedFilterEventSource.Contains("ALL")) return ""; return $" AND (\"source\" in ({string.Join(",", SelectedFilterEventSource.Select(x => $"'{x}'"))}))"; } /// /// 获取WHERE条件中的“事件描述关键字过滤条件“子句。 /// /// private string GetWhereFilterEventKeywordExpression() { return string.IsNullOrEmpty(FilterKeyWords) ? "" : $" AND (lower(\"description\") like lower('%{FilterKeyWords}%'))"; } /// /// 获取SQL查询语句中的分页表达式。 /// /// /// /// /// private string GetPaginationExpression(int countPerPage, int currentPage) { if (countPerPage <= 0) throw new ArgumentOutOfRangeException(nameof(countPerPage), "the amount items per page can not be less than 1."); if (currentPage <= 0) throw new ArgumentOutOfRangeException(nameof(currentPage), "the current page required can not be less than 1."); return $" LIMIT {countPerPage} OFFSET {countPerPage * (currentPage - 1)}"; } #endregion /// /// 查询当前条件下共有多少行数据。 /// 是否包含过滤条件。 /// private int QueryRowAmount(bool isIncludeFilter) { var sql = GetEventLogCountSql() + GetWhereLevelExpression() + GetWhereQueryKeyWordsExpression(); if (isIncludeFilter) sql += GetWhereFilterEventSourceExpression() + GetWhereFilterEventKeywordExpression(); var dt = QueryDataClient.Instance.Service.QueryData(sql); // 未查询到任何数据。 if (dt == null || dt.Rows.Count <= 0 || dt.Columns.Count <= 0) return 0; // 返回满足当前条件的行数。 return int.Parse(dt.Rows[0][0].ToString()); } /// /// 查询Event源列表。 /// 注意:该查询仅在首次查询时使用;语句的条件仅只用时间范围和事件类型,不包含Filter选项。 /// 当套用Filter选项查询、或分页查询时,不再重构Event源列表。 /// /// private List QueryEventSources() { var chamberList = new List(); var sql = GetChamberListSql() + GetWhereLevelExpression(); var dt = QueryDataClient.Instance.Service.QueryData(sql); // 未查询到任何数据。 if (dt == null || dt.Rows.Count <= 0 || dt.Columns.Count <= 0) return chamberList; // 返回满足当前条件的行数。 chamberList.AddRange(from DataRow row in dt.Rows select row[0].ToString()); return chamberList; } /// /// 查询符合条件的事件日志。 /// /// 待查询页码。0:查询所有数据而不分页;其它:查询指定的页码。 /// 是否包含过滤条件。 /// 页码错误,页码必须为大于等于0的整数。 /// private List QueryEventLogs(int page, bool isIncludeFilter) { if (page < 0) throw new ArgumentOutOfRangeException(nameof(page), "the page number must be greater than 0."); var sql = GetEventLogQuerySql() + GetWhereLevelExpression() + GetWhereQueryKeyWordsExpression(); if (isIncludeFilter) sql += GetWhereFilterEventSourceExpression() + GetWhereFilterEventKeywordExpression(); // page == 0 表示查询所有数据,仅导出Excel时使用。 if(page > 0) sql += GetPaginationExpression(SelectedPaginationCapacity, page); return QueryDataClient.Instance.Service.QueryDBEvent(sql); } /// /// 查询数据。 /// /// 待查询的页号 /// 是否包含过滤条件。 /// public void Query(int page = QUERY_FIRST_TIME, bool isIncludeFilter = false) { if (IsLoading) return; if (SearchBeginTime > SearchEndTime) { MessageBox.Show("Time range invalid, start time should be early than end time"); return; } Task.Run(() => { var searchingResult = new List(); // 查询开始 _progUpdateQueryStatus.Report(true); if (page <= QUERY_FIRST_TIME) // Query按钮按下,或首次加载界面 { // 统计总页数 var rowCount = QueryRowAmount(isIncludeFilter); var totalPage = (int)Math.Ceiling(rowCount / (double)SelectedPaginationCapacity); // 首次查询第一页 searchingResult = QueryEventLogs(1, isIncludeFilter); if (isIncludeFilter == false) { // 构造Event Sources列表,仅在首次查询时构造 var eventSources = QueryEventSources(); _progConstructEventSourcesList.Report(eventSources); } // 更新页面信息 _progUpdatePaginationInfo.Report(new Tuple(1, totalPage)); } else if (page == QUERY_FOR_EXCEL_EXPORT) { } else if (page >= 1) { // 翻页按钮按下 searchingResult = QueryEventLogs(page, isIncludeFilter); // 更新页面信息 _progUpdatePaginationInfo.Report(new Tuple(page, -1)); } else { throw new ArgumentOutOfRangeException(nameof(page), "invalid page number."); } // 显示结果 _progShowSearchingResult.Report(searchingResult); // 查询完毕 _progUpdateQueryStatus.Report(false); }); } /// /// 导出 /// public async void Export() { try { var dlg = new Microsoft.Win32.SaveFileDialog(); dlg.DefaultExt = ".xlsx"; // Default file extension dlg.FileName = $"Operation Log_{DateTime.Now:yyyyMMdd_HHmmss}"; dlg.Filter = "Excel数据表格文件(*.xlsx)|*.xlsx"; // Filter files by extension var result = dlg.ShowDialog(); // Show open file dialog box if (result != true) return; await Task.Run(() => { _progUpdateExportingState.Report(new Tuple(0, "Querying data ...")); // 查询数据 var queryRet = QueryEventLogs(0, false); if (queryRet == null || queryRet.Count <= 0) throw new Exception("no event logs are found, please change the conditions and retry."); var ds = new DataSet(); ds.Tables.Add(new System.Data.DataTable("系统运行日志")); ds.Tables[0].Columns.Add("Type"); ds.Tables[0].Columns.Add("Time"); ds.Tables[0].Columns.Add("System"); ds.Tables[0].Columns.Add("Content"); var currRow = 0; var totalRow = queryRet.Count; foreach (var item in queryRet) { currRow++; _progUpdateExportingState.Report(new Tuple(50, $"Exporting item {currRow}/{totalRow} ...")); var row = ds.Tables[0].NewRow(); row[0] = item.Level; row[1] = item.OccuringTime.ToString("yyyy/MM/dd HH:mm:ss.fff"); row[2] = item.Source; row[3] = item.Description; ds.Tables[0].Rows.Add(row); } _progUpdateExportingState.Report(new Tuple(60, "Writing excel file ...")); if (!ExcelHelper.ExportToExcel(dlg.FileName, ds, out var reason)) throw new InvalidOperationException($"Export failed, {reason}"); _progUpdateExportingState.Report(new Tuple(100, "Done!")); }); MessageBox.Show($"Export succeed, file save as {dlg.FileName}", "Export", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { LOG.Write(ex); MessageBox.Show("导出系统日志发生错误", "导出失败", MessageBoxButton.OK, MessageBoxImage.Warning); } finally { _progUpdateExportingState.Report(new Tuple(100, "Done!")); } } #endregion #endregion } }