Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2877_HiveImprovements/HeuristicLab.Clients.Hive.Administrator/3.3/Views/ResourcesView.cs @ 16752

Last change on this file since 16752 was 15635, checked in by swagner, 7 years ago

#2877: Improved groups/slaves treeview of Hive Administrator

  • prevented indirect cycles in parent relationshop of groups, which caused an endless loop
File size: 20.6 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Drawing;
25using System.Linq;
26using System.ServiceModel.Security;
27using System.Threading;
28using System.Threading.Tasks;
29using System.Windows.Forms;
30using HeuristicLab.Clients.Access;
31using HeuristicLab.Clients.Hive.Views;
32using HeuristicLab.Core;
33using HeuristicLab.Core.Views;
34using HeuristicLab.MainForm;
35using TS = System.Threading.Tasks;
36
37namespace HeuristicLab.Clients.Hive.Administrator.Views {
38  [View("Resources View")]
39  [Content(typeof(IItemList<Resource>), false)]
40  public partial class ResourcesView : ItemView, IDisposable {
41    public new IItemList<Resource> Content {
42      get { return (IItemList<Resource>)base.Content; }
43      set { base.Content = value; }
44    }
45
46    public const string UNGROUPED_GROUP_NAME = "UNGROUPED";
47    private TreeNode ungroupedGroupNode;
48    private const int slaveImageIndex = 0;
49    private const int slaveGroupImageIndex = 1;
50    private readonly Color ownedResourceColor = Color.LightGreen;
51    private readonly Color calculatingColor = Color.DarkGreen;
52    private readonly Color offlineColor = Color.Red;
53    private TS.Task progressTask;
54    private bool stopProgressTask;
55    private bool currentlyAuthorized;
56
57
58    public ResourcesView() {
59      InitializeComponent();
60      treeSlaveGroup.ImageList.Images.Add(HeuristicLab.Common.Resources.VSImageLibrary.MonitorLarge);
61      treeSlaveGroup.ImageList.Images.Add(HeuristicLab.Common.Resources.VSImageLibrary.NetworkCenterLarge);
62
63      HiveAdminClient.Instance.Refreshing += new EventHandler(Instance_Refreshing);
64      HiveAdminClient.Instance.Refreshed += new EventHandler(Instance_Refreshed);
65
66      Access.AccessClient.Instance.Refreshing += new EventHandler(AccessClient_Refreshing);
67      Access.AccessClient.Instance.Refreshed += new EventHandler(AccessClient_Refreshed);
68    }
69
70    private void UpdateProgress() {
71      while (!stopProgressTask) {
72        int diff = (progressBar.Maximum - progressBar.Minimum) / 10;
73
74        if (progressBar.InvokeRequired) {
75          progressBar.Invoke(new Action(delegate() { progressBar.Value = (progressBar.Value + diff) % progressBar.Maximum; }));
76        } else {
77          progressBar.Value = (progressBar.Value + diff) % progressBar.Maximum;
78        }
79
80        //ok, this is not very clever...
81        Thread.Sleep(500);
82      }
83      if (progressBar.InvokeRequired) {
84        progressBar.Invoke(new Action(delegate() { progressBar.Value = progressBar.Minimum; }));
85      } else {
86        progressBar.Value = progressBar.Minimum;
87      }
88    }
89
90    void Instance_Refreshing(object sender, EventArgs e) {
91      stopProgressTask = false;
92      progressTask = new TS.Task(UpdateProgress);
93      progressTask.Start();
94      SetEnabledStateOfControls();
95    }
96
97    void Instance_Refreshed(object sender, EventArgs e) {
98      stopProgressTask = true;
99      SetEnabledStateOfControls();
100    }
101
102    void AccessClient_Refreshing(object sender, EventArgs e) {
103      stopProgressTask = false;
104      progressTask = new TS.Task(UpdateProgress);
105      progressTask.Start();
106      SetEnabledStateOfControls();
107      btnPermissionsSave.Enabled = false;
108    }
109
110    void AccessClient_Refreshed(object sender, EventArgs e) {
111      stopProgressTask = true;
112      SetEnabledStateOfControls();
113    }
114
115    #region Register Content Events
116    protected override void DeregisterContentEvents() {
117      Content.ItemsAdded -= new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<Resource>>(Content_ItemsAdded);
118      Content.ItemsRemoved -= new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<Resource>>(Content_ItemsRemoved);
119      base.DeregisterContentEvents();
120    }
121    protected override void RegisterContentEvents() {
122      base.RegisterContentEvents();
123      Content.ItemsAdded += new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<Resource>>(Content_ItemsAdded);
124      Content.ItemsRemoved += new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<Resource>>(Content_ItemsRemoved);
125    }
126    #endregion
127
128    protected override void OnContentChanged() {
129      base.OnContentChanged();
130      if (Content == null) {
131        slaveView.Content = null;
132        scheduleView.Content = null;
133        permissionView.Content = null;
134        permissionView.FetchSelectedUsers = null;
135        treeSlaveGroup.Nodes.Clear();
136      } else {
137        permissionView.Content = Access.AccessClient.Instance;
138        treeSlaveGroup.Nodes.Clear();
139
140        //rebuild
141        var ungroupedSlaves = new SlaveGroup {
142          Id = Guid.NewGuid(),
143          Name = UNGROUPED_GROUP_NAME,
144          Description = "Slaves without a group"
145        };
146        ungroupedGroupNode = CreateTreeNode(ungroupedSlaves);
147
148        var parentlessResources = from r in Content
149                                  where r.ParentResourceId == null
150                                  orderby r.Name
151                                  select r;
152        foreach (Resource r in parentlessResources) {
153          if (r.GetType() == typeof(SlaveGroup)) {  // root node
154            var root = CreateTreeNode(r);
155            treeSlaveGroup.Nodes.Add(root);
156            BuildSlaveGroupTree((SlaveGroup)r, root);
157          } else if (r.GetType() == typeof(Slave)) {  // ungrouped slaves
158            ungroupedGroupNode.Nodes.Add(CreateTreeNode(r));
159          }
160        }
161        if (ungroupedGroupNode.Nodes.Count > 0)
162          ungroupedGroupNode.Text += "   [" + ungroupedGroupNode.Nodes.Count.ToString() + "]";
163        treeSlaveGroup.Nodes.Add(ungroupedGroupNode);
164      }
165    }
166
167    private void BuildSlaveGroupTree(SlaveGroup group, TreeNode node) {
168      var childGroups = from r in Content.OfType<SlaveGroup>()
169                        where r.ParentResourceId == @group.Id
170                        orderby r.Name
171                        select r;
172      foreach (var g in childGroups) {
173        var childNode = CreateTreeNode(g);
174        node.Nodes.Add(childNode);
175        BuildSlaveGroupTree(g, childNode);
176      }
177
178      var childSlaves = from r in Content.OfType<Slave>()
179                        where r.ParentResourceId == @group.Id
180                        orderby r.Name
181                        select r;
182      var childSlavesCount = 0;
183      foreach (var s in childSlaves) {
184        node.Nodes.Add(CreateTreeNode(s));
185        childSlavesCount++;
186      }
187      if (childSlavesCount > 0)
188        node.Text = group.Name + "   [" + childSlavesCount.ToString() + "]";
189    }
190
191    private TreeNode CreateTreeNode(Resource r) {
192      var node = new TreeNode();
193      node.Text = r.Name;
194
195      if (r is SlaveGroup) {
196        node.ImageIndex = slaveGroupImageIndex;
197      } else if (r is Slave) {
198        var s = r as Slave;
199        node.ImageIndex = slaveImageIndex;
200        if (s.SlaveState == SlaveState.Calculating) {
201          node.ForeColor = calculatingColor;
202          node.Text += "   [" + s.CpuUtilization.ToString("N2") + " %]";
203        } else if (s.SlaveState == SlaveState.Offline) {
204          node.ForeColor = offlineColor;
205          if (s.LastHeartbeat.HasValue)
206            node.Text += "   [" + s.LastHeartbeat?.ToString("g") + "]";
207        }
208      }
209      node.SelectedImageIndex = node.ImageIndex;
210
211      if (r.OwnerUserId == Access.UserInformation.Instance.User.Id) node.BackColor = ownedResourceColor;
212
213      node.Tag = r;
214      return node;
215    }
216
217    protected override void SetEnabledStateOfControls() {
218      base.SetEnabledStateOfControls();
219      if (Content == null) {
220        btnAddGroup.Enabled = false;
221        btnRemoveGroup.Enabled = false;
222        btnSave.Enabled = false;
223        btnPermissionsSave.Enabled = false;
224        permissionView.Enabled = false;
225        scheduleView.SetEnabledStateOfSchedule(false);
226        btnPermissionsSave.Enabled = false;
227        permissionView.Enabled = false;
228      } else {
229        btnAddGroup.Enabled = true;
230        btnRemoveGroup.Enabled = true;
231        btnSave.Enabled = true;
232        scheduleView.SetEnabledStateOfSchedule(IsAuthorized(slaveView.Content));
233        btnPermissionsSave.Enabled = permissionView.FetchSelectedUsers != null;
234        permissionView.Enabled = permissionView.FetchSelectedUsers != null;
235      }
236    }
237
238    private bool IsAuthorized(Resource resource) {
239      return resource != null
240          && resource != ungroupedGroupNode.Tag
241          && resource.Id != Guid.Empty
242          && UserInformation.Instance.UserExists
243          && (resource.OwnerUserId == UserInformation.Instance.User.Id || HiveRoles.CheckAdminUserPermissions());
244    }
245
246    private void treeSlaveGroup_AfterSelect(object sender, TreeViewEventArgs e) {
247      if (e.Action != TreeViewAction.Unknown) {
248        Resource selectedResource = ((Resource)e.Node.Tag);
249        currentlyAuthorized = IsAuthorized(selectedResource);
250        if (currentlyAuthorized) {
251          permissionView.FetchSelectedUsers = new Func<List<Guid>>(() => {
252            return HiveServiceLocator.Instance.CallHiveService<List<ResourcePermission>>(service => {
253              return service.GetResourcePermissions(selectedResource.Id);
254            }).Select(x => x.GrantedUserId).ToList();
255          });
256          if (!tabSlaveGroup.TabPages.Contains(tabPermissions)) tabSlaveGroup.TabPages.Add(tabPermissions);
257        } else {
258          permissionView.FetchSelectedUsers = null;
259          btnPermissionsSave.Enabled = false;
260          if (selectedResource.Id == Guid.Empty) {
261            if (!tabSlaveGroup.TabPages.Contains(tabPermissions)) tabSlaveGroup.TabPages.Add(tabPermissions);
262          } else tabSlaveGroup.TabPages.Remove(tabPermissions);
263        }
264
265        if (slaveView.Content != null && slaveView.Content is SlaveGroup) {
266          slaveView.Content.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(SlaveViewContent_PropertyChanged);
267        }
268
269        slaveView.Content = selectedResource;
270        HiveAdminClient.Instance.DowntimeForResourceId = selectedResource.Id;
271
272        if (selectedResource is SlaveGroup) {
273          slaveView.Content.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(SlaveViewContent_PropertyChanged);
274        }
275
276        if (tabSlaveGroup.SelectedIndex == 1) {
277          UpdateScheduleAsync();
278        } else if (tabSlaveGroup.SelectedIndex == 2) {
279          UpdatePermissionsAsync();
280        }
281      }
282    }
283
284    void SlaveViewContent_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
285      OnContentChanged();
286      if (e.PropertyName == "HbInterval") {
287        UpdateChildHbIntervall(slaveView.Content);
288      }
289    }
290
291    private void UpdateChildHbIntervall(Resource resource) {
292      foreach (Resource r in Content.Where(x => x.ParentResourceId == resource.Id)) {
293        r.HbInterval = resource.HbInterval;
294        if (r is SlaveGroup) {
295          UpdateChildHbIntervall(r);
296        }
297      }
298    }
299
300    private void btnAddGroup_Click(object sender, EventArgs e) {
301      SlaveGroup newGroup = new SlaveGroup();
302      newGroup.Name = "New Group";
303      newGroup.OwnerUserId = UserInformation.Instance.User.Id;
304      Content.Add(newGroup);
305    }
306
307    void Content_ItemsRemoved(object sender, Collections.CollectionItemsChangedEventArgs<Collections.IndexedItem<Resource>> e) {
308      OnContentChanged();
309    }
310
311    void Content_ItemsAdded(object sender, Collections.CollectionItemsChangedEventArgs<Collections.IndexedItem<Resource>> e) {
312      OnContentChanged();
313    }
314
315    private void btnRemoveGroup_Click(object sender, EventArgs e) {
316      if (treeSlaveGroup.SelectedNode != null && treeSlaveGroup.SelectedNode.Tag != null) {
317        Resource res = (Resource)treeSlaveGroup.SelectedNode.Tag;
318
319        DialogResult diagRes = MessageBox.Show("Do you really want to delete " + res.Name + "?", "HeuristicLab Hive Administrator", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
320        if (diagRes == DialogResult.Yes) {
321          if (res is Slave) {
322            Content.Remove(res);
323            HiveAdminClient.Delete(res);
324          } else if (res is SlaveGroup) {
325            //only delete empty groups
326            if (Content.Where(s => s.ParentResourceId == res.Id).Count() < 1) {
327              Content.Remove(res);
328              HiveAdminClient.Delete(res);
329            } else {
330              MessageBox.Show("Only empty groups can be deleted.", "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Error);
331            }
332          }
333        }
334      }
335    }
336
337    private void btnSave_Click(object sender, EventArgs e) {
338      foreach (Resource res in Content) {
339        if (res is SlaveGroup && res.Id == Guid.Empty) {
340          SlaveGroup slaveGroup = (SlaveGroup)res;
341          slaveGroup.Store();
342        } else if (res.Id != Guid.Empty && res.Modified) {
343          res.Store();
344        }
345      }
346    }
347
348    private void treeSlaveGroup_ItemDrag(object sender, ItemDragEventArgs e) {
349      var nodes = GetCheckedNodes(treeSlaveGroup.Nodes).ToList();
350      var draggedNode = (TreeNode)e.Item;
351      if (!draggedNode.Checked) nodes.Add(draggedNode);
352      nodes.Remove(ungroupedGroupNode);
353      var resources = nodes.Select(x => x.Tag).OfType<Resource>().ToList();
354
355      if ((resources.Count > 0) && resources.TrueForAll((r) => IsAuthorized(r))) {
356        DataObject data = new DataObject();
357        data.SetData(HeuristicLab.Common.Constants.DragDropDataFormat, resources);
358        var action = DoDragDrop(data, DragDropEffects.Copy | DragDropEffects.Link | DragDropEffects.Move);
359        if (action.HasFlag(DragDropEffects.Move)) {
360          foreach (var node in nodes) node.Remove();
361          ungroupedGroupNode.Text = ((Resource)ungroupedGroupNode.Tag).Name;
362          if (ungroupedGroupNode.Nodes.Count > 0)
363            ungroupedGroupNode.Text += "   [" + ungroupedGroupNode.Nodes.Count.ToString() + "]";
364
365        }
366      }
367    }
368
369    private IEnumerable<TreeNode> GetCheckedNodes(TreeNodeCollection nodes) {
370      if (nodes != null) {
371        foreach (var node in nodes.OfType<TreeNode>()) {
372          if (node.Checked) yield return node;
373          foreach (var child in GetCheckedNodes(node.Nodes))
374            yield return child;
375        }
376      }
377    }
378
379    private void treeSlaveGroup_DragEnterOver(object sender, DragEventArgs e) {
380      e.Effect = DragDropEffects.None;
381      var resources = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat) as IEnumerable<Resource>;
382      var node = treeSlaveGroup.GetNodeAt(treeSlaveGroup.PointToClient(new Point(e.X, e.Y)));
383      var group = node?.Tag as SlaveGroup;
384
385      if ((resources != null) && (node != null) && (group != null) && IsAuthorized(group) && !resources.Contains(group)) {
386        while ((node != null) && ((group == null) || !resources.Contains(group))) {  // prevent cycles in groups
387          node = node.Parent;
388          group = node?.Tag as SlaveGroup;
389        }
390        if ((node == null) && e.AllowedEffect.HasFlag(DragDropEffects.Move)) e.Effect = DragDropEffects.Move;
391      }
392    }
393
394    private void treeSlaveGroup_DragDrop(object sender, DragEventArgs e) {
395      if (e.Effect != DragDropEffects.None) {
396        var node = treeSlaveGroup.GetNodeAt(treeSlaveGroup.PointToClient(new Point(e.X, e.Y)));
397        var group = (SlaveGroup)node.Tag;  // drop is only allowed on slave groups, validity of drop target is check in treeSlaveGroup_DragEnterOver
398        var resources = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat) as IEnumerable<Resource>;
399
400        foreach (var r in resources) r.ParentResourceId = group.Id;
401        node.Nodes.Clear();
402        BuildSlaveGroupTree(group, node);
403      }
404    }
405
406    void ResetView() {
407      if (this.InvokeRequired) {
408        Invoke(new Action(ResetView));
409      } else {
410        treeSlaveGroup.Nodes.Clear();
411
412        if (slaveView.Content != null && slaveView.Content is SlaveGroup) {
413          slaveView.Content.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(SlaveViewContent_PropertyChanged);
414        }
415        slaveView.Content = null;
416        if (scheduleView.Content != null) {
417          scheduleView.Content.Clear();
418        }
419        HiveAdminClient.Instance.ResetDowntime();
420      }
421    }
422
423    private void UpdateResources() {
424      ResetView();
425
426      try {
427        if (!Access.UserInformation.Instance.UserExists) {
428          //do a refresh just in case that the user has changed his usr and pwd in between
429          Access.UserInformation.Instance.Refresh();
430        }
431        HiveAdminClient.Instance.Refresh();
432        Content = HiveAdminClient.Instance.Resources;
433      }
434      catch (MessageSecurityException) {
435        ShowMessageSecurityException();
436      }
437      catch (AnonymousUserException) {
438        ShowHiveInformationDialog();
439      }
440    }
441
442    private void ShowMessageSecurityException() {
443      if (this.InvokeRequired) {
444        Invoke(new Action(ShowMessageSecurityException));
445      } else {
446        MessageBox.Show("A Message Security error has occured. This normally means that your user name or password is wrong.", "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Error);
447      }
448    }
449
450    private void ShowHiveInformationDialog() {
451      if (this.InvokeRequired) {
452        Invoke(new Action(ShowHiveInformationDialog));
453      } else {
454        using (HiveInformationDialog dialog = new HiveInformationDialog()) {
455          dialog.ShowDialog(this);
456        }
457      }
458    }
459
460    private void UpdateResourcesAsync() {
461      TS.Task.Factory.StartNew(UpdateResources).ContinueWith((t) => {
462        DisplayError(t.Exception);
463      }, TaskContinuationOptions.OnlyOnFaulted);
464    }
465
466    private void UpdateSchedule() {
467      HiveAdminClient.Instance.RefreshCalendar();
468      scheduleView.Invoke(new Action(() => {
469        scheduleView.Content = HiveAdminClient.Instance.Downtimes;
470        SetEnabledStateOfControls();
471      }));
472    }
473
474    private void UpdateScheduleAsync() {
475      TS.Task.Factory.StartNew(UpdateSchedule).ContinueWith((t) => {
476        DisplayError(t.Exception);
477      }, TaskContinuationOptions.OnlyOnFaulted);
478    }
479
480    private void UpdatePermissions() {
481      if (permissionView.Content != null && permissionView.FetchSelectedUsers != null)
482        permissionView.Invoke(new Action(() => permissionView.ManualRefresh()));
483    }
484
485    private void UpdatePermissionsAsync() {
486      TS.Task.Factory.StartNew(UpdatePermissions).ContinueWith((t) => {
487        DisplayError(t.Exception);
488      }, TaskContinuationOptions.OnlyOnFaulted);
489    }
490
491
492    private void DisplayError(Exception ex) {
493      MessageBox.Show(string.Format("An error occured while updating: {0} {1}", Environment.NewLine, ex.Message), "HeuristicLab Hive Administrator", MessageBoxButtons.OK, MessageBoxIcon.Error);
494    }
495
496    private void tabSlaveGroup_SelectedIndexChanged(object sender, EventArgs e) {
497      if (tabSlaveGroup.SelectedIndex == 1) {
498        UpdateScheduleAsync();
499      } else if (tabSlaveGroup.SelectedIndex == 2) {
500        UpdatePermissionsAsync();
501      }
502    }
503
504    private void btnRefresh_Click(object sender, EventArgs e) {
505      UpdateResourcesAsync();
506    }
507
508    private void ResourcesView_Load(object sender, EventArgs e) {
509      UpdateResourcesAsync();
510    }
511
512    private void btnPermissionsSave_Click(object sender, EventArgs e) {
513      SetEnabledStateOfControls();
514      HiveServiceLocator.Instance.CallHiveService(service => {
515        service.GrantResourcePermissions(((Resource)treeSlaveGroup.SelectedNode.Tag).Id, permissionView.GetAddedUsers().Select(x => x.Id).ToList());
516        service.RevokeResourcePermissions(((Resource)treeSlaveGroup.SelectedNode.Tag).Id, permissionView.GetDeletedUsers().Select(x => x.Id).ToList());
517      });
518      SetEnabledStateOfControls();
519    }
520  }
521}
Note: See TracBrowser for help on using the repository browser.