/* Copyright (c) Citrix Systems Inc. * All rights reserved. * * 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.Windows.Forms; using XenAdmin.CustomFields; using XenAdmin.XenSearch; using XenAPI; using XenAdmin.Core; using XenAdmin.Controls.CustomGridView; using XenAdmin.Dialogs; using XenAdmin.Model; namespace XenAdmin.Controls.XenSearch { public class QueryPanel : GridView { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private const int ROW_HEIGHT = 27; private const int FOLDER_ROW_HEIGHT = 14; public static Brush TextBrush = SystemBrushes.ControlText; public static Brush RedBrush = Brushes.Red; public static Brush LinkBrush = Brushes.Blue; public static Brush GreyBrush = SystemBrushes.GrayText; public static Brush DarkGreyBrush = new SolidBrush(Color.FromArgb(255, 100, 100, 100)); private static readonly Font HeaderBoldFont = new Font(Program.DefaultFont.FontFamily, Program.DefaultFont.Size + 0.2f, FontStyle.Bold); private static readonly Font HeaderSmallFont = new Font(Program.DefaultFont.FontFamily, Program.DefaultFont.Size); private static MetricUpdater MetricUpdater; private Search search; public event Action SearchChanged; private readonly UpdateManager listUpdateManager = new UpdateManager(); public QueryPanel() { this.SuspendLayout(); MetricUpdater = PropertyAccessorHelper.MetricUpdater; // new MetricUpdater(); MetricUpdater.MetricsUpdated += MetricsUpdated; this.ResumeLayout(false); base.HasLeftExpanders = false; VerticalScroll.Visible = true; SetupHeaderRow(); CustomFieldsManager.CustomFieldsChanged += CustomFields_CustomFieldsChanged; listUpdateManager.Update += listUpdateManager_Update; } protected override void Dispose(bool disposing) { if (disposing) { if (MetricUpdater != null) MetricUpdater.MetricsUpdated -= MetricsUpdated; CustomFieldsManager.CustomFieldsChanged -= CustomFields_CustomFieldsChanged; listUpdateManager.Update -= listUpdateManager_Update; listUpdateManager.Dispose(); } base.Dispose(disposing); } private void OnSearchChanged() { if (SearchChanged != null) SearchChanged(); } void CustomFields_CustomFieldsChanged() { SetupCustomColumns(); } #region Column Handling Code private GridHeaderRow GridHeaderRow = null; private readonly Dictionary Columns = new Dictionary(); private static readonly String[] DEFAULT_COLUMNS = new String[]{ "name", "cpu", "memory", "disks", "network", "ip", "ha", "uptime" }; private static bool IsDefaultColumn(String column) { return Array.IndexOf(DEFAULT_COLUMNS, column) > -1; } private bool IsMovableColumn(string column) { if (GridHeaderRow == null || !GridHeaderRow.Items.ContainsKey(column)) return true; GridHeaderItem item = GridHeaderRow.Items[column] as GridHeaderItem; if (item == null) return true; return !item.Immovable; } private void ShowColumn(String column) { Columns[column] = true; if (column == "ha" && !HelpersGUI.HAEnabledOnAtLeastOnePool) { HideColumn("ha"); return; } if (!GridHeaderRow.Columns.Contains(column)) { if (IsDefaultColumn(column)) { GridHeaderRow.AddItem(column, GetDefaultColumn(column)); } else { String text = column.StartsWith(CustomFieldsManager.CUSTOM_FIELD) ? column.Substring(CustomFieldsManager.CUSTOM_FIELD.Length) : column; GridHeaderRow.AddItem(column, NewHeaderItem(text, SortOrder.Ascending, 100, 100)); } } } private void HideColumn(String column) { Columns[column] = false; GridHeaderRow.RemoveItem(column); } private void ToggleColumn(String column) { if (Columns[column]) HideColumn(column); else ShowColumn(column); } private void RemoveColumn(String column) { if (IsDefaultColumn(column)) return; HideColumn(column); Columns.Remove(column); } private void SetupHeaderRow() { GridHeaderRow = new GridHeaderRow(ROW_HEIGHT); foreach (string s in DEFAULT_COLUMNS) ShowColumn(s); SetHeaderRow(GridHeaderRow); } private static GridItemBase GetDefaultColumn(string s) { if (s == "name") { GridHeaderItem nameHeaderItem = NewHeaderItem(Messages.OVERVIEW_NAME, SortOrder.Ascending, 250, 250); nameHeaderItem.IsDefaultSortColumn = true; nameHeaderItem.Immovable = true; return nameHeaderItem; } if (s == "cpu") { return NewHeaderItem(Messages.OVERVIEW_CPU_USAGE, SortOrder.Descending, 115, 115); } if (s == "memory") { return NewHeaderItem(Messages.OVERVIEW_MEMORY_USAGE, SortOrder.Descending, 125, 125); } if (s == "disks") { return NewGridHeaderItem(Messages.OVERVIEW_DISKS, SortOrder.Descending, 120, 120); } if (s == "network") { return NewGridHeaderItem(Messages.OVERVIEW_NETWORK, SortOrder.Descending, 120, 120); } if (s == "ha") { return NewHeaderItem(Messages.HA, SortOrder.Ascending, 120, 120); } if (s == "ip") { return NewHeaderItem(Messages.ADDRESS, SortOrder.Ascending, 120, 120); } if (s == "uptime") { return NewHeaderItem(Messages.UPTIME, SortOrder.Descending, 170, 170); } // dont be a muppet, only ask for a default column return NewHeaderItem(s, SortOrder.Ascending, 100, 100); } private void ShowColumns(List> columns) { List columnKeys = new List(Columns.Keys); if (columns == null) { // Show all columns foreach (String column in columnKeys) ShowColumn(column); foreach (CustomFieldDefinition definition in CustomFieldsManager.GetCustomFields()) ShowColumn(CustomFieldsManager.CUSTOM_FIELD + definition.Name); return; } // Hide all columns which are currently visible but not in columns // (do not hide default columns) foreach (String column in columnKeys) { if (!IsMovableColumn(column)) continue; if (columns.Exists(delegate(KeyValuePair kvp) { return kvp.Key == column; })) continue; HideColumn(column); } ShowColumn("ha"); // force decision to show ha // Show appropriate columns int i = 0; foreach (GridHeaderItem item in HeaderRow.Items.Values) { if (!item.Immovable) break; i++; } foreach (KeyValuePair column in columns) { if (!IsMovableColumn(column.Key)) continue; String key = (column.Key.StartsWith(CustomFieldsManager.CUSTOM_FIELD) || IsDefaultColumn(column.Key)) ? column.Key : CustomFieldsManager.CUSTOM_FIELD + column.Key; ShowColumn(key); if (!HeaderRow.Items.ContainsKey(key)) continue; GridHeaderItem item = HeaderRow.Items[key] as GridHeaderItem; if (item == null) continue; // Make the column the correct width; item.Width = column.Value; // Move the column to the correct place; GridHeaderRow.Columns.Remove(column.Key); GridHeaderRow.Columns.Insert(i, column.Key); i++; } } public override void OpenChooseColumnsMenu(Point point) { ContextMenu contextMenu = new ContextMenu(); foreach (String column in Columns.Keys) { if (!IsMovableColumn(column)) continue; if (column == "ha" && !Columns[column] && !HelpersGUI.HAEnabledOnAtLeastOnePool) // it's off, and we can't turn it on continue; String columnCopy = column; MenuItem item = new MenuItem(GetI18nColumnName(column).Ellipsise(30)); item.Checked = Columns[column]; item.Enabled = IsMovableColumn(column); item.Click += delegate(object sender, EventArgs e) { item.Checked = !item.Checked; ToggleColumn(columnCopy); Refresh(); }; contextMenu.MenuItems.Add(item); } contextMenu.Show(this, point); } public List GetChooseColumnsMenu() { List menuItems = new List(); foreach (String column in Columns.Keys) { if (!IsMovableColumn(column)) continue; if (column == "ha" && !Columns[column] && !HelpersGUI.HAEnabledOnAtLeastOnePool) // it's off, and we can't turn it on continue; String columnCopy = column; ToolStripMenuItem item = new ToolStripMenuItem(GetI18nColumnName(column).Ellipsise(30)); item.Checked = Columns[column]; item.Enabled = IsMovableColumn(column); item.Click += delegate(object sender, EventArgs e) { item.Checked = !item.Checked; ToggleColumn(columnCopy); Refresh(); }; menuItems.Add(item); } return menuItems; } private static string GetI18nColumnName(string column) { switch (column) { case "name": return Messages.OVERVIEW_NAME; case "cpu": return Messages.OVERVIEW_CPU_USAGE; case "memory": return Messages.OVERVIEW_MEMORY_USAGE; case "disks": return Messages.OVERVIEW_DISKS; case "network": return Messages.OVERVIEW_NETWORK; case "ha": return Messages.HA; case "ip": return Messages.ADDRESS; case "uptime": return Messages.UPTIME; default: return column.StartsWith(CustomFieldsManager.CUSTOM_FIELD) ? column.Substring(CustomFieldsManager.CUSTOM_FIELD.Length) : column; } } /// /// Add and/or remove custom columns to the grid. /// private void SetupCustomColumns() { List customFieldDefinitions = CustomFieldsManager.GetCustomFields(); // Did the user remove a custom field? String[] columns = new String[Columns.Keys.Count]; Columns.Keys.CopyTo(columns, 0); foreach (String column in columns) { // Skip default columns if (IsDefaultColumn(column)) continue; // Does the custom column in table exist in the custom fields // collection? If not remove. if (!customFieldDefinitions.Exists(delegate(CustomFieldDefinition definition) { return definition.Name == column; })) RemoveColumn(column); } // Add any new columns foreach (CustomFieldDefinition customFieldDefinition in customFieldDefinitions) { if (Columns.ContainsKey(customFieldDefinition.Name)) continue; ShowColumn(CustomFieldsManager.CUSTOM_FIELD + customFieldDefinition.Name); } BuildList(); } #endregion [Browsable(false)] public Search Search { set { search = value; if (search != null) { ShowColumns(search.Columns); Sorting = search.Sorting; BuildList(); } } } internal override void Sort() { LastClickedRow = null; OnSearchChanged(); } public Sort[] Sorting { get { List sorting = new List(); foreach (SortParams sp in HeaderRow.CompareOrder) sorting.Add(new Sort(sp.Column, sp.SortOrder == SortOrder.Ascending)); return sorting.ToArray(); } set { List order = new List(); foreach (Sort sort in value) order.Add(new SortParams(sort.Column, sort.Ascending ? SortOrder.Ascending : SortOrder.Descending)); HeaderRow.UpdateCellSorts(order); } } // Are any of the sort columns based on one of the performance metrics? public bool SortingByMetrics { get { foreach (SortParams sp in HeaderRow.CompareOrder) { if (sp.Column == "cpu" || sp.Column == "memory" || sp.Column == "disks" || sp.Column == "network" || sp.Column == "uptime") return true; } return false; } } private void listUpdateManager_Update(object sender, EventArgs e) { GridRow root = new GridRow(-1); RowGroupAcceptor acceptor = new RowGroupAcceptor(root); CollectionAcceptor ca = new CollectionAcceptor(); bool addedAny = search.PopulateAdapters(acceptor, ca); Program.Invoke(Program.MainWindow, delegate { SaveRowStates(); Clear(); if (!addedAny) { AddNoResultsRow(); } foreach (GridRow row in root.Rows) { AddRow(row); } MetricUpdater.SetXenObjects(ca.XenObjects.ToArray()); RestoreRowStates(); Refresh(); }); } public void BuildList() { Program.AssertOnEventThread(); if (search == null) return; listUpdateManager.RequestUpdate(); } private class RowGroupAcceptor : IAcceptGroups { private readonly GridRow _gridRow; public RowGroupAcceptor(GridRow gridRow) { _gridRow = gridRow; } public IAcceptGroups Add(Grouping grouping, object group, int indent) { GridRow gridRow = CreateRow(grouping, group, indent); if (gridRow != null) { _gridRow.AddRow(gridRow); return new RowGroupAcceptor(gridRow); } return null; } public void FinishedInThisGroup(bool defaultExpand) { } } private void AddNoResultsRow() { GridRow row = new GridRow(ROW_HEIGHT); GridStringItem resultsItem = new GridStringItem(Messages.OVERVIEW_NO_RESULTS, HorizontalAlignment.Left, VerticalAlignment.Middle, false, false, TextBrush, Program.DefaultFont, 6); row.AddItem("name", resultsItem); AddRow(row); } private static GridHeaderArrayItem NewGridHeaderItem(string label, SortOrder defaultSortOrder, int width, int minwidth) { return new GridHeaderArrayItem( new GridStringItem[] { new GridStringItem(label, HorizontalAlignment.Center, VerticalAlignment.Middle, false, false, TextBrush, HeaderBoldFont, GreyBrush, HeaderBoldFont), new GridStringItem(Messages.OVERVIEW_UNITS, HorizontalAlignment.Center, VerticalAlignment.Middle, false, false, TextBrush, HeaderSmallFont, GreyBrush, HeaderSmallFont) }, HorizontalAlignment.Center, VerticalAlignment.Middle, TextBrush, HeaderBoldFont, defaultSortOrder, width, minwidth); } private static GridHeaderItem NewHeaderItem(string label, SortOrder defaultSortOrder, int width, int minwidth) { return new GridHeaderItem(HorizontalAlignment.Center, VerticalAlignment.Middle, TextBrush, HeaderBoldFont, label, defaultSortOrder, width, minwidth, GreyBrush); } private static void AddCustomFieldsToRow(IXenObject o, GridRow row) { foreach (CustomFieldDefinition customFieldDefinition in CustomFieldsManager.GetCustomFields()) { GridStringItem customFieldItem = new GridStringItem( new CustomFieldWrapper(o, customFieldDefinition), HorizontalAlignment.Center, VerticalAlignment.Middle, false, false, TextBrush, Program.DefaultFont, new EventHandler(delegate { using (PropertiesDialog dialog = new PropertiesDialog(o)) { dialog.SelectCustomFieldsEditPage(); dialog.ShowDialog(); } })); row.AddItem(CustomFieldsManager.CUSTOM_FIELD + customFieldDefinition.Name, customFieldItem); } } private static GridVerticalArrayItem NewDoubleRowItem(Grouping grouping, object group) { string line1 = (grouping is BoolGrouping ? (grouping as BoolGrouping).GroupingName2(group) : grouping.GroupingName); // exception for Boolean groups: CA-165366 object line2 = (group is DateTime ? group : grouping.GetGroupName(group)); // exception for DateTime: CA-46983 List items = new List(); if (line1 != null) items.Add(new GridStringItem(line1, HorizontalAlignment.Left, VerticalAlignment.Middle, false, true, TextBrush, Program.DefaultFont)); if (line2 != null) items.Add(new GridStringItem(line2, HorizontalAlignment.Left, VerticalAlignment.Middle, false, false, DarkGreyBrush, Program.DefaultFont)); return new GridVerticalArrayItem(items.ToArray(), false); } private static GridRow NewGroupRow(string opaqueref, object tag, int rowHeight, int priority) { GridRow row = new GridRow(rowHeight); row.OpaqueRef = opaqueref; row.Tag = tag; row.Expanded = true; row.Priority = priority; return row; } private static GridHorizontalArrayItem NewNameItem(GridItemBase iconItem, GridItemBase dataItem, int iconWidth, int indent) { GridTreeExpanderItem expander = new GridTreeExpanderItem(); GridItemBase[] items = { expander, iconItem, dataItem }; int[] widths = { 12, iconWidth }; return new GridHorizontalArrayItem(indent, items, widths, false); } private static GridRow CreateRow(Grouping grouping, Object o, int indent) { IXenObject ixmo = o as IXenObject; if (ixmo != null) { bool isFolderRow = (o is Folder); GridRow _row = NewGroupRow(ixmo.opaque_ref, ixmo, isFolderRow ? FOLDER_ROW_HEIGHT : ROW_HEIGHT, 0); foreach (ColumnNames column in Enum.GetValues(typeof(ColumnNames))) { GridItemBase item = ColumnAccessors.Get(column).GetGridItem(ixmo); if (item != null) { if (column == XenAdmin.XenSearch.ColumnNames.name) { EventHandler onDoubleClickDelegate = isFolderRow ? (EventHandler)delegate { Program.MainWindow.DoSearch(Search.SearchForFolder(ixmo.opaque_ref)); } : (EventHandler)delegate { if (Program.MainWindow.SelectObject(ixmo) && Program.MainWindow.TheTabControl.TabPages.Contains(Program.MainWindow.TabPageGeneral)) Program.MainWindow.SwitchToTab(MainWindow.Tab.Settings); }; GridImageItem _statusItem = new GridImageItem( "foo", new ImageDelegate(delegate() { return Images.GetImage16For(ixmo); }), HorizontalAlignment.Left, VerticalAlignment.Top, true, onDoubleClickDelegate); _row.AddItem("name", NewNameItem(_statusItem, item, 16, indent)); } else _row.AddItem(column.ToString(), item); } } AddCustomFieldsToRow(ixmo, _row); return _row; } if (grouping == null) return null; GridRow row = NewGroupRow(String.Format("{0}: {1}", grouping.GroupingName, o), null, ROW_HEIGHT, 0); GridImageItem statusItem = new GridImageItem( grouping.GroupingName, new ImageDelegate(delegate() { return Images.GetImage16For(grouping.GetGroupIcon(o)); }), HorizontalAlignment.Left, VerticalAlignment.Top, true); GridVerticalArrayItem nameItem = NewDoubleRowItem(grouping, o); row.AddItem("name", NewNameItem(statusItem, nameItem, 16, indent)); return row; } public override bool IsDraggableRow(GridRow row) { IXenObject o = row.Tag as IXenObject; return o != null; } private void MetricsUpdated(object o, EventArgs e) { Program.Invoke(Program.MainWindow, delegate { if (SortingByMetrics) Sort(); else Refresh(); }); } public static void PanelShown() { MetricUpdater.Start(); } public static void PanelHidden() { MetricUpdater.Pause(); } internal static void Prod() { MetricUpdater.Prod(); } } public class CollectionAcceptor : IAcceptGroups { public List XenObjects = new List(); public IAcceptGroups Add(Grouping grouping, object group, int indent) { if (group is Host || group is VM) XenObjects.Add((IXenObject)group); return this; } public void FinishedInThisGroup(bool defaultExpand) { } } }