#region License Information /* HeuristicLab * Copyright (C) 2002-2017 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Windows.Forms; using HeuristicLab.Clients.Access; using HeuristicLab.Clients.Hive.Views; using HeuristicLab.Collections; using HeuristicLab.Common.Resources; using HeuristicLab.Core; using HeuristicLab.Core.Views; using HeuristicLab.MainForm; namespace HeuristicLab.Clients.Hive.Administrator.Views { [View("Resources View")] [Content(typeof(IItemList), false)] public partial class ResourcesView : ItemView, IDisposable { private const int slaveImageIndex = 0; private const int slaveGroupImageIndex = 1; public const string UNGROUPED_GROUP_NAME = "UNGROUPED"; public const string UNGROUPED_GROUP_DESCRIPTION = "Contains slaves that are not assigned to any group."; private const string SELECTED_TAG = ""; // " [selected]"; private const string NOT_STORED_TAG = "*"; // " [not stored]"; private const string CHANGES_NOT_STORED_TAG = "*"; // " [changes not stored]"; private readonly Color changedColor = Color.FromArgb(255, 87, 191, 193); // #57bfc1 private readonly Color selectedBackColor = Color.DodgerBlue; private readonly Color selectedForeColor = Color.White; private readonly Color calculatingColor = Color.FromArgb(255, 58, 114, 35); // #3a7223 private readonly Color offlineColor = Color.FromArgb(255, 187, 36, 36); // #bb2424 private readonly Color grayTextColor = SystemColors.GrayText; private TreeNode ungroupedGroupNode; private Resource selectedResource = null; public Resource SelectedResource { get { return selectedResource; } set { if (selectedResource != value) ChangeSelectedResource(value); } } private readonly object locker = new object(); public new IItemList Content { get { return (IItemList)base.Content; } set { base.Content = value; } } public ResourcesView() { InitializeComponent(); treeView.ImageList.Images.Add(VSImageLibrary.MonitorLarge); treeView.ImageList.Images.Add(VSImageLibrary.NetworkCenterLarge); HiveAdminClient.Instance.Refreshing += HiveAdminClient_Instance_Refreshing; HiveAdminClient.Instance.Refreshed += HiveAdminClient_Instance_Refreshed; AccessClient.Instance.Refreshing += AccessClient_Instance_Refreshing; AccessClient.Instance.Refreshed += AccessClient_Instance_Refreshed; } #region Overrides protected override void OnClosing(FormClosingEventArgs e) { AccessClient.Instance.Refreshed -= AccessClient_Instance_Refreshed; AccessClient.Instance.Refreshing -= AccessClient_Instance_Refreshing; HiveAdminClient.Instance.Refreshed -= HiveAdminClient_Instance_Refreshed; HiveAdminClient.Instance.Refreshing -= HiveAdminClient_Instance_Refreshing; base.OnClosing(e); } protected override void RegisterContentEvents() { base.RegisterContentEvents(); Content.ItemsAdded += Content_ItemsAdded; Content.ItemsRemoved += Content_ItemsRemoved; } protected override void DeregisterContentEvents() { Content.ItemsRemoved -= Content_ItemsRemoved; Content.ItemsAdded -= Content_ItemsAdded; base.DeregisterContentEvents(); } protected override void OnContentChanged() { base.OnContentChanged(); if (Content == null) { treeView.Nodes.Clear(); viewHost.Content = null; scheduleView.Content = null; } else { BuildResourceTree(Content); } } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); bool locked = Content == null || selectedResource == null || Locked || ReadOnly; bool adminLocked = locked || !IsAdmin(); bool addLocked = adminLocked || selectedResource.Id == Guid.Empty || selectedResource is Slave; bool deleteLocked = adminLocked || HiveAdminClient.Instance.ResourceDescendants[selectedResource.Id].Any(); btnAddGroup.Enabled = !addLocked; btnRemoveGroup.Enabled = !deleteLocked; btnSave.Enabled = !locked; scheduleView.Locked = adminLocked; } #endregion #region Event Handlers private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs> e) { if (InvokeRequired) Invoke((Action>>)Content_ItemsAdded, sender, e); else { OnContentChanged(); } } private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs> e) { if (InvokeRequired) Invoke((Action>>)Content_ItemsRemoved, sender, e); else { OnContentChanged(); } } private void SlaveViewContent_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (InvokeRequired) Invoke((Action)SlaveViewContent_PropertyChanged, sender, e); else { OnContentChanged(); if (e.PropertyName == "HbInterval") { UpdateChildHbIntervall((Resource)viewHost.Content); } } } private void HiveAdminClient_Instance_Refreshing(object sender, EventArgs e) { if (InvokeRequired) Invoke((Action)HiveAdminClient_Instance_Refreshing, sender, e); else { var mainForm = MainFormManager.GetMainForm(); mainForm.AddOperationProgressToView(this, "Refreshing ..."); SetEnabledStateOfControls(); } } private void HiveAdminClient_Instance_Refreshed(object sender, EventArgs e) { if (InvokeRequired) Invoke((Action)HiveAdminClient_Instance_Refreshed, sender, e); else { var mainForm = MainFormManager.GetMainForm(); mainForm.RemoveOperationProgressFromView(this); SetEnabledStateOfControls(); } } private void AccessClient_Instance_Refreshing(object sender, EventArgs e) { if (InvokeRequired) Invoke((Action)AccessClient_Instance_Refreshing, sender, e); else { var mainForm = MainFormManager.GetMainForm(); mainForm.AddOperationProgressToView(this, "Refreshing ..."); SetEnabledStateOfControls(); } } private void AccessClient_Instance_Refreshed(object sender, EventArgs e) { if (InvokeRequired) Invoke((Action)AccessClient_Instance_Refreshed, sender, e); else { var mainForm = MainFormManager.GetMainForm(); mainForm.RemoveOperationProgressFromView(this); SetEnabledStateOfControls(); } } private async void ResourcesView_Load(object sender, EventArgs e) { await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions( action: () => UpdateResources()); } private async void btnRefresh_Click(object sender, EventArgs e) { lock (locker) { if (!btnRefresh.Enabled) return; btnRefresh.Enabled = false; } await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions( action: () => UpdateResources(), finallyCallback: () => btnRefresh.Enabled = true); } private void btnAddGroup_Click(object sender, EventArgs e) { Guid? parentResourceId = null; if (!IsAdmin()) { MessageBox.Show( "You have no permission to add a resource group.", "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } else if (selectedResource != null && selectedResource.Id == Guid.Empty) { MessageBox.Show( "You cannot add a resource group to a not yet stored group.", "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if (selectedResource != null && selectedResource is SlaveGroup) { parentResourceId = selectedResource.Id; } else { MessageBox.Show( "You cannot add a resource group to a slave.", "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var group = new SlaveGroup { Name = "New Group", OwnerUserId = UserInformation.Instance.User.Id, ParentResourceId = parentResourceId }; //HiveAdminClient.Instance.UpdateResourceGenealogy(Content); SelectedResource = group; Content.Add(group); } private async void btnRemoveGroup_Click(object sender, EventArgs e) { if (selectedResource == null) return; lock (locker) { if (!btnRemoveGroup.Enabled) return; btnRemoveGroup.Enabled = false; } if (!IsAdmin()) { MessageBox.Show( "You have no permission to delete resource groups.", "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if (Content.Any(x => x.ParentResourceId == selectedResource.Id)) { MessageBox.Show( "Only empty resource groups can be deleted.", "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var result = MessageBox.Show( "Do you really want to delete " + selectedResource.Name + "?", "HeuristicLab Hive Administrator", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions( action: () => { RemoveResource(selectedResource); }, finallyCallback: () => { btnRemoveGroup.Enabled = true; }); } else { btnRemoveGroup.Enabled = true; } } private async void btnSave_Click(object sender, EventArgs e) { lock (locker) { if (!btnSave.Enabled) return; btnSave.Enabled = false; } await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions( action: () => { var resourcesToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified); foreach (var resource in resourcesToSave) resource.Store(); UpdateResources(); }, finallyCallback: () => btnSave.Enabled = true); OnContentChanged(); } private void treeSlaveGroup_MouseDown(object sender, MouseEventArgs e) { var node = treeView.GetNodeAt(e.Location); if (node == null || node == ungroupedGroupNode) return; var r = (Resource)node.Tag; if (!HiveAdminClient.Instance.DisabledParentResources.Contains(r)) ChangeSelectedResourceNode(node); } private void treeSlaveGroup_BeforeSelect(object sender, TreeViewCancelEventArgs e) { e.Cancel = true; } private void treeSlaveGroup_BeforeCheck(object sender, TreeViewCancelEventArgs e) { if (e.Node == ungroupedGroupNode) { e.Cancel = true; } else { var r = (Resource)e.Node.Tag; if (HiveAdminClient.Instance.DisabledParentResources.Contains(r)) { e.Cancel = true; } } } private void treeSlaveGroup_DragDrop(object sender, DragEventArgs e) { if (e.Effect == DragDropEffects.None) return; var targetNode = treeView.GetNodeAt(treeView.PointToClient(new Point(e.X, e.Y))); var targetResource = (targetNode != null) ? (Resource)targetNode.Tag : null; var resources = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat) as IEnumerable; foreach (var r in resources) { r.ParentResourceId = targetResource != null ? targetResource.Id : (Guid?)null; } // TODO //HiveAdminClient.Instance.UpdateResourceGenealogy(Content); OnContentChanged(); } private void treeSlaveGroup_ItemDrag(object sender, ItemDragEventArgs e) { if (!IsAdmin()) return; var nodes = GetCheckedNodes(treeView.Nodes).ToList(); TreeNode sourceNode = (TreeNode)e.Item; if (!sourceNode.Checked) nodes.Add(sourceNode); nodes.Remove(ungroupedGroupNode); ungroupedGroupNode.Checked = false; var resources = nodes.Select(x => x.Tag).OfType().ToList(); if (resources.Count > 0) { DataObject data = new DataObject(); data.SetData(HeuristicLab.Common.Constants.DragDropDataFormat, resources); var action = DoDragDrop(data, DragDropEffects.Copy | DragDropEffects.Link | DragDropEffects.Move); if (action.HasFlag(DragDropEffects.Move)) { foreach (var node in nodes) node.Remove(); StyleTreeNode(ungroupedGroupNode, (Resource)ungroupedGroupNode.Tag, resources); } } } private IEnumerable GetCheckedNodes(TreeNodeCollection nodes) { if (nodes != null) { foreach (var node in nodes.OfType()) { if (node.Checked && node != ungroupedGroupNode) yield return node; foreach (var child in GetCheckedNodes(node.Nodes)) yield return child; } } } private void treeSlaveGroup_DragEnterOver(object sender, DragEventArgs e) { e.Effect = DragDropEffects.Move; var resources = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat) as IEnumerable; var targetNode = treeView.GetNodeAt(treeView.PointToClient(new Point(e.X, e.Y))); var targetResource = (targetNode != null ? targetNode.Tag : null) as Resource; if (!IsAdmin() || resources == null || !resources.Any() || resources.Any(x => !HiveAdminClient.Instance.CheckParentChange(x, targetResource)) || (targetNode != null && (targetNode == ungroupedGroupNode || targetNode.Parent == ungroupedGroupNode))) { e.Effect = DragDropEffects.None; } } private void TabSlaveGroup_TabIndexChanged(object sender, EventArgs e) { throw new NotImplementedException(); } private async void TabSlaveGroup_Selected(object sender, System.Windows.Forms.TabControlEventArgs e) { if (e.TabPage == tabSchedule) { await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions( action: () => UpdateSchedule(), finallyCallback: () => scheduleView.Content = HiveAdminClient.Instance.Downtimes); } SetEnabledStateOfControls(); } #endregion #region Helpers private void BuildResourceTree(IEnumerable resources) { treeView.Nodes.Clear(); if (!resources.Any()) return; var disabledParentResources = HiveAdminClient.Instance.DisabledParentResources; var mainResources = new HashSet(resources.OfType() .Where(x => x.ParentResourceId == null)); //var parentedMainResources = new HashSet(resources.OfType() // .Where(x => x.ParentResourceId.HasValue && !resources.Select(y => y.Id).Contains(x.ParentResourceId.Value))); //mainResources.UnionWith(parentedMainResources); var mainDisabledParentResources = new HashSet(disabledParentResources.Where(x => x.ParentResourceId == null || x.ParentResourceId == Guid.Empty)); mainResources.UnionWith(mainDisabledParentResources); var subResources = new HashSet(resources.Union(disabledParentResources).Except(mainResources).OrderByDescending(x => x.Name)); var stack = new Stack(mainResources.OrderByDescending(x => x.Name)); if (selectedResource != null) SelectedResource = resources.Where(x => x.Id == selectedResource.Id).FirstOrDefault(); bool nodeSelected = false; TreeNode currentNode = null; Resource currentResource = null; while (stack.Any()) { var newResource = stack.Pop(); var newNode = new TreeNode(newResource.Name) { Tag = newResource }; StyleTreeNode(newNode, newResource, resources); if (selectedResource == null && !disabledParentResources.Contains(newResource)) { SelectedResource = newResource; } if (!nodeSelected && selectedResource != null && newResource.Id == selectedResource.Id) { newNode.BackColor = selectedBackColor; newNode.ForeColor = selectedForeColor; newNode.Text += SELECTED_TAG; nodeSelected = true; } if (disabledParentResources.Contains(newResource)) { newNode.Checked = false; newNode.ForeColor = grayTextColor; } // search for parent node of newNode and save in currentNode // necessary since newNodes (stack top items) might be siblings // or grand..grandparents of previous node (currentNode) while (currentNode != null && newResource.ParentResourceId != currentResource.Id) { currentNode = currentNode.Parent; currentResource = currentNode == null ? null : (Resource)currentNode.Tag; } if (currentNode == null) { treeView.Nodes.Add(newNode); } else { currentNode.Nodes.Add(newNode); } if (newResource is SlaveGroup) { var childResources = subResources.Where(x => x.ParentResourceId == newResource.Id); if (childResources.Any()) { foreach (var resource in childResources.OrderByDescending(x => x.Name)) { subResources.Remove(resource); stack.Push(resource); } currentNode = newNode; currentResource = newResource; } } newNode.SelectedImageIndex = newNode.ImageIndex; } // collapse slave-only nodes foreach (TreeNode n in treeView.Nodes) { CollapseSlaveOnlyNodes(n); } ungroupedGroupNode = new TreeNode(UNGROUPED_GROUP_NAME) { ForeColor = SystemColors.GrayText, Tag = new SlaveGroup() { Name = UNGROUPED_GROUP_NAME, Description = UNGROUPED_GROUP_DESCRIPTION } }; foreach (var slave in subResources.OfType().OrderBy(x => x.Name)) { var slaveNode = new TreeNode(slave.Name) { Tag = slave }; StyleTreeNode(slaveNode, slave, resources); ungroupedGroupNode.Nodes.Add(slaveNode); if (selectedResource == null) { SelectedResource = slave; } if (slave.Id == selectedResource.Id && !nodeSelected) { slaveNode.BackColor = selectedBackColor; slaveNode.ForeColor = selectedForeColor; slaveNode.Text += SELECTED_TAG; nodeSelected = true; } } if (ungroupedGroupNode.Nodes.Count > 0) { ungroupedGroupNode.Text += " [" + ungroupedGroupNode.Nodes.Count.ToString() + "]"; ungroupedGroupNode.Expand(); } treeView.Nodes.Add(ungroupedGroupNode); } private void CollapseSlaveOnlyNodes(TreeNode tn) { Resource r = (Resource)tn.Tag; var descendants = GetResourceDescendants(); if (descendants.ContainsKey(r.Id)) { if (descendants[r.Id].OfType().Any()) { tn.Expand(); foreach (TreeNode n in tn.Nodes) CollapseSlaveOnlyNodes(n); } else { tn.Collapse(); } } } private void ExpandResourceNodesOfInterest(TreeNodeCollection nodes) { foreach (TreeNode n in nodes) { Resource r = (Resource)n.Tag; if (n.Nodes.Count > 0) { if (HiveAdminClient.Instance.GetAvailableResourceDescendants(r.Id).OfType().Any()) { n.Expand(); ExpandResourceNodesOfInterest(n.Nodes); } else { n.Collapse(); } } else { n.Collapse(); } } } private void UpdateChildHbIntervall(Resource resource) { foreach (Resource r in Content.Where(x => x.ParentResourceId == resource.Id)) { r.HbInterval = resource.HbInterval; if (r is SlaveGroup) { UpdateChildHbIntervall(r); } } } private void UpdateResources() { try { HiveAdminClient.Instance.Refresh(); Content = HiveAdminClient.Instance.Resources; } catch (AnonymousUserException) { ShowHiveInformationDialog(); } } private void RemoveResource(Resource resource) { if (resource == null) return; try { if (resource.Id != Guid.Empty) { var resourcesToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified); foreach (var r in resourcesToSave) r.Store(); SelectedResource = HiveAdminClient.Instance.GetAvailableResourceAncestors(resource.Id).LastOrDefault(); HiveAdminClient.Delete(resource); UpdateResources(); } else { Content.Remove(selectedResource); } } catch (AnonymousUserException) { ShowHiveInformationDialog(); } } private void UpdateSchedule() { try { HiveAdminClient.Instance.RefreshCalendar(); } catch (AnonymousUserException) { ShowHiveInformationDialog(); } } private bool IsAdmin() { return HiveRoles.CheckAdminUserPermissions(); } private void StyleTreeNode(TreeNode n, Resource r, IEnumerable resources) { n.Text = r.Name; n.BackColor = Color.Transparent; n.ForeColor = Color.Black; if (HiveAdminClient.Instance.DisabledParentResources.Select(x => x.Id).Contains(r.Id)) { n.ForeColor = grayTextColor; } else if (r.Id == Guid.Empty && n != ungroupedGroupNode /*!r.Name.StartsWith(UNGROUPED_GROUP_NAME)*/) { // not stored (i.e. new) n.Text += NOT_STORED_TAG; } else if (r.Modified && n != ungroupedGroupNode /*!r.Name.StartsWith(UNGROUPED_GROUP_NAME)*/) { // changed n.Text += CHANGES_NOT_STORED_TAG; } // slave count int childSlavesCount = 0; if (r.Id != Guid.Empty && r is SlaveGroup) { var descendants = GetResourceDescendants(); if (descendants.ContainsKey(r.Id)) { childSlavesCount = resources .OfType() .Where(x => descendants[r.Id].Select(y => y.Id) .Contains(x.Id)) .Count(); } } else if (n == ungroupedGroupNode /*|| r.Name.StartsWith(UNGROUPED_GROUP_NAME)*/) { childSlavesCount = resources .OfType() .Where(x => x.ParentResourceId == null || (x.ParentResourceId.HasValue && x.ParentResourceId.Value == Guid.Empty)) .Count(); } if (childSlavesCount > 0) n.Text += " [" + childSlavesCount.ToString() + "]"; // slave image index, state, utilization if (r is Slave) { n.ImageIndex = slaveImageIndex; var s = r as Slave; if (s.SlaveState == SlaveState.Calculating) { n.ForeColor = calculatingColor; n.Text += " [" + s.CpuUtilization.ToString("N2") + "%]"; } else if (s.SlaveState == SlaveState.Offline) { n.ForeColor = offlineColor; if (s.LastHeartbeat.HasValue) n.Text += " [" + (s.LastHeartbeat != null ? s.LastHeartbeat.Value.ToString("g") : null) + "]"; } } else { n.ImageIndex = slaveGroupImageIndex; } // ungrouped if (n == ungroupedGroupNode /*r.Name.StartsWith(UNGROUPED_GROUP_NAME)*/) { n.ForeColor = SystemColors.GrayText; } } private void ResetTreeNodes(TreeNodeCollection nodes, IEnumerable resources) { foreach (TreeNode n in nodes) { StyleTreeNode(n, (Resource)n.Tag, resources); if (n.Nodes.Count > 0) { ResetTreeNodes(n.Nodes, resources); } } } private async void ChangeSelectedResource(Resource resource) { selectedResource = resource; viewHost.Content = selectedResource; HiveAdminClient.Instance.DowntimeForResourceId = selectedResource != null ? selectedResource.Id : Guid.Empty; if (tabSlaveGroup.SelectedTab == tabSchedule) { await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions( action: () => UpdateSchedule(), finallyCallback: () => scheduleView.Content = HiveAdminClient.Instance.Downtimes); } SetEnabledStateOfControls(); } private void ChangeSelectedResourceNode(TreeNode resourceNode) { if (resourceNode == null) return; SelectedResource = (Resource)resourceNode.Tag; ResetTreeNodes(treeView.Nodes, Content); resourceNode.BackColor = selectedBackColor; resourceNode.ForeColor = selectedForeColor; resourceNode.Text += SELECTED_TAG; } private void ShowHiveInformationDialog() { if (InvokeRequired) Invoke((Action)ShowHiveInformationDialog); else { using (HiveInformationDialog dialog = new HiveInformationDialog()) { dialog.ShowDialog(this); } } } private void ResetView() { if (InvokeRequired) Invoke((Action)ResetView); else { treeView.Nodes.Clear(); if (viewHost.Content != null && viewHost.Content is SlaveGroup) { ((SlaveGroup)viewHost.Content).PropertyChanged -= SlaveViewContent_PropertyChanged; } viewHost.Content = null; if (scheduleView.Content != null) { scheduleView.Content.Clear(); } HiveAdminClient.Instance.ResetDowntime(); } } private Dictionary> GetResourceDescendants() { var resourceDescendants = new Dictionary>(); var resources = Content.Union(HiveAdminClient.Instance.DisabledParentResources).ToList(); foreach (var r in resources) resourceDescendants.Add(r.Id, new HashSet()); foreach (var r in resources) { var parentResourceId = r.ParentResourceId; while (parentResourceId != null) { var parent = resources.SingleOrDefault(x => x.Id == parentResourceId); if (parent != null) { resourceDescendants[parent.Id].Add(r); parentResourceId = parent.ParentResourceId; } else { parentResourceId = null; } } } return resourceDescendants; } #endregion } }