/* Copyright (c) Cloud Software Group, Inc. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Windows.Forms; using XenAdmin.Actions; using XenAdmin.Controls; using XenAdmin.Controls.MainWindowControls; using XenAdmin.Core; using XenAdmin.Dialogs; using XenAPI; namespace XenAdmin.TabPages { public partial class HistoryPage : NotificationsBasePage { private const int MAX_HISTORY_ITEM = 1000; internal event Action GoToXenObjectRequested; public HistoryPage() { InitializeComponent(); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); toolStripDdbFilterStatus.ImplementsQueued = false; toolStripDdbFilterStatus.ImplementsIncomplete = false; dataGridView.Sort(columnDate, ListSortDirection.Descending); toolStripTop.Renderer = new CustomToolStripRenderer(); toolStripSplitButtonDismiss.DefaultItem = tsmiDismissAll; toolStripSplitButtonDismiss.Text = tsmiDismissAll.Text; UpdateButtons(); ActionBase.NewAction += Action_NewAction; } #region NotificationPage overrides protected override void RefreshPage() { toolStripDdbFilterLocation.InitializeHostList(); BuildRowList(); } protected override void RegisterEventHandlers() { ConnectionsManager.History.CollectionChanged += History_CollectionChanged; } protected override void DeregisterEventHandlers() { ConnectionsManager.History.CollectionChanged -= History_CollectionChanged; foreach (var action in ConnectionsManager.History) DeregisterActionEvents(action); } public override string HelpID => "EventsPane"; public override NotificationsSubMode NotificationsSubMode => NotificationsSubMode.Events; #endregion private void Action_NewAction(ActionBase action) { if (action == null) return; Program.Invoke(Program.MainWindow, () => { int count = ConnectionsManager.History.Count; if (count >= MAX_HISTORY_ITEM) ConnectionsManager.History.RemoveAt(0); ConnectionsManager.History.Add(action); }); } private void History_CollectionChanged(object sender, CollectionChangeEventArgs e) { Program.BeginInvoke(this, () => { ActionBase action = e.Element as ActionBase; switch (e.Action) { case CollectionChangeAction.Add: var actions = new List(ConnectionsManager.History); SortActions(actions); var index = actions.IndexOf(action); if (index > -1) InsertActionRow(index, action); break; case CollectionChangeAction.Remove: if (action != null) { RemoveActionRow(action); } else { var range = e.Element as List; if (range != null) BuildRowList(); } break; case CollectionChangeAction.Refresh: BuildRowList(); break; } UpdateButtons(); }); } private void action_Changed(ActionBase sender) { if (sender is AsyncAction asyncAction) asyncAction.RecomputeCanCancel(); Program.Invoke(Program.MainWindow, () => { var row = FindRowFromAction(sender); if (row != null) { //the row is already in the grid, refresh and show or hide it if (!FilterAction(sender)) { row.RefreshSelf(); row.Visible = true; } else { row.Visible = false; } } else if (!FilterAction(sender)) { //adding the row to the grid, because it has not been there and now it should be visible based on active filters CreateActionRow(sender); } UpdateButtons(); }); } public override bool FilterIsOn => toolStripDdbFilterDates.FilterIsOn || toolStripDdbFilterLocation.FilterIsOn || toolStripDdbFilterStatus.FilterIsOn; private void BuildRowList() { if (!Visible) return; try { dataGridView.SuspendLayout(); dataGridView.Rows.Clear(); //creating a sorted list of actions that should currently be visible var actions = ConnectionsManager.History.Where(a => !FilterAction(a)).ToList(); SortActions(actions); //adding a row to the grid for each action var rows = actions.Select(a => CreateActionRow(a)).ToList(); dataGridView.Rows.AddRange(rows.ToArray()); //registering to action events of all the actions foreach (var action in ConnectionsManager.History) RegisterActionEvents(action); OnFiltersChanged(); } finally { dataGridView.ResumeLayout(); UpdateButtons(); } } private void SortActions(List actions) { if (dataGridView.SortedColumn != null) { if (dataGridView.SortedColumn.Index == columnStatus.Index) actions.Sort(ActionBaseExtensions.CompareOnStatus); else if (dataGridView.SortedColumn.Index == columnMessage.Index) actions.Sort(ActionBaseExtensions.CompareOnTitle); else if (dataGridView.SortedColumn.Index == columnLocation.Index) actions.Sort(ActionBaseExtensions.CompareOnLocation); else if (dataGridView.SortedColumn.Index == columnDate.Index) actions.Sort(ActionBaseExtensions.CompareOnDateStarted); if (dataGridView.SortOrder == SortOrder.Descending) actions.Reverse(); } } /// /// Returns true when the action needs to be filtered out /// /// /// private bool FilterAction(ActionBase action) { bool hide = false; Program.Invoke(Program.MainWindow, () => hide = toolStripDdbFilterDates.HideByDate(action.Started) || toolStripDdbFilterLocation.HideByLocation(action.GetApplicableHosts()) || toolStripDdbFilterStatus.HideByStatus(action)); return hide; } private void InsertActionRow(int index, ActionBase action) { if (index < 0) index = 0; if (index > dataGridView.RowCount) index = dataGridView.RowCount; var row = CreateActionRow(action); dataGridView.Rows.Insert(index, row); RegisterActionEvents(action); } private DataGridViewActionRow CreateActionRow(ActionBase action) { var row = new DataGridViewActionRow(action); row.Visible = !FilterAction(action); row.DismissalRequested += row_DismissalRequested; row.GoToXenObjectRequested += row_GoToXenObjectRequested; return row; } private void RegisterActionEvents(ActionBase action) { DeregisterActionEvents(action); action.Changed += action_Changed; action.Completed += action_Changed; } private void DeregisterActionEvents(ActionBase action) { action.Changed -= action_Changed; action.Completed -= action_Changed; } private void RemoveActionRow(ActionBase action) { DeregisterActionEvents(action); var row = FindRowFromAction(action); if (row != null) { dataGridView.Rows.Remove(row); } } private DataGridViewActionRow FindRowFromAction(ActionBase action) { foreach (DataGridViewRow row in dataGridView.Rows) { var actionRow = row as DataGridViewActionRow; if (actionRow == null) continue; if (actionRow.Action == action) return actionRow; } return null; } private void ToggleExpandedState(int rowIndex) { var row = dataGridView.Rows[rowIndex] as DataGridViewActionRow; if (row == null) return; if (row.Expanded) { row.Cells[columnExpander.Index].Value = Images.StaticImages.contracted_triangle; row.Cells[columnMessage.Index].Value = row.Action.GetTitle(); } else { row.Cells[columnExpander.Index].Value = Images.StaticImages.expanded_triangle; row.Cells[columnMessage.Index].Value = row.Action.GetDetails(); } row.Expanded = !row.Expanded; } private void UpdateButtons() { tsmiDismissAll.Enabled = ConnectionsManager.History.Any(a => a.IsCompleted); tsmiDismissSelected.Enabled = dataGridView.SelectedRows.Cast().Any(row => row.Action.IsCompleted); toolStripSplitButtonDismiss.Enabled = tsmiDismissAll.Enabled || tsmiDismissSelected.Enabled; if (toolStripSplitButtonDismiss.DefaultItem != null && !toolStripSplitButtonDismiss.DefaultItem.Enabled) { foreach (ToolStripItem item in toolStripSplitButtonDismiss.DropDownItems) { if (item.Enabled) { toolStripSplitButtonDismiss.DefaultItem = item; toolStripSplitButtonDismiss.Text = item.Text; break; } } } } #region Control event handlers private void row_DismissalRequested(DataGridViewActionRow row) { if (ConnectionsManager.History.Count > 0) { if (!Program.RunInAutomatedTestMode && !Properties.Settings.Default.DoNotConfirmDismissEvents) { using (var dlg = new NoIconDialog(Messages.MESSAGEBOX_LOG_DELETE, ThreeButtonDialog.ButtonYes, ThreeButtonDialog.ButtonNo) { ShowCheckbox = true, CheckboxCaption = Messages.DO_NOT_SHOW_THIS_MESSAGE }) { var result = dlg.ShowDialog(this); Properties.Settings.Default.DoNotConfirmDismissEvents = dlg.IsCheckBoxChecked; Settings.TrySaveSettings(); if (result != DialogResult.Yes) return; } } ConnectionsManager.History.Remove(row.Action); } } private void row_GoToXenObjectRequested(IXenObject obj) { if (GoToXenObjectRequested != null) GoToXenObjectRequested(obj); } private void dataGridView_CellClick(object sender, DataGridViewCellEventArgs e) { // If you click on the headers you can get -1 as the index. if (e.ColumnIndex < 0 || e.RowIndex < 0 || e.ColumnIndex != columnExpander.Index) return; ToggleExpandedState(e.RowIndex); } private void dataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex < 0 || e.RowIndex < 0) return; if (e.ColumnIndex != columnActions.Index) ToggleExpandedState(e.RowIndex); } private void dataGridView_SelectionChanged(object sender, EventArgs e) { tsmiDismissSelected.Enabled = dataGridView.SelectedRows.Cast().Any(row => row.Action.IsCompleted); } private void dataGridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) { if (dataGridView.Columns[e.ColumnIndex].SortMode == DataGridViewColumnSortMode.Automatic) BuildRowList(); } private void toolStripSplitButtonDismiss_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e) { toolStripSplitButtonDismiss.DefaultItem = e.ClickedItem; toolStripSplitButtonDismiss.Text = toolStripSplitButtonDismiss.DefaultItem.Text; } private void tsmiDismissAll_Click(object sender, EventArgs e) { if (ConnectionsManager.History.Count == 0) return; DialogResult result = DialogResult.Yes; if (!Program.RunInAutomatedTestMode) { if (FilterIsOn) { using (var dlog = new NoIconDialog(Messages.MESSAGEBOX_LOGS_DELETE, new ThreeButtonDialog.TBDButton(Messages.DISMISS_ALL_CONFIRM_BUTTON, DialogResult.Yes), new ThreeButtonDialog.TBDButton(Messages.DISMISS_FILTERED_CONFIRM_BUTTON, DialogResult.No, ThreeButtonDialog.ButtonType.NONE), ThreeButtonDialog.ButtonCancel)) { result = dlog.ShowDialog(this); } } else if (!Properties.Settings.Default.DoNotConfirmDismissEvents) { using (var dlog = new NoIconDialog(Messages.MESSAGEBOX_LOGS_DELETE_NO_FILTER, new ThreeButtonDialog.TBDButton(Messages.DISMISS_ALL_YES_CONFIRM_BUTTON, DialogResult.Yes), ThreeButtonDialog.ButtonCancel) { ShowCheckbox = true, CheckboxCaption = Messages.DO_NOT_SHOW_THIS_MESSAGE }) { result = dlog.ShowDialog(this); Properties.Settings.Default.DoNotConfirmDismissEvents = dlog.IsCheckBoxChecked; Settings.TrySaveSettings(); } } if (result == DialogResult.Cancel) return; } var actions = result == DialogResult.No ? (from DataGridViewActionRow row in dataGridView.Rows where row.Action != null && row.Action.IsCompleted && row.Visible select row.Action) : ConnectionsManager.History.Where(action => action != null && action.IsCompleted); ConnectionsManager.History.RemoveAll(actions.Contains); } private void tsmiDismissSelected_Click(object sender, EventArgs e) { if (!Properties.Settings.Default.DoNotConfirmDismissEvents) { using (var dlog = new NoIconDialog(Messages.MESSAGEBOX_LOGS_DELETE_SELECTED, ThreeButtonDialog.ButtonYes, ThreeButtonDialog.ButtonNo) { ShowCheckbox = true, CheckboxCaption = Messages.DO_NOT_SHOW_THIS_MESSAGE }) { var result = dlog.ShowDialog(this); Properties.Settings.Default.DoNotConfirmDismissEvents = dlog.IsCheckBoxChecked; Settings.TrySaveSettings(); if (result != DialogResult.Yes) return; } } var actions = from DataGridViewActionRow row in dataGridView.SelectedRows where row != null && row.Action != null && row.Action.IsCompleted && row.Visible select row.Action; ConnectionsManager.History.RemoveAll(actions.Contains); } private void toolStripDdbFilterStatus_FilterChanged() { BuildRowList(); } private void toolStripDdbFilterLocation_FilterChanged() { BuildRowList(); } private void toolStripDdbFilterDates_FilterChanged() { BuildRowList(); } #endregion #region Nested classes public class DataGridViewActionRow : DataGridViewRow { private DataGridViewImageCell expanderCell = new DataGridViewImageCell(); private DataGridViewImageCell statusCell = new DataGridViewImageCell(); private DataGridViewTextBoxCell messageCell = new DataGridViewTextBoxCell(); private DataGridViewTextBoxCell locationCell = new DataGridViewTextBoxCell(); private DataGridViewTextBoxCell dateCell = new DataGridViewTextBoxCell(); private DataGridViewDropDownSplitButtonCell actionCell = new DataGridViewDropDownSplitButtonCell(); private ToolStripMenuItem cancelItem = new ToolStripMenuItem(Messages.CANCEL); private ToolStripMenuItem dismissItem = new ToolStripMenuItem(Messages.ALERT_DISMISS); private ToolStripMenuItem goToItem = new ToolStripMenuItem(Messages.HISTORYPAGE_GOTO); private ToolStripSeparator separatorItem = new ToolStripSeparator(); private ToolStripMenuItem copyItem = new ToolStripMenuItem(Messages.COPY); public event Action DismissalRequested; public event Action GoToXenObjectRequested; public ActionBase Action { get; private set; } public bool Expanded { get; set; } public DataGridViewActionRow(ActionBase action) { Action = action; cancelItem.Click += ToolStripMenuItemCancel_Click; dismissItem.Click += ToolStripMenuItemDismiss_Click; goToItem.Click += ToolStripMenuItemGoTo_Click; copyItem.Click += ToolStripMenuItemCopy_Click; MinimumHeight = DataGridViewDropDownSplitButtonCell.MIN_ROW_HEIGHT; Cells.AddRange(expanderCell, statusCell, messageCell, locationCell, dateCell, actionCell); RefreshSelf(); } public void RefreshSelf() { var actionItems = new List(); if (!Action.IsCompleted && Action.CanCancel) actionItems.Add(cancelItem); if (Action.IsCompleted) actionItems.Add(dismissItem); var obj = Action.GetRelevantXenObject(); if (obj != null) actionItems.Add(goToItem); if (actionItems.Count > 0) actionItems.Add(separatorItem); actionItems.Add(copyItem); actionCell.RefreshItems(actionItems.ToArray()); statusCell.Value = Action.GetImage(); if (Expanded) { expanderCell.Value = Images.StaticImages.expanded_triangle; messageCell.Value = Action.GetDetails(); } else { expanderCell.Value = Images.StaticImages.contracted_triangle; messageCell.Value = Action.GetTitle(); } locationCell.Value = Action.GetLocation(); dateCell.Value = HelpersGUI.DateTimeToString(Action.Started.ToLocalTime(), Messages.DATEFORMAT_DMY_HM, true); } private void ToolStripMenuItemCancel_Click(object sender, EventArgs e) { Action.Cancel(); } private void ToolStripMenuItemDismiss_Click(object sender, EventArgs e) { if (DismissalRequested != null) DismissalRequested(this); } private void ToolStripMenuItemGoTo_Click(object sender, EventArgs e) { if (GoToXenObjectRequested != null) GoToXenObjectRequested(Action.GetRelevantXenObject()); } private void ToolStripMenuItemCopy_Click(object sender, EventArgs e) { string text = string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\"", Action.GetStatusString(), messageCell.Value, locationCell.Value, dateCell.Value); Clip.SetClipboardText(text); } } private class CustomToolStripRenderer : ToolStripProfessionalRenderer { public CustomToolStripRenderer() : base(new CustomToolstripColourTable()) { RoundedEdges = false; } } private class CustomToolstripColourTable : ProfessionalColorTable { public override Color ToolStripBorder { get { return Color.Gainsboro; } } } #endregion } }