/* 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.Collections.ObjectModel; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.Drawing; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Threading; using System.Windows.Forms; using System.Xml; using XenAdmin.Actions.GUIActions; using XenAdmin.Controls.MainWindowControls; using XenAdmin.Wizards.ImportWizard; using XenAPI; using XenAdmin.Actions; using XenAdmin.Alerts; using XenAdmin.Commands; using XenAdmin.Controls; using XenAdmin.Core; using XenAdmin.Dialogs; using XenAdmin.Model; using XenAdmin.Network; using XenAdmin.TabPages; using XenAdmin.XenSearch; using XenAdmin.Wizards.PatchingWizard; using XenAdmin.Plugins; using XenCenterLib; using System.Linq; using XenAdmin.Help; using XenAdmin.Wizards; namespace XenAdmin { [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] [ComVisibleAttribute(true)] public partial class MainWindow : Form, ISynchronizeInvoke, IMainWindow { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); /// /// A mapping between objects in the tree and the associated selected tab. /// private Dictionary selectedTabs = new Dictionary(); /// /// The selected tab for the overview node. /// private TabPage selectedOverviewTab = null; internal readonly PerformancePage PerformancePage = new PerformancePage(); internal readonly GeneralTabPage GeneralPage = new GeneralTabPage(); internal readonly BallooningPage BallooningPage = new BallooningPage(); internal readonly ConsolePanel ConsolePanel = new ConsolePanel(); internal readonly CvmConsolePanel CvmConsolePanel = new CvmConsolePanel(); internal readonly HAPage HAPage = new HAPage(); internal readonly HAUpsellPage HAUpsellPage = new HAUpsellPage(); internal readonly HomePage HomePage = new HomePage(); internal readonly SearchPage SearchPage = new SearchPage(); internal readonly NetworkPage NetworkPage = new NetworkPage(); internal readonly NICPage NICPage = new NICPage(); internal readonly WlbPage WlbPage = new WlbPage(); internal readonly WLBUpsellPage WLBUpsellPage = new WLBUpsellPage(); internal readonly SrStoragePage SrStoragePage = new SrStoragePage(); internal readonly PhysicalStoragePage PhysicalStoragePage = new PhysicalStoragePage(); internal readonly VMStoragePage VMStoragePage = new VMStoragePage(); internal readonly AdPage AdPage = new AdPage(); internal readonly ADUpsellPage AdUpsellPage = new ADUpsellPage(); internal readonly GpuPage GpuPage = new GpuPage(); internal readonly PvsPage PvsPage = new PvsPage(); internal readonly DockerProcessPage DockerProcessPage = new DockerProcessPage(); internal readonly DockerDetailsPage DockerDetailsPage = new DockerDetailsPage(); internal readonly UsbPage UsbPage = new UsbPage(); private ActionBase statusBarAction = null; private bool IgnoreTabChanges = false; private bool ToolbarsEnabled; /// /// Helper boolean to only trigger Resize_End when window is really resized by dragging edges /// Without this Resize_End is triggered even when window is moved around and not resized /// private bool mainWindowResized = false; private readonly Dictionary> activePoolWizards = new Dictionary>(); private readonly Dictionary activeXenModelObjectWizards = new Dictionary(); /// /// The arguments passed in on the command line. /// private string[] CommandLineParam = null; private ArgType CommandLineArgType = ArgType.None; private static readonly System.Windows.Forms.Timer CheckForUpdatesTimer = new System.Windows.Forms.Timer(); private readonly PluginManager pluginManager; private readonly ContextMenuBuilder contextMenuBuilder; private readonly LicenseManagerLauncher licenseManagerLauncher; private readonly LicenseTimer licenseTimer; public readonly HealthCheckOverviewLauncher HealthCheckOverviewLauncher; private readonly System.Windows.Forms.Timer healthCheckResultTimer = new System.Windows.Forms.Timer(); private Dictionary pluginMenuItemStartIndexes = new Dictionary(); private bool expandTreeNodesOnStartup; private int connectionsInProgressOnStartup; [DllImport("kernel32.dll", CharSet = CharSet.Auto)] static extern uint RegisterApplicationRestart(string pszCommandline, uint dwFlags); public MainWindow(ArgType argType, string[] args) { Program.MainWindow = this; licenseManagerLauncher = new LicenseManagerLauncher(Program.MainWindow); HealthCheckOverviewLauncher = new HealthCheckOverviewLauncher(Program.MainWindow); InvokeHelper.Initialize(this); InitializeComponent(); SetMenuItemStartIndexes(); Icon = Properties.Resources.AppIcon; //CA-270999: Add registration to RestartManager RegisterApplicationRestart(null, 0); #region Add Tab pages components.Add(NICPage); components.Add(VMStoragePage); components.Add(SrStoragePage); components.Add(PerformancePage); components.Add(GeneralPage); components.Add(BallooningPage); components.Add(ConsolePanel); components.Add(CvmConsolePanel); components.Add(NetworkPage); components.Add(HAPage); components.Add(HomePage); components.Add(WlbPage); components.Add(AdPage); components.Add(GpuPage); components.Add(PvsPage); components.Add(SearchPage); components.Add(DockerProcessPage); components.Add(DockerDetailsPage); components.Add(UsbPage); AddTabContents(VMStoragePage, TabPageStorage); AddTabContents(SrStoragePage, TabPageSR); AddTabContents(NICPage, TabPageNICs); AddTabContents(PerformancePage, TabPagePeformance); AddTabContents(GeneralPage, TabPageGeneral); AddTabContents(BallooningPage, TabPageBallooning); AddTabContents(ConsolePanel, TabPageConsole); AddTabContents(CvmConsolePanel, TabPageCvmConsole); AddTabContents(NetworkPage, TabPageNetwork); AddTabContents(HAPage, TabPageHA); AddTabContents(HAUpsellPage, TabPageHAUpsell); AddTabContents(HomePage, TabPageHome); AddTabContents(WlbPage, TabPageWLB); AddTabContents(WLBUpsellPage, TabPageWLBUpsell); AddTabContents(PhysicalStoragePage, TabPagePhysicalStorage); AddTabContents(AdPage, TabPageAD); AddTabContents(AdUpsellPage, TabPageADUpsell); AddTabContents(GpuPage, TabPageGPU); AddTabContents(PvsPage, TabPagePvs); AddTabContents(SearchPage, TabPageSearch); AddTabContents(DockerProcessPage, TabPageDockerProcess); AddTabContents(DockerDetailsPage, TabPageDockerDetails); AddTabContents(UsbPage, TabPageUSB); #endregion PoolCollectionChangedWithInvoke = Program.ProgramInvokeHandler(CollectionChanged); MessageCollectionChangedWithInvoke = Program.ProgramInvokeHandler(MessageCollectionChanged); HostCollectionChangedWithInvoke = Program.ProgramInvokeHandler(CollectionChanged); VMCollectionChangedWithInvoke = Program.ProgramInvokeHandler(CollectionChanged); SRCollectionChangedWithInvoke = Program.ProgramInvokeHandler(CollectionChanged); FolderCollectionChangedWithInvoke = Program.ProgramInvokeHandler(CollectionChanged); TaskCollectionChangedWithInvoke = Program.ProgramInvokeHandler(MeddlingActionManager.TaskCollectionChanged); ConnectionsManager.History.CollectionChanged += History_CollectionChanged; CommandLineArgType = argType; CommandLineParam = args; pluginManager = new PluginManager(); pluginManager.PluginsChanged += pluginManager_PluginsChanged; pluginManager.LoadPlugins(); contextMenuBuilder = new ContextMenuBuilder(pluginManager, this); ((WinformsXenAdminConfigProvider) XenAdminConfigManager.Provider).PluginManager = pluginManager; eventsPage.GoToXenObjectRequested += eventsPage_GoToXenObjectRequested; SearchPage.SearchChanged += SearchPanel_SearchChanged; Alert.RegisterAlertCollectionChanged(XenCenterAlerts_CollectionChanged); Updates.RegisterCollectionChanged(Updates_CollectionChanged); FormFontFixer.Fix(this); Folders.InitFolders(); DockerContainers.InitDockerContainers(); OtherConfigAndTagsWatcher.InitEventHandlers(); // Fix colour of text on gradient panels TitleLabel.ForeColor = Program.TitleBarForeColor; loggedInLabel1.SetTextColor(Program.TitleBarForeColor); statusProgressBar.Visible = false; SelectionManager.BindTo(MainMenuBar.Items, this); SelectionManager.BindTo(ToolStrip.Items, this); Properties.Settings.Default.SettingChanging += Default_SettingChanging; licenseTimer = new LicenseTimer(licenseManagerLauncher); GeneralPage.LicenseLauncher = licenseManagerLauncher; toolStripSeparator7.Visible = xenSourceOnTheWebToolStripMenuItem.Visible = xenCenterPluginsOnlineToolStripMenuItem.Visible = !HiddenFeatures.ToolStripMenuItemHidden; healthCheckToolStripMenuItem1.Visible = !HiddenFeatures.HealthCheckHidden; } private void Default_SettingChanging(object sender, SettingChangingEventArgs e) { if (e == null) return; if (e.SettingName == "AutoSwitchToRDP" || e.SettingName == "EnableRDPPolling") { ConsolePanel.ResetAllViews(); if (SelectionManager.Selection.FirstIsRealVM) ConsolePanel.setCurrentSource((VM)SelectionManager.Selection.First); else if (SelectionManager.Selection.FirstIs()) ConsolePanel.setCurrentSource((Host)SelectionManager.Selection.First); UnpauseVNC(sender == TheTabControl); } } private void SetMenuItemStartIndexes() { foreach (ToolStripMenuItem menu in MainMenuBar.Items) { foreach (ToolStripItem item in menu.DropDownItems) { if (item != null && item.Text == "PluginItemsPlaceHolder") { pluginMenuItemStartIndexes.Add(menu, menu.DropDownItems.IndexOf(item)); menu.DropDownItems.Remove(item); break; } } } } internal SelectionBroadcaster SelectionManager { get { return navigationPane.SelectionManager; } } internal ContextMenuBuilder ContextMenuBuilder { get { return contextMenuBuilder; } } protected override void OnLoad(EventArgs e) { Program.AssertOnEventThread(); History.EnableHistoryButtons(); History.NewHistoryItem(new XenModelObjectHistoryItem(null, TabPageHome)); /* * Resume window size and location */ try { // Bring in previous version user setting for the first time. if (Properties.Settings.Default.DoUpgrade) { Properties.Settings.Default.Upgrade(); Properties.Settings.Default.DoUpgrade = false; XenAdmin.Settings.TrySaveSettings(); } Point savedLocation = Properties.Settings.Default.WindowLocation; Size savedSize = Properties.Settings.Default.WindowSize; if (HelpersGUI.WindowIsOnScreen(savedLocation, savedSize)) { this.Location = savedLocation; this.Size = savedSize; } } catch { } // Using the Load event ensures that the handle has been // created: base.OnLoad(e); } protected override void OnShown(EventArgs e) { base.OnShown(e); TheTabControl.Visible = true; alertPage.Visible = updatesPage.Visible = eventsPage.Visible = false; navigationPane.FocusTreeView(); } protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); Clip.RegisterClipboardViewer(); } protected override void WndProc(ref System.Windows.Forms.Message e) { //System.Console.WriteLine(Win32.GetWindowsMessageName(e.Msg)); switch (e.Msg) { case Win32.WM_CHANGECBCHAIN: // Clipboard chain has changed. Clip.ProcessWMChangeCBChain(e); break; case Win32.WM_DRAWCLIPBOARD: // Content of clipboard has changed. Clip.ProcessWMDrawClipboard(e); break; case Win32.WM_DESTROY: Clip.UnregisterClipboardViewer(); base.WndProc(ref e); break; default: base.WndProc(ref e); break; } } private void AddTabContents(Control contents, TabPage TabPage) { contents.Location = new Point(0, 0); contents.Size = TabPage.Size; contents.Anchor = AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Top | AnchorStyles.Right; TabPage.Controls.Add(contents); } void History_CollectionChanged(object sender, CollectionChangeEventArgs e) { if (Program.Exiting) return; Program.BeginInvoke(Program.MainWindow, () => { ActionBase action = e.Element as ActionBase; switch (e.Action) { case CollectionChangeAction.Add: { if (action == null) return; var meddlingAction = action as MeddlingAction; if (meddlingAction == null) { SetStatusBar(null, null); if (statusBarAction != null) { statusBarAction.Changed -= actionChanged; statusBarAction.Completed -= actionCompleted; } statusBarAction = action; } action.Changed += actionChanged; action.Completed += actionCompleted; actionChanged(action); break; } case CollectionChangeAction.Remove: { if (action != null) { action.Changed -= actionChanged; action.Completed -= actionCompleted; } else { var range = e.Element as List; if (range != null) { foreach (var a in range) { a.Changed -= actionChanged; a.Completed -= actionCompleted; } } else { return; } } int errors = ConnectionsManager.History.Count(a => a.IsCompleted && !a.Succeeded); navigationPane.UpdateNotificationsButton(NotificationsSubMode.Events, errors); if (eventsPage.Visible) { TitleLabel.Text = NotificationsSubModeItem.GetText(NotificationsSubMode.Events, errors); TitleIcon.Image = NotificationsSubModeItem.GetImage(NotificationsSubMode.Events, errors); } break; } } }); } private void actionCompleted(ActionBase action) { actionChanged(action); if (action is SrAction) Program.Invoke(this, UpdateToolbars); } private void actionChanged(ActionBase action) { if (Program.Exiting) return; Program.Invoke(this, () => actionChanged_(action)); } private void actionChanged_(ActionBase action) { // suppress updates when the PureAsyncAction runs the action to populate the ApiMethodsToRoleCheck if (action.SuppressProgressReport) return; var percentage = action.PercentComplete; Debug.Assert(0 <= percentage && percentage <= 100, "PercentComplete is out of range, the reporting action needs to be fixed."); //CA-8517 var meddlingAction = action as MeddlingAction; if (meddlingAction == null) { statusProgressBar.Visible = action.ShowProgress && !action.IsCompleted; if (percentage < 0) percentage = 0; else if (percentage > 100) percentage = 100; statusProgressBar.Value = percentage; // Don't show cancelled exception if (action.Exception != null && !(action.Exception is CancelledException)) { SetStatusBar(Properties.Resources._000_error_h32bit_16, action.Exception.Message); } else { SetStatusBar(null, action.IsCompleted ? null : !string.IsNullOrEmpty(action.Description) ? action.Description : !string.IsNullOrEmpty(action.Title) ? action.Title : null); } } int errors = ConnectionsManager.History.Count(a => a.IsCompleted && !a.Succeeded && !(a is CancellingAction && ((CancellingAction)a).Cancelled)); navigationPane.UpdateNotificationsButton(NotificationsSubMode.Events, errors); if (eventsPage.Visible) { TitleLabel.Text = NotificationsSubModeItem.GetText(NotificationsSubMode.Events, errors); TitleIcon.Image = NotificationsSubModeItem.GetImage(NotificationsSubMode.Events, errors); } } private void SetStatusBar(Image image, string message) { statusLabel.Image = image; statusLabel.Text = Helpers.FirstLine(message); } private void MainWindow_Shown(object sender, EventArgs e) { MainMenuBar.Location = new Point(0, 0); var rendProf = ToolStrip.Renderer as ToolStripProfessionalRenderer; if (rendProf != null) rendProf.RoundedEdges = false; ConnectionsManager.XenConnections.CollectionChanged += XenConnection_CollectionChanged; try { Settings.RestoreSession(); HealthCheck.SendProxySettingsToHealthCheck(); } catch (ConfigurationErrorsException ex) { log.Error("Could not load settings.", ex); Program.CloseSplash(); using (var dlg = new ThreeButtonDialog( new ThreeButtonDialog.Details( SystemIcons.Error, string.Format(Messages.MESSAGEBOX_LOAD_CORRUPTED, Settings.GetUserConfigPath()), Messages.MESSAGEBOX_LOAD_CORRUPTED_TITLE))) { dlg.ShowDialog(this); } Application.Exit(); return; //return explicitly because Application.Exit() does not exit the current method. } ToolbarsEnabled = Properties.Settings.Default.ToolbarsEnabled; RequestRefreshTreeView(); // if there are fewer than 30 connections, then expand the tree nodes. expandTreeNodesOnStartup = ConnectionsManager.XenConnectionsCopy.Count < 30; connectionsInProgressOnStartup = 0; // kick-off connections for all the loaded server list foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) { if (!connection.SaveDisconnected) { connectionsInProgressOnStartup++; connection.ConnectionStateChanged += Connection_ConnectionStateChangedOnStartup; connection.CachePopulated += connection_CachePopulatedOnStartup; XenConnectionUI.BeginConnect(connection, true, this, true); } } ThreadPool.QueueUserWorkItem(delegate { // Sleep a short time before closing the splash Thread.Sleep(500); Program.Invoke(Program.MainWindow, Program.CloseSplash); }); if (!Program.RunInAutomatedTestMode && !Helpers.CommonCriteriaCertificationRelease) { if (!Properties.Settings.Default.SeenAllowUpdatesDialog) new AllowUpdatesDialog(pluginManager).ShowDialog(this); // start checkforupdates thread CheckForUpdatesTimer.Interval = 1000 * 60 * 60 * 24; // 24 hours CheckForUpdatesTimer.Tick += CheckForUpdatesTimer_Tick; CheckForUpdatesTimer.Start(); Updates.CheckForUpdates(false); } if (!Program.RunInAutomatedTestMode) { // start healthCheckResult thread healthCheckResultTimer.Interval = 1000 * 60 * 60; // 1 hour healthCheckResultTimer.Tick += HealthCheckResultTimer_Tick; healthCheckResultTimer.Start(); } ProcessCommand(CommandLineArgType, CommandLineParam); } private void CheckForUpdatesTimer_Tick(object sender, EventArgs e) { Updates.CheckForUpdates(false); } private void HealthCheckResultTimer_Tick(object sender, EventArgs e) { HealthCheck.CheckForAnalysisResults(); } private void LoadTasksAsMeddlingActions(IXenConnection connection) { if (!connection.IsConnected || connection.Session == null) return; Dictionary, Task> tasks = Task.get_all_records(connection.Session); foreach (KeyValuePair, Task> pair in tasks) { pair.Value.Connection = connection; pair.Value.opaque_ref = pair.Key; MeddlingActionManager.ForceAddTask(pair.Value); } } private void connection_CachePopulatedOnStartup(object sender, EventArgs e) { IXenConnection c = (IXenConnection)sender; c.CachePopulated -= connection_CachePopulatedOnStartup; if (expandTreeNodesOnStartup) TrySelectNewNode(c, false, true, false); Program.Invoke(this, ShowAboutDialogOnStartup); } private void Connection_ConnectionStateChangedOnStartup(object sender, EventArgs e) { IXenConnection c = (IXenConnection)sender; c.ConnectionStateChanged -= Connection_ConnectionStateChangedOnStartup; Program.Invoke(Program.MainWindow, delegate { connectionsInProgressOnStartup--; // show the About dialog if this was the last connection in progress and the connection failed if (!c.IsConnected) ShowAboutDialogOnStartup(); }); } /// /// Show the About dialog after all conncections kicked-off on startup have finished the connection phase (cache populated) /// Must be called on the event thread. /// private void ShowAboutDialogOnStartup() { Program.AssertOnEventThread(); if (connectionsInProgressOnStartup > 0) return; if (Properties.Settings.Default.ShowAboutDialog && HiddenFeatures.LicenseNagVisible) ShowForm(typeof(AboutDialog)); } private bool Launched = false; internal void ProcessCommand(ArgType argType, string[] args) { switch (argType) { case ArgType.Import: log.DebugFormat("Importing VM export from {0}", args[0]); OpenGlobalImportWizard(args[0]); break; case ArgType.License: log.DebugFormat("Installing license from {0}", args[0]); LaunchLicensePicker(args[0]); break; case ArgType.Restore: log.DebugFormat("Restoring host backup from {0}", args[0]); new RestoreHostFromBackupCommand(this, null, args[0]).Execute(); break; case ArgType.Update: log.DebugFormat("Installing server update from {0}", args[0]); InstallUpdate(args[0]); break; case ArgType.XenSearch: log.DebugFormat("Importing saved XenSearch from '{0}'", args[0]); new ImportSearchCommand(this, args[0]).Execute(); break; case ArgType.Connect: log.DebugFormat("Connecting to server '{0}'", args[0]); IXenConnection connection = new XenConnection(); connection.Hostname = args[0]; connection.Port = ConnectionsManager.DEFAULT_XEN_PORT; connection.Username = args[1]; connection.Password = args[2]; if (ConnectionsManager.XenConnectionsContains(connection)) break; lock (ConnectionsManager.ConnectionsLock) ConnectionsManager.XenConnections.Add(connection); XenConnectionUI.BeginConnect(connection, true, null, false); break; case ArgType.None: if (Launched) { // The user has launched the splash screen, but we're already running. // Draw his attention. HelpersGUI.BringFormToFront(this); Activate(); } break; case ArgType.Passwords: Trace.Assert(false); break; } Launched = true; } // Manages UI and network updates whenever hosts are added and removed void XenConnection_CollectionChanged(object sender, CollectionChangeEventArgs e) { if (Program.Exiting) return; //Program.AssertOnEventThread(); Program.BeginInvoke(Program.MainWindow, () => XenConnectionCollectionChanged(e)); } private readonly CollectionChangeEventHandler PoolCollectionChangedWithInvoke = null; private readonly CollectionChangeEventHandler MessageCollectionChangedWithInvoke = null; private readonly CollectionChangeEventHandler HostCollectionChangedWithInvoke = null; private readonly CollectionChangeEventHandler VMCollectionChangedWithInvoke = null; private readonly CollectionChangeEventHandler SRCollectionChangedWithInvoke = null; private readonly CollectionChangeEventHandler FolderCollectionChangedWithInvoke = null; private readonly CollectionChangeEventHandler TaskCollectionChangedWithInvoke = null; private void XenConnectionCollectionChanged(CollectionChangeEventArgs e) { try { IXenConnection connection = e.Element as IXenConnection; navigationPane.XenConnectionCollectionChanged(e); if (e.Action == CollectionChangeAction.Add) { if (connection == null) return; connection.ClearingCache += connection_ClearingCache; connection.ConnectionResult += Connection_ConnectionResult; connection.ConnectionLost += Connection_ConnectionLost; connection.ConnectionClosed += Connection_ConnectionClosed; connection.ConnectionReconnecting += connection_ConnectionReconnecting; connection.XenObjectsUpdated += Connection_XenObjectsUpdated; connection.Cache.RegisterCollectionChanged(MessageCollectionChangedWithInvoke); connection.Cache.RegisterCollectionChanged(PoolCollectionChangedWithInvoke); connection.Cache.RegisterCollectionChanged(HostCollectionChangedWithInvoke); connection.Cache.RegisterCollectionChanged(VMCollectionChangedWithInvoke); connection.Cache.RegisterCollectionChanged(SRCollectionChangedWithInvoke); connection.Cache.RegisterCollectionChanged(FolderCollectionChangedWithInvoke); connection.Cache.RegisterCollectionChanged(TaskCollectionChangedWithInvoke); connection.CachePopulated += connection_CachePopulated; } else if (e.Action == CollectionChangeAction.Remove) { var range = new List(); if (connection != null) { range.Add(connection); } else { var r = e.Element as List; if (r != null) range = r; else return; } foreach (var con in range) { con.ClearingCache -= connection_ClearingCache; con.ConnectionResult -= Connection_ConnectionResult; con.ConnectionLost -= Connection_ConnectionLost; con.ConnectionClosed -= Connection_ConnectionClosed; con.ConnectionReconnecting -= connection_ConnectionReconnecting; con.XenObjectsUpdated -= Connection_XenObjectsUpdated; con.Cache.DeregisterCollectionChanged(MessageCollectionChangedWithInvoke); con.Cache.DeregisterCollectionChanged(PoolCollectionChangedWithInvoke); con.Cache.DeregisterCollectionChanged(HostCollectionChangedWithInvoke); con.Cache.DeregisterCollectionChanged(VMCollectionChangedWithInvoke); con.Cache.DeregisterCollectionChanged(SRCollectionChangedWithInvoke); con.Cache.DeregisterCollectionChanged(FolderCollectionChangedWithInvoke); con.Cache.DeregisterCollectionChanged(TaskCollectionChangedWithInvoke); con.CachePopulated -= connection_CachePopulated; foreach (VM vm in con.Cache.VMs) { ConsolePanel.closeVNCForSource(vm); } foreach (Host host in con.Cache.Hosts) { ConsolePanel.closeVNCForSource(host.ControlDomainZero()); foreach (VM vm in host.OtherControlDomains()) CvmConsolePanel.closeVNCForSource(vm); } con.EndConnect(); } //CA-41228 refresh submenu items when there are no connections SelectionManager.RefreshSelection(); } } catch (Exception exn) { log.Error(exn, exn); // Can't do any more about this. } } /// /// Closes any wizards for this connection. Must be done before we clear the cache so that per-VM wizards are closed. /// In many cases this is already covered (e.g. if the user explicitly disconnects). This method ensures we also /// do it when we unexpectedly lose the connection. /// /// /// private void connection_ClearingCache(object sender, EventArgs e) { IXenConnection connection = (IXenConnection)sender; CloseActiveWizards(connection); Alert.RemoveAlert(alert => alert.Connection != null && alert.Connection.Equals(connection)); Updates.CheckServerPatches(); Updates.CheckServerVersion(); RequestRefreshTreeView(); } void connection_CachePopulated(object sender, EventArgs e) { IXenConnection connection = sender as IXenConnection; if (connection == null) return; Host master = Helpers.GetMaster(connection); if (master == null) return; log.InfoFormat("Connected to {0} (version {1}, build {2}.{3}) with {4} {5} (build {6}.{7})", Helpers.GetName(master), Helpers.HostProductVersionText(master), Helpers.HostProductVersion(master), master.BuildNumberRaw(), Messages.XENCENTER, Branding.PRODUCT_VERSION_TEXT, Branding.XENCENTER_VERSION, Program.Version.Revision); // Check the PRODUCT_BRAND if (!Program.RunInAutomatedTestMode && !SameProductBrand(master)) { connection.EndConnect(); Program.Invoke(Program.MainWindow, delegate { var title = string.Format(Messages.CONNECTION_REFUSED_TITLE, Helpers.GetName(master).Ellipsise(80)); new ActionBase(title, "", false, true, Messages.INCOMPATIBLE_PRODUCTS); using (var dlog = new ConnectionRefusedDialog {ErrorMessage = Messages.INCOMPATIBLE_PRODUCTS, Url = ""}) dlog.ShowDialog(this); }); return; } //check the pool has no slaves earlier than the lowest supported version //(could happen if trying to connect to a partially upgraded pool where //the newest hosts have been upgraded using a earlier XenCenter) var slaves = connection.Cache.Hosts.Where(h => h.opaque_ref != master.opaque_ref); foreach (var slave in slaves) { if (Helpers.DundeeOrGreater(slave)) continue; connection.EndConnect(); Program.Invoke(Program.MainWindow, () => { var title = string.Format(Messages.CONNECTION_REFUSED_TITLE, Helpers.GetName(master).Ellipsise(80)); new ActionBase(title, "", false, true, Messages.SLAVE_TOO_OLD); using (var dlg = new ThreeButtonDialog( new ThreeButtonDialog.Details(SystemIcons.Error, Messages.SLAVE_TOO_OLD, Messages.CONNECT_TO_SERVER), ThreeButtonDialog.ButtonOK)) { dlg.ShowDialog(this); } }); return; } // When releasing a new version of the server, we should set xencenter_min and xencenter_max on the server // as follows: // // xencenter_min should be the lowest version of XenCenter we want the new server to work with. In the // (common) case that we want to force the user to upgrade XenCenter when they upgrade the server, // xencenter_min should equal the current version of XenCenter. // if (server_min > current_version) // // xencenter_max should always equal the current version of XenCenter. This ensures that even if they are // not required to upgrade, we at least warn them. // else if (server_max > current_version) int server_min = master.XenCenterMin(); int server_max = master.XenCenterMax(); if (server_min > 0 && server_max > 0) { int current_version = (int)API_Version.LATEST; if (server_min > current_version) { connection.EndConnect(); Program.Invoke(Program.MainWindow, delegate { var msg = string.Format(Messages.GUI_OUT_OF_DATE, Helpers.GetName(master)); var url = InvisibleMessages.OUT_OF_DATE_WEBSITE; var title = string.Format(Messages.CONNECTION_REFUSED_TITLE, Helpers.GetName(master).Ellipsise(80)); var error = $"{msg}\n{url}"; new ActionBase(title, "", false, true, error); using (var dlog = new ConnectionRefusedDialog {ErrorMessage = msg, Url = url}) dlog.ShowDialog(this); }); return; } if (server_max > current_version) Alert.AddAlert(new GuiOldAlert()); LoadTasksAsMeddlingActions(connection); } // // Every time we connect, make sure any host with other_config[maintenance_mode] == true // is disabled. // CheckMaintenanceMode(connection); if (HelpersGUI.iSCSIisUsed()) HelpersGUI.PerformIQNCheck(); if(licenseTimer != null) licenseTimer.CheckActiveServerLicense(connection, false); if (Properties.Settings.Default.ShowHealthCheckEnrollmentReminder) ThreadPool.QueueUserWorkItem(CheckHealthCheckEnrollment, connection); ThreadPool.QueueUserWorkItem(HealthCheck.CheckForAnalysisResults, connection); ThreadPool.QueueUserWorkItem(InformHealthCheckEnrollment, connection); Updates.CheckServerPatches(); Updates.CheckServerVersion(); HealthCheck.SendMetadataToHealthCheck(); RequestRefreshTreeView(); } private void CheckHealthCheckEnrollment(object connection) { if (HealthCheckOverviewLauncher != null && !HiddenFeatures.HealthCheckHidden) HealthCheckOverviewLauncher.CheckHealthCheckEnrollment((IXenConnection) connection); } private void InformHealthCheckEnrollment(object connection) { Pool pool = Helpers.GetPoolOfOne((IXenConnection)connection); if (pool == null) return; var newHealthCheckSSettings = pool.HealthCheckSettings(); new TransferHealthCheckSettingsAction(pool, newHealthCheckSSettings, newHealthCheckSSettings.GetSecretyInfo(pool.Connection, HealthCheckSettings.UPLOAD_CREDENTIAL_USER_SECRET), newHealthCheckSSettings.GetSecretyInfo(pool.Connection, HealthCheckSettings.UPLOAD_CREDENTIAL_PASSWORD_SECRET), true).RunAsync(); } private static bool SameProductBrand(Host host) { var brand = host.ProductBrand(); return brand == Branding.PRODUCT_BRAND || brand == Branding.LEGACY_PRODUCT_BRAND || Branding.PRODUCT_BRAND == "[XenServer product]"; } /// /// Ensures all hosts on the connection are disabled if they are in maintenance mode. /// /// private void CheckMaintenanceMode(IXenConnection connection) { foreach (Host host in connection.Cache.Hosts) { CheckMaintenanceMode(host); } } /// /// Ensures the host is disabled if it is in maintenance mode by spawning a new HostAction if necessary. /// /// private void CheckMaintenanceMode(Host host) { if (host.IsLive() && host.MaintenanceMode() && host.enabled) { Program.MainWindow.CloseActiveWizards(host); var action = new DisableHostAction(host); action.Completed += action_Completed; action.RunAsync(); } } void MessageCollectionChanged(object sender, CollectionChangeEventArgs e) { Program.AssertOnEventThread(); XenAPI.Message m = (XenAPI.Message)e.Element; if (e.Action == CollectionChangeAction.Add) { if (!m.ShowOnGraphs() && !m.IsSquelched()) Alert.AddAlert(MessageAlert.ParseMessage(m)); } else if (e.Action == CollectionChangeAction.Remove) { if (!m.ShowOnGraphs()) MessageAlert.RemoveAlert(m); } } void CollectionChanged(object sender, CollectionChangeEventArgs e) where T : XenObject { Program.AssertOnEventThread(); T o = (T)e.Element; if (e.Action == CollectionChangeAction.Add) { if (o is Pool) ((Pool)e.Element).PropertyChanged += Pool_PropertyChanged; else if (o is Host) ((Host)e.Element).PropertyChanged += Host_PropertyChanged; else if (o is VM) ((VM)e.Element).PropertyChanged += VM_PropertyChanged; else o.PropertyChanged += o_PropertyChanged; } else if (e.Action == CollectionChangeAction.Remove) { if (o is Pool) ((Pool)e.Element).PropertyChanged -= Pool_PropertyChanged; else if (o is Host) ((Host)e.Element).PropertyChanged -= Host_PropertyChanged; else if (o is VM) ((VM)e.Element).PropertyChanged -= VM_PropertyChanged; else o.PropertyChanged -= o_PropertyChanged; if (o is VM) { VM vm = (VM)e.Element; ConsolePanel.closeVNCForSource(vm); CloseActiveWizards(vm); } selectedTabs.Remove(o); pluginManager.DisposeURLs(o); } } private void Pool_PropertyChanged(object obj, PropertyChangedEventArgs e) { Pool pool = (Pool)obj; switch (e.PropertyName) { case "other_config": // other_config may contain HideFromXenCenter UpdateToolbars(); // other_config contains which patches to ignore Updates.CheckServerPatches(); Updates.CheckServerVersion(); break; case "name_label": pool.Connection.FriendlyName = Helpers.GetName(pool); break; } } private void Host_PropertyChanged(object obj, PropertyChangedEventArgs e) { Host host = (Host)obj; switch (e.PropertyName) { case "allowed_operations": case "enabled": // We want to ensure that a host is disabled if it is in maintenance mode, by starting a new DisableHostAction if necessary (CheckMaintenanceMode) if (host.enabled && host.MaintenanceMode()) { // This is an invalid state: the host is enabled but still "in maintenance mode"; // But maybe MaintenanceMode hasn't been updated yet, because host.enabled being processed before host.other_config (CA-75625); // We'll check it again after the cache update operation is complete, in Connection_XenObjectsUpdated hostsInInvalidState.Add(host); } UpdateToolbars(); break; case "edition": case "license_server": case "license_params": UpdateHeader(); UpdateToolbars(); break; case "other_config": // other_config may contain HideFromXenCenter UpdateToolbars(); break; case "name_label": //check whether it's a standalone host if(Helpers.GetPool(host.Connection) == null) host.Connection.FriendlyName = Helpers.GetName(host); break; } } private void VM_PropertyChanged(object obj, PropertyChangedEventArgs e) { VM vm = (VM)obj; switch (e.PropertyName) { case "allowed_operations": case "is_a_template": case "resident_on": UpdateToolbars(); break; case "power_state": case "other_config": // other_config may contain HideFromXenCenter UpdateToolbars(); // Make all vms have the correct start times UpdateBodgedTime(vm, e.PropertyName); break; } } void o_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "allowed_operations": case "power_state": case "is_a_template": case "enabled": case "other_config": // other_config may contain HideFromXenCenter UpdateToolbars(); break; } } // Update bodged startup time if the powerstate goes to running (vm started from halted), otherconfig last shutdown changed (vm rebooted) or start time changed (occurs a few seconds after start) private void UpdateBodgedTime(VM vm, string p) { if (vm == null) return; if (p == "power_state") { vm.SetBodgeStartupTime(DateTime.UtcNow); // always newer than current bodge startup time } else if (p == "other_config" && vm.other_config.ContainsKey("last_shutdown_time")) { DateTime newTime = vm.LastShutdownTime(); if (newTime != DateTime.MinValue && newTime.Ticks > vm.GetBodgeStartupTime().Ticks) vm.SetBodgeStartupTime(newTime); // only update if is newer than current bodge startup time } } private void Connection_ConnectionResult(object sender, Network.ConnectionResultEventArgs e) { RequestRefreshTreeView(); } private void Connection_ConnectionClosed(object sender, EventArgs e) { RequestRefreshTreeView(); gc(); } // called whenever our connection with the Xen server fails (i.e., after we've successfully logged in) private void Connection_ConnectionLost(object sender, EventArgs e) { if (Program.Exiting) return; Program.Invoke(this, () => CloseActiveWizards((IXenConnection)sender)); RequestRefreshTreeView(); gc(); } private static void gc() { GC.Collect(); } void connection_ConnectionReconnecting(object sender, EventArgs e) { if (Program.Exiting) return; RequestRefreshTreeView(); gc(); } private List hostsInInvalidState = new List(); // called whenever Xen objects on the server change state void Connection_XenObjectsUpdated(object sender, EventArgs e) { if (Program.Exiting) return; IXenConnection connection = (IXenConnection) sender; if (hostsInInvalidState.Count > 0) { foreach (var host in hostsInInvalidState.Where(host => host.Connection == connection)) CheckMaintenanceMode(host); hostsInInvalidState.RemoveAll(host => host.Connection == connection); } RequestRefreshTreeView(); } /// /// Requests a refresh of the main tree view. The refresh will be managed such that we are not overloaded using an UpdateManager. /// public void RequestRefreshTreeView() { Program.Invoke(this, navigationPane.RequestRefreshTreeView); } void exitToolStripMenuItem_Click(object sender, EventArgs e) { this.Close(); } private bool _menuShortcutsEnabled = true; public bool MenuShortcutsEnabled { get { return _menuShortcutsEnabled; } set { if (value != _menuShortcutsEnabled) { //if the VNC Console is active (the user is typing into it etc) all of the shortcuts for XenCenter are disabled //IMPORTANT! add any shortcuts you want to pass to the VNC console into this if, else statement _menuShortcutsEnabled = value; // update the selection so menu items can enable/disable keyboard shortcuts as appropriate. SelectionManager.RefreshSelection(); } } } /// /// Must be called on the event thread. /// public void UpdateToolbars() { Program.AssertOnEventThread(); try { ToolStrip.SuspendLayout(); UpdateToolbarsCore(); MainMenuBar_MenuActivate(null, null); } finally { ToolStrip.ResumeLayout(); } // Save and restore focus on treeView, since selecting tabs in ChangeToNewTabs() has the // unavoidable side-effect of giving them focus - this is irritating if trying to navigate // the tree using the keyboard. navigationPane.SaveAndRestoreTreeViewFocus(ChangeToNewTabs); } private static int TOOLBAR_HEIGHT = 31; /// /// Updates the toolbar buttons. /// private void UpdateToolbarsCore() { // refresh the selection-manager SelectionManager.RefreshSelection(); ToolStrip.Height = ToolbarsEnabled ? TOOLBAR_HEIGHT : 0; ToolStrip.Enabled = ToolbarsEnabled; ShowToolbarMenuItem.Checked = toolbarToolStripMenuItem.Checked = ToolbarsEnabled; bool containerButtonsAvailable = startContainerToolStripButton.Enabled || stopContainerToolStripButton.Enabled || resumeContainerToolStripButton.Enabled || pauseContainerToolStripButton.Enabled || restartContainerToolStripButton.Enabled; startContainerToolStripButton.Available = containerButtonsAvailable && startContainerToolStripButton.Enabled; stopContainerToolStripButton.Available = containerButtonsAvailable && (stopContainerToolStripButton.Enabled || !startContainerToolStripButton.Available); resumeContainerToolStripButton.Available = containerButtonsAvailable && resumeContainerToolStripButton.Enabled; pauseContainerToolStripButton.Available = containerButtonsAvailable && (pauseContainerToolStripButton.Enabled || !resumeContainerToolStripButton.Available); restartContainerToolStripButton.Available = containerButtonsAvailable; powerOnHostToolStripButton.Available = powerOnHostToolStripButton.Enabled; startVMToolStripButton.Available = startVMToolStripButton.Enabled; shutDownToolStripButton.Available = shutDownToolStripButton.Enabled || (!startVMToolStripButton.Available && !powerOnHostToolStripButton.Available && !containerButtonsAvailable); RebootToolbarButton.Available = RebootToolbarButton.Enabled || !containerButtonsAvailable; resumeToolStripButton.Available = resumeToolStripButton.Enabled; SuspendToolbarButton.Available = SuspendToolbarButton.Enabled || (!resumeToolStripButton.Available && !containerButtonsAvailable); ForceRebootToolbarButton.Available = ((ForceVMRebootCommand)ForceRebootToolbarButton.Command).ShowOnMainToolBar; ForceShutdownToolbarButton.Available = ((ForceVMShutDownCommand)ForceShutdownToolbarButton.Command).ShowOnMainToolBar; } private List GetNewTabPages() { IXenConnection selectionConnection = SelectionManager.Selection.GetConnectionOfFirstItem(); Pool selectionPool = selectionConnection == null ? null : Helpers.GetPool(selectionConnection); // 'Home' tab is only visible if the 'Overview' tree node is selected, or if the tree is // empty (i.e. at startup). bool show_home = SelectionManager.Selection.Count == 1 && SelectionManager.Selection[0].Value == null; // The upsell pages use the first selected XenObject: but they're only shown if there is only one selected object (see calls to ShowTab() below). bool ha_upsell = Helpers.FeatureForbidden(SelectionManager.Selection.FirstAsXenObject, Host.RestrictHA) && (selectionPool != null && !selectionPool.ha_enabled); bool wlb_upsell = Helpers.FeatureForbidden(SelectionManager.Selection.FirstAsXenObject, Host.RestrictWLB); bool ad_upsell = Helpers.FeatureForbidden(SelectionManager.Selection.FirstAsXenObject, Host.RestrictAD); bool is_connected = selectionConnection != null && selectionConnection.IsConnected; bool multi = SelectionManager.Selection.Count > 1; bool isPoolSelected = SelectionManager.Selection.FirstIs(); bool isVMSelected = SelectionManager.Selection.FirstIs(); bool isHostSelected = SelectionManager.Selection.FirstIs(); bool isSRSelected = SelectionManager.Selection.FirstIs(); bool isVdiSelected = SelectionManager.Selection.FirstIs(); bool isRealVMSelected = SelectionManager.Selection.FirstIsRealVM; bool isTemplateSelected = SelectionManager.Selection.FirstIsTemplate; bool isHostLive = SelectionManager.Selection.FirstIsLiveHost; bool isDockerContainerSelected = SelectionManager.Selection.First is DockerContainer; bool hasManyControlDomains = isHostSelected && ((Host)SelectionManager.Selection.First).HasManyControlDomains(); bool selectedTemplateHasProvisionXML = SelectionManager.Selection.FirstIsTemplate && ((VM)SelectionManager.Selection[0].XenObject).HasProvisionXML(); var newTabs = new List(); if (!SearchMode && show_home) newTabs.Add(TabPageHome); if (!multi && !SearchMode && (isVMSelected || (isHostSelected && (isHostLive || !is_connected)) || isPoolSelected || isSRSelected || isVdiSelected || isDockerContainerSelected)) newTabs.Add(TabPageGeneral); if (!multi && !SearchMode && (isVMSelected || (isHostSelected && isHostLive) || isPoolSelected)) newTabs.Add(TabPageBallooning); if (!multi && !SearchMode && (isRealVMSelected || (isTemplateSelected && !selectedTemplateHasProvisionXML))) newTabs.Add(TabPageStorage); if (!multi && !SearchMode && isSRSelected) newTabs.Add(TabPageSR); if (!multi && !SearchMode && ((isHostSelected && isHostLive) || isPoolSelected)) newTabs.Add(TabPagePhysicalStorage); if (!multi && !SearchMode && (isVMSelected || (isHostSelected && isHostLive) || isPoolSelected)) newTabs.Add(TabPageNetwork); if (!multi && !SearchMode && isHostSelected && isHostLive) newTabs.Add(TabPageNICs); if (!multi && !SearchMode && isDockerContainerSelected && !(SelectionManager.Selection.First as DockerContainer).Parent.IsWindows()) newTabs.Add(TabPageDockerProcess); if (!multi && !SearchMode && isDockerContainerSelected) newTabs.Add(TabPageDockerDetails); bool isPoolOrLiveStandaloneHost = isPoolSelected || (isHostSelected && isHostLive && selectionPool == null); if (!multi && !SearchMode && ((isHostSelected && isHostLive) || isPoolOrLiveStandaloneHost) && !Helpers.FeatureForbidden(selectionConnection, Host.RestrictGpu)) newTabs.Add(TabPageGPU); if (!multi && !SearchMode && isHostSelected && isHostLive && ((Host)SelectionManager.Selection.First).PUSBs.Count > 0 && !Helpers.FeatureForbidden(selectionConnection, Host.RestrictUsbPassthrough)) newTabs.Add(TabPageUSB); var consoleFeatures = new List(); var otherFeatures = new List(); if (SelectionManager.Selection.Count == 1 && !SearchMode) GetFeatureTabPages(SelectionManager.Selection.FirstAsXenObject, out consoleFeatures, out otherFeatures); foreach (var f in consoleFeatures) newTabs.Add(f.TabPage); if (consoleFeatures.Count == 0 && !multi && !SearchMode && (isRealVMSelected || (isHostSelected && isHostLive))) newTabs.Add(TabPageConsole); if (consoleFeatures.Count == 0 && !multi && !SearchMode && isHostLive && hasManyControlDomains) newTabs.Add(TabPageCvmConsole); if (!multi && !SearchMode && (isRealVMSelected || (isHostSelected && isHostLive))) newTabs.Add(TabPagePeformance); if (!multi && !SearchMode && isPoolSelected) newTabs.Add(ha_upsell ? TabPageHAUpsell : TabPageHA); if(!multi && !SearchMode && isRealVMSelected) newTabs.Add(TabPageSnapshots); //Any Clearwater XenServer, or WLB is not licensed on XenServer, the WLB tab and any WLB menu items disappear completely. if (!wlb_upsell && !multi && !SearchMode && isPoolSelected) newTabs.Add(TabPageWLB); if (!multi && !SearchMode && (isPoolSelected || isHostSelected && isHostLive)) newTabs.Add(ad_upsell ? TabPageADUpsell : TabPageAD); if (!multi && !SearchMode && isPoolOrLiveStandaloneHost && !Helpers.FeatureForbidden(SelectionManager.Selection.FirstAsXenObject, Host.RestrictPvsCache) && Helpers.PvsCacheCapability(selectionConnection)) newTabs.Add(TabPagePvs); foreach (var f in otherFeatures) newTabs.Add(f.TabPage); newTabs.Add(TabPageSearch); // N.B. Change NewTabs definition if you add more tabs here. return newTabs; } private void GetFeatureTabPages(IXenObject xenObject, out List consoleFeatures, out List otherFeatures) { consoleFeatures = new List(); otherFeatures = new List(); var plugins = pluginManager.Plugins; foreach (var p in plugins) { var features = p.Features; foreach (var feature in features) { var f = feature as TabPageFeature; if (f == null) continue; f.SelectedXenObject = xenObject; if (!f.ShowTab) continue; if (f.IsConsoleReplacement) { f.SetUrl(); if (!f.IsError) consoleFeatures.Add(f); } else { var page = GetLastSelectedPage(xenObject); if (page != null && page.Tag == f) f.SetUrl(); otherFeatures.Add(f); } } } } private void ChangeToNewTabs() { var newTabs = GetNewTabPages(); var pageToSelect = GetLastSelectedPage(SelectionManager.Selection.First); if (pageToSelect != null && !newTabs.Contains(pageToSelect)) pageToSelect = null; TheTabControl.SuspendLayout(); IgnoreTabChanges = true; try { foreach (TabPage page in TheTabControl.TabPages) { if (!newTabs.Contains(page)) TheTabControl.TabPages.Remove(page); } int m = 0; // Index into TheTabControl.TabPages foreach (var newTab in newTabs) { var index = TheTabControl.TabPages.IndexOf(newTab); if (index < 0) TheTabControl.TabPages.Insert(m, newTab); m++; if (newTab == pageToSelect) TheTabControl.SelectedTab = newTab; } if (pageToSelect == null) TheTabControl.SelectedTab = TheTabControl.TabPages[0]; } finally { IgnoreTabChanges = false; TheTabControl.ResumeLayout(); SetLastSelectedPage(SelectionManager.Selection.First, TheTabControl.SelectedTab); } } private void SetLastSelectedPage(object o, TabPage p) { if (SearchMode) return; if (o == null || !Properties.Settings.Default.RememberLastSelectedTab) { selectedOverviewTab = p; } else { selectedTabs[o] = p; } } private TabPage GetLastSelectedPage(object o) { return o == null || !Properties.Settings.Default.RememberLastSelectedTab ? selectedOverviewTab : selectedTabs.ContainsKey(o) ? selectedTabs[o] : null; } private void pluginManager_PluginsChanged() { UpdateToolbars(); foreach (ToolStripMenuItem menu in MainMenuBar.Items) { //clear existing plugin items for (int i = menu.DropDownItems.Count - 1; i >= 0; i--) { CommandToolStripMenuItem commandMenuItem = menu.DropDownItems[i] as CommandToolStripMenuItem; if (commandMenuItem != null && (commandMenuItem.Command is MenuItemFeatureCommand || commandMenuItem.Command is ParentMenuItemFeatureCommand)) { menu.DropDownItems.RemoveAt(i); if (menu.DropDownItems.Count > 0 && menu.DropDownItems[i] is ToolStripSeparator) menu.DropDownItems.RemoveAt(i); } } // get insert index using the placeholder int insertIndex = pluginMenuItemStartIndexes[menu]; bool itemAdded = false; // add plugin items for this menu at insertIndex foreach (PluginDescriptor plugin in pluginManager.Plugins) { if (!plugin.Enabled) continue; foreach (Plugins.Feature feature in plugin.Features) { var menuItemFeature = feature as MenuItemFeature; if (menuItemFeature != null && menuItemFeature.ParentFeature == null && (int)menuItemFeature.Menu == MainMenuBar.Items.IndexOf(menu)) { Command cmd = menuItemFeature.GetCommand(this, SelectionManager.Selection); menu.DropDownItems.Insert(insertIndex, new CommandToolStripMenuItem(cmd)); insertIndex++; itemAdded = true; } var parentMenuItemFeature = feature as ParentMenuItemFeature; if (parentMenuItemFeature != null && (int)parentMenuItemFeature.Menu == MainMenuBar.Items.IndexOf(menu)) { Command cmd = parentMenuItemFeature.GetCommand(this, SelectionManager.Selection); CommandToolStripMenuItem parentMenuItem = new CommandToolStripMenuItem(cmd); menu.DropDownItems.Insert(insertIndex, parentMenuItem); insertIndex++; itemAdded = true; foreach (MenuItemFeature childFeature in parentMenuItemFeature.Features) { Command childCommand = childFeature.GetCommand(this, SelectionManager.Selection); parentMenuItem.DropDownItems.Add(new CommandToolStripMenuItem(childCommand)); } } } } if (itemAdded && insertIndex != menu.DropDownItems.Count) menu.DropDownItems.Insert(insertIndex, new ToolStripSeparator()); } } private void MainMenuBar_MenuActivate(object sender, EventArgs e) { bool vm = SelectionManager.Selection.FirstIsRealVM && !((VM)SelectionManager.Selection.First).Locked; exportSettingsToolStripMenuItem.Enabled = ConnectionsManager.XenConnectionsCopy.Count > 0; MenuShortcutsEnabled = true; startOnHostToolStripMenuItem.Available = startOnHostToolStripMenuItem.Enabled; resumeOnToolStripMenuItem.Available = resumeOnToolStripMenuItem.Enabled; relocateToolStripMenuItem.Available = relocateToolStripMenuItem.Enabled; sendCtrlAltDelToolStripMenuItem.Enabled = (TheTabControl.SelectedTab == TabPageConsole) && vm && ((VM)SelectionManager.Selection.First).power_state == vm_power_state.Running; IXenConnection conn = SelectionManager.Selection.GetConnectionOfAllItems(); if (SelectionManager.Selection.Count > 0 && (Helpers.GetMaster(conn) != null) && (Helpers.FalconOrGreater(conn))) { assignSnapshotScheduleToolStripMenuItem.Available = true; VMSnapshotScheduleToolStripMenuItem.Available = true; } else /* hide VMSS */ { assignSnapshotScheduleToolStripMenuItem.Available = false; VMSnapshotScheduleToolStripMenuItem.Available = false; } templatesToolStripMenuItem1.Checked = Properties.Settings.Default.DefaultTemplatesVisible; customTemplatesToolStripMenuItem.Checked = Properties.Settings.Default.UserTemplatesVisible; localStorageToolStripMenuItem.Checked = Properties.Settings.Default.LocalSRsVisible; ShowHiddenObjectsToolStripMenuItem.Checked = Properties.Settings.Default.ShowHiddenVMs; connectDisconnectToolStripMenuItem.Enabled = ConnectionsManager.XenConnectionsCopy.Count > 0; conversionToolStripMenuItem.Available = conn != null && conn.Cache.VMs.Any(v => v.IsConversionVM()); } private void xenSourceOnTheWebToolStripMenuItem_Click(object sender, EventArgs e) { Program.OpenURL(InvisibleMessages.HOMEPAGE); } private void xenCenterPluginsOnTheWebToolStripMenuItem_Click(object sender, EventArgs e) { Program.OpenURL(InvisibleMessages.PLUGINS_URL); } private void aboutXenSourceAdminToolStripMenuItem_Click(object sender, EventArgs e) { ShowForm(typeof(AboutDialog)); } /// /// Apply license, if HostAncestorOfSelectedNode is null, show host picker, if filepath == "" show filepicker /// public void LaunchLicensePicker(string filepath) { HelpersGUI.BringFormToFront(this); OpenFileDialog dialog = null; DialogResult result = DialogResult.Cancel; if (filepath == "") { if (!Program.RunInAutomatedTestMode) { dialog = new OpenFileDialog(); dialog.Multiselect = false; dialog.Title = Messages.INSTALL_LICENSE_KEY; dialog.CheckFileExists = true; dialog.CheckPathExists = true; dialog.Filter = string.Format("{0} (*.xslic)|*.xslic|{1} (*.*)|*.*", Messages.XS_LICENSE_FILES, Messages.ALL_FILES); dialog.ShowHelp = true; dialog.HelpRequest += new EventHandler(dialog_HelpRequest); result = dialog.ShowDialog(this); } } else { result = DialogResult.OK; } if (result == DialogResult.OK || Program.RunInAutomatedTestMode) { filepath = Program.RunInAutomatedTestMode ? "" : filepath == "" ? dialog.FileName : filepath; Host hostAncestor = SelectionManager.Selection.Count == 1 ? SelectionManager.Selection[0].HostAncestor : null; if (SelectionManager.Selection.Count == 1 && hostAncestor == null) { SelectHostDialog hostdialog = new SelectHostDialog(); hostdialog.TheHost = null; hostdialog.Owner = this; hostdialog.ShowDialog(this); if (string.IsNullOrEmpty(filepath) || hostdialog.DialogResult != DialogResult.OK) { return; } hostAncestor = hostdialog.TheHost; } DoLicenseAction(hostAncestor, filepath); } } private void DoLicenseAction(Host host, string filePath) { ApplyLicenseAction action = new ApplyLicenseAction(host.Connection, host, filePath); using (var actionProgress = new ActionProgressDialog(action, ProgressBarStyle.Marquee)) { actionProgress.Text = Messages.INSTALL_LICENSE_KEY; actionProgress.ShowDialog(this); } } private void dialog_HelpRequest(object sender, EventArgs e) { Help.HelpManager.Launch("LicenseKeyDialog"); } private void TheTabControl_Deselected(object sender, TabControlEventArgs e) { TabPage t = e.TabPage; if (t == null) return; BaseTabPage tabPage = t.Controls.OfType().FirstOrDefault(); if (tabPage != null) tabPage.PageHidden(); } /// /// /// If null, then we deduce the method was called by TreeView_AfterSelect /// and don't focus the VNC console. i.e. we only focus the VNC console if the user /// explicitly clicked on the console tab rather than arriving there by navigating /// in treeView. /// private void TheTabControl_SelectedIndexChanged(object sender, EventArgs e) { if (IgnoreTabChanges) return; TabPage t = TheTabControl.SelectedTab; if (!SearchMode) History.NewHistoryItem(new XenModelObjectHistoryItem(SelectionManager.Selection.FirstAsXenObject, t)); if (t == TabPageConsole) { if (SelectionManager.Selection.FirstIsRealVM) { ConsolePanel.setCurrentSource((VM)SelectionManager.Selection.First); UnpauseVNC(e != null && sender == TheTabControl); } else if (SelectionManager.Selection.FirstIs()) { ConsolePanel.setCurrentSource((Host)SelectionManager.Selection.First); UnpauseVNC(e != null && sender == TheTabControl); } ConsolePanel.UpdateRDPResolution(); } else if (t == TabPageCvmConsole) { if (SelectionManager.Selection.FirstIs()) { CvmConsolePanel.setCurrentSource((Host)SelectionManager.Selection.First); UnpauseVNC(e != null && sender == TheTabControl); } } else { ConsolePanel.PauseAllViews(); CvmConsolePanel.PauseAllViews(); // Start timer for closing the VNC connection after an interval (20 seconds) // when the console tab is not selected ConsolePanel.StartCloseVNCTimer(ConsolePanel.activeVNCView); CvmConsolePanel.StartCloseVNCTimer(CvmConsolePanel.activeVNCView); if (t == TabPageGeneral) { GeneralPage.XenObject = SelectionManager.Selection.FirstAsXenObject; } else if (t == TabPageBallooning) { BallooningPage.XenObject = SelectionManager.Selection.FirstAsXenObject; } else if (t == TabPageSR) { SrStoragePage.SR = SelectionManager.Selection.First as SR; } else if (t == TabPageNetwork) { NetworkPage.XenObject = SelectionManager.Selection.FirstAsXenObject; } else if (t == TabPageUSB) { UsbPage.XenObject = SelectionManager.Selection.FirstAsXenObject as Host; } else if (t == TabPageNICs) { NICPage.Host = SelectionManager.Selection.First as Host; } else if (t == TabPageStorage) { VMStoragePage.VM = SelectionManager.Selection.First as VM; } else if (t == TabPagePeformance) { PerformancePage.XenObject = SelectionManager.Selection.FirstAsXenObject; } else if (t == TabPageSearch && !SearchMode) { var rootNode = SelectionManager.Selection.RootNode; var rootNodeGrouping = rootNode == null ? null : rootNode.Tag as GroupingTag; var search = rootNode == null ? null : rootNode.Tag as Search; if (search != null) { SearchPage.Search = search; } else if (rootNodeGrouping != null) { var objectsView = rootNodeGrouping.Grouping as OrganizationViewObjects; var vappsView = rootNodeGrouping.Grouping as OrganizationViewVapps; var foldersView = rootNodeGrouping.Grouping as OrganizationViewFolders; if (vappsView != null) { SearchPage.Search = Search.SearchForVappGroup(rootNodeGrouping.Grouping, rootNodeGrouping.Parent, rootNodeGrouping.Group); } else if (objectsView != null) { //We are in Objects View GroupingTag gt = null; if (SelectionManager.Selection.Count == 1) { gt = SelectionManager.Selection.First as GroupingTag ?? SelectionManager.Selection[0].GroupAncestor; } else { //If multiple items have been selected we count the number of the grouping tags in the selection var selectedGroups = SelectionManager.Selection.Where(s => s.GroupingTag != null); //if exactly one grouping tag has been selected we show the search view for that one tag, but only if all the other items in the selection belong to this group/tag if (selectedGroups.Count() == 1) { var groupingTag = selectedGroups.First().GroupingTag; if (SelectionManager.Selection.Where(s => s.GroupingTag == null).All(s => s.GroupAncestor == groupingTag)) gt = groupingTag; else gt = null; } else { gt = SelectionManager.Selection.GroupAncestor; } } //if there has been a grouping tag determined above we use that //if not we show the search view for the root node if (gt != null) { SearchPage.Search = Search.SearchForNonVappGroup(gt.Grouping, gt.Parent, gt.Group); } else { SearchPage.Search = Search.SearchForNonVappGroup(rootNodeGrouping.Grouping, rootNodeGrouping.Parent, rootNodeGrouping.Group); } } else if (foldersView != null) { SearchPage.Search = Search.SearchForFolderGroup(rootNodeGrouping.Grouping, rootNodeGrouping.Parent, rootNodeGrouping.Group); } else { SearchPage.Search = Search.SearchForNonVappGroup(rootNodeGrouping.Grouping, rootNodeGrouping.Parent, rootNodeGrouping.Group); } } else { // Infrastructure View: // - In case of single selection or multiple selection within the same pool, // find the top-level parent (pool or standalone server) and show that search // - In case of multiple selection across pools or standalone servers, // or selection of the XenCenter node, or selection of a disconnected host, // show the default search. var connection = SelectionManager.Selection.GetConnectionOfAllItems(); if (connection == null) { SearchPage.XenObject = null; } else { var pool = Helpers.GetPool(connection); SearchPage.XenObject = pool ?? (IXenObject)Helpers.GetMaster(connection); } } } else if (t == TabPageHA) { HAPage.XenObject = SelectionManager.Selection.FirstAsXenObject; } else if (t == TabPageWLB) { WlbPage.Pool = SelectionManager.Selection.First as Pool; } else if (t == TabPageSnapshots) { snapshotPage.VM = SelectionManager.Selection.First as VM; } else if (t == TabPagePhysicalStorage) { PhysicalStoragePage.SetSelectionBroadcaster(SelectionManager, this); PhysicalStoragePage.Host = SelectionManager.Selection.First as Host; PhysicalStoragePage.Connection = SelectionManager.Selection.GetConnectionOfFirstItem(); } else if (t == TabPageAD) { AdPage.XenObject = SelectionManager.Selection.FirstAsXenObject; } else if (t == TabPageGPU) { GpuPage.XenObject = SelectionManager.Selection.FirstAsXenObject; } else if (t == TabPageDockerProcess) { DockerProcessPage.DockerContainer = SelectionManager.Selection.First as DockerContainer; } else if (t == TabPageDockerDetails) { DockerDetailsPage.DockerContainer = SelectionManager.Selection.First as DockerContainer; } else if (t == TabPagePvs) { PvsPage.Connection = SelectionManager.Selection.GetConnectionOfFirstItem(); } } if (t == TabPageSearch) SearchPage.PanelShown(); else SearchPage.PanelHidden(); if (t == TabPageDockerDetails) DockerDetailsPage.ResumeRefresh(); else DockerDetailsPage.PauseRefresh(); if (t == TabPageDockerProcess) DockerProcessPage.ResumeRefresh(); else DockerProcessPage.PauseRefresh(); if (t != null) SetLastSelectedPage(SelectionManager.Selection.First, t); UpdateTabePageFeatures(); } private void UpdateTabePageFeatures() { var plugins = pluginManager.Plugins; foreach (var p in plugins) { var features = p.Features; foreach (var feature in features) { var f = feature as TabPageFeature; if (f == null) continue; if (!f.ShowTab) continue; if (f.IsConsoleReplacement) { f.SetUrl(); continue; } var page = GetLastSelectedPage(f.SelectedXenObject); if (page != null && page.Tag == f) f.SetUrl(); } } } private void UnpauseVNC(bool focus) { ConsolePanel.UnpauseActiveView(); CvmConsolePanel.UnpauseActiveView(); if (focus) { ConsolePanel.FocusActiveView(); CvmConsolePanel.FocusActiveView(); ConsolePanel.SwitchIfRequired(); CvmConsolePanel.SwitchIfRequired(); } } /// /// The tabs that may be visible in the main GUI window. Used in SwitchToTab(). /// public enum Tab { Home, General, Storage, Network, Console, CvmConsole, Performance, NICs, SR, DockerProcess, DockerDetails, USB, Search } public void SwitchToTab(Tab tab) { switch (tab) { case Tab.Home: TheTabControl.SelectedTab = TabPageHome; break; case Tab.General: TheTabControl.SelectedTab = TabPageGeneral; break; case Tab.Storage: TheTabControl.SelectedTab = TabPageStorage; break; case Tab.Network: TheTabControl.SelectedTab = TabPageNetwork; break; case Tab.Console: TheTabControl.SelectedTab = TabPageConsole; break; case Tab.CvmConsole: TheTabControl.SelectedTab = TabPageCvmConsole; break; case Tab.Performance: TheTabControl.SelectedTab = TabPagePeformance; break; case Tab.NICs: TheTabControl.SelectedTab = TabPageNICs; break; case Tab.SR: TheTabControl.SelectedTab = TabPageSR; break; case Tab.DockerProcess: TheTabControl.SelectedTab = TabPageDockerProcess; break; case Tab.DockerDetails: TheTabControl.SelectedTab = TabPageDockerDetails; break; case Tab.USB: TheTabControl.SelectedTab = TabPageUSB; break; case Tab.Search: TheTabControl.SelectedTab = TabPageSearch; break; default: throw new NotImplementedException(); } } private void templatesToolStripMenuItem1_Click(object sender, EventArgs e) { templatesToolStripMenuItem1.Checked = !templatesToolStripMenuItem1.Checked; Properties.Settings.Default.DefaultTemplatesVisible = templatesToolStripMenuItem1.Checked; ViewSettingsChanged(); } private void customTemplatesToolStripMenuItem_Click(object sender, EventArgs e) { customTemplatesToolStripMenuItem.Checked = !customTemplatesToolStripMenuItem.Checked; Properties.Settings.Default.UserTemplatesVisible = customTemplatesToolStripMenuItem.Checked; ViewSettingsChanged(); } private void localStorageToolStripMenuItem_Click(object sender, EventArgs e) { localStorageToolStripMenuItem.Checked = !localStorageToolStripMenuItem.Checked; Properties.Settings.Default.LocalSRsVisible = localStorageToolStripMenuItem.Checked; ViewSettingsChanged(); } private void ShowHiddenObjectsToolStripMenuItem_Click(object sender, EventArgs e) { ShowHiddenObjectsToolStripMenuItem.Checked = !ShowHiddenObjectsToolStripMenuItem.Checked; Properties.Settings.Default.ShowHiddenVMs = ShowHiddenObjectsToolStripMenuItem.Checked; ViewSettingsChanged(); } private void ViewSettingsChanged() { Settings.TrySaveSettings(); navigationPane.UpdateSearch(); RequestRefreshTreeView(); } private void EditSelectedNodeInTreeView() { navigationPane.EditSelectedNode(); } protected override void OnClosing(CancelEventArgs e) { bool currentTasks = false; foreach (ActionBase a in ConnectionsManager.History) { if (a is MeddlingAction || a.IsCompleted) continue; currentTasks = true; break; } if (currentTasks) { e.Cancel = true; if (Program.RunInAutomatedTestMode || new Dialogs.WarningDialogs.CloseXenCenterWarningDialog().ShowDialog(this) == DialogResult.OK) { this.Hide(); // Close all open forms List
forms = new List(); foreach (Form form in Application.OpenForms) { if (form != this) { forms.Add(form); } } foreach (Form form in forms) { form.Close(); } // Disconnect the named pipe Program.DisconnectPipe(); foreach (ActionBase a in ConnectionsManager.History) { if(!Program.RunInAutomatedTestMode) { if (a is AsyncAction) { AsyncAction aa = (AsyncAction) a; aa.PrepareForLogReloadAfterRestart(); } if (!a.IsCompleted && a.CanCancel && !a.SafeToExit) a.Cancel(); } else { if (!a.IsCompleted && a.CanCancel) a.Cancel(); } } ThreadPool.QueueUserWorkItem(CloseWhenActionsCanceled); } return; } // Disconnect the named pipe Program.DisconnectPipe(); Properties.Settings.Default.WindowSize = this.Size; Properties.Settings.Default.WindowLocation = this.Location; try { Settings.SaveServerList(); Properties.Settings.Default.Save(); } catch (ConfigurationErrorsException ex) { using (var dlg = new ThreeButtonDialog( new ThreeButtonDialog.Details( SystemIcons.Error, string.Format(Messages.MESSAGEBOX_SAVE_CORRUPTED, Settings.GetUserConfigPath()), Messages.MESSAGEBOX_SAVE_CORRUPTED_TITLE))) { dlg.ShowDialog(this); } log.Error("Couldn't save settings"); log.Error(ex, ex); } base.OnClosing(e); } private void sendCtrlAltDelToolStripMenuItem_Click(object sender, EventArgs e) { ConsolePanel.SendCAD(); } #region IMainWindowCommandInterface Members /// /// Closes all per-Connection and per-VM wizards for the given connection. /// /// public void CloseActiveWizards(IXenConnection connection) { Program.Invoke(Program.MainWindow, delegate { // Close and remove any active wizards for any VMs foreach (VM vm in connection.Cache.VMs) { CloseActiveWizards(vm); } closeActivePoolWizards(connection); }); } /// /// Closes all per-Connection wizards. /// /// private void closeActivePoolWizards(IXenConnection connection) { IList wizards; if (activePoolWizards.TryGetValue(connection, out wizards)) { foreach (var wizard in wizards) { if (!wizard.IsDisposed) { wizard.Close(); } } activePoolWizards.Remove(connection); } } /// /// Closes all per-XenObject wizards. /// /// public void CloseActiveWizards(IXenObject obj) { Program.Invoke(Program.MainWindow, delegate { Form wizard; if (activeXenModelObjectWizards.TryGetValue(obj, out wizard)) { if (!wizard.IsDisposed) { wizard.Close(); } activeXenModelObjectWizards.Remove(obj); } }); } /// /// Show the given wizard, and impose a one-wizard-per-XenObject limit. /// /// The relevant VM /// The new wizard to show public void ShowPerXenModelObjectWizard(IXenObject obj, Form wizard) { CloseActiveWizards(obj); activeXenModelObjectWizards.Add(obj, wizard); wizard.Show(this); } /// /// Show the given wizard, and impose a one-wizard-per-connection limit. /// /// The connection. May be null, in which case the wizard /// is not added to any dictionary. This should happen iff this is the New Pool Wizard. /// The new wizard to show. May not be null. /// The form owning the wizard to be launched. public void ShowPerConnectionWizard(IXenConnection connection, Form wizard, Form parentForm = null) { if (connection != null) { if (activePoolWizards.ContainsKey(connection)) { var w = activePoolWizards[connection].FirstOrDefault(x => x.GetType() == wizard.GetType()); if (w != null && !w.IsDisposed) { if (w.WindowState == FormWindowState.Minimized) { w.WindowState = FormWindowState.Normal; } w.Focus(); return; } if (w != null && w.IsDisposed) activePoolWizards[connection].Remove(w); } //closeActivePoolWizards(connection); if (activePoolWizards.ContainsKey(connection)) activePoolWizards[connection].Add(wizard); else activePoolWizards.Add(connection, new List() { wizard }); } if (!wizard.Disposing && !wizard.IsDisposed && !Program.Exiting) { wizard.Show(parentForm ?? this); } } /// /// Shows a form of the specified type if it has already been created. If the form doesn't exist yet /// it is created first and then shown. /// /// The type of the form to be shown. public Form ShowForm(Type type) { return ShowForm(type, null); } /// /// Shows a form of the specified type if it has already been created. If the form doesn't exist yet /// it is created first and then shown. /// /// The type of the form to be shown. /// The arguments to pass to the form's consructor public Form ShowForm(Type type, object[] args) { foreach (Form form in Application.OpenForms) { if (form.GetType() == type) { HelpersGUI.BringFormToFront(form); return form; } } Form newForm = (Form)Activator.CreateInstance(type, args); newForm.Show(this); return newForm; } public Form Form { get { return this; } } public void Invoke(MethodInvoker method) { Program.Invoke(this, method); } public bool SelectObjectInTree(IXenObject xenObject) { return SelectObject(xenObject); } public Collection GetXenConnectionsCopy() { return new Collection(ConnectionsManager.XenConnectionsCopy); } public void SaveServerList() { Settings.SaveServerList(); } public bool RunInAutomatedTestMode { get { return Program.RunInAutomatedTestMode; } } public void RemoveConnection(IXenConnection connection) { ConnectionsManager.ClearCacheAndRemoveConnection(connection); } public void PutSelectedNodeIntoEditMode() { EditSelectedNodeInTreeView(); } public void TrySelectNewObjectInTree(Predicate tagMatch, bool selectNode, bool expandNode, bool ensureNodeVisible) { TrySelectNewNode(tagMatch, selectNode, expandNode, ensureNodeVisible); } public void TrySelectNewObjectInTree(IXenConnection c, bool selectNode, bool expandNode, bool ensureNodeVisible) { TrySelectNewNode(c, selectNode, expandNode, ensureNodeVisible); } #endregion #region Help private string TabHelpID() { string modelObj = getSelectedXenModelObjectType(); if (TheTabControl.SelectedTab == TabPageHome) return "TabPageHome" + modelObj; if (TheTabControl.SelectedTab == TabPageSearch) return "TabPageSearch" + modelObj; if (TheTabControl.SelectedTab == TabPageConsole) return "TabPageConsole" + modelObj; if (TheTabControl.SelectedTab == TabPageCvmConsole) return "TabPageCvmConsole" + modelObj; if (TheTabControl.SelectedTab == TabPageGeneral) return "TabPageSettings" + modelObj; if (TheTabControl.SelectedTab == TabPagePhysicalStorage || TheTabControl.SelectedTab == TabPageStorage || TheTabControl.SelectedTab == TabPageSR) return "TabPageStorage" + modelObj; if (TheTabControl.SelectedTab == TabPageNetwork) return "TabPageNetwork" + modelObj; if (TheTabControl.SelectedTab == TabPageNICs) return "TabPageNICs" + modelObj; if (TheTabControl.SelectedTab == TabPageWLB) return "TabPageWLB" + modelObj; if (TheTabControl.SelectedTab == TabPagePeformance) return "TabPagePerformance" + modelObj; if (TheTabControl.SelectedTab == TabPageHA) return "TabPageHA" + modelObj; if (TheTabControl.SelectedTab == TabPageHAUpsell) return "TabPageHAUpsell" + modelObj; if (TheTabControl.SelectedTab == TabPageSnapshots) return "TabPageSnapshots" + modelObj; if (TheTabControl.SelectedTab == TabPageBallooning) return "TabPageBallooning" + modelObj; if (TheTabControl.SelectedTab == TabPageAD) return "TabPageAD" + modelObj; if (TheTabControl.SelectedTab == TabPageADUpsell) return "TabPageADUpsell" + modelObj; if (TheTabControl.SelectedTab == TabPageWLBUpsell) return "TabPageWLBUpsell"; if (TheTabControl.SelectedTab == TabPageGPU) return "TabPageGPU" + modelObj; if (TheTabControl.SelectedTab == TabPageDockerProcess) return "TabPageDockerProcess" + modelObj; if (TheTabControl.SelectedTab == TabPageDockerDetails) return "TabPageDockerDetails" + modelObj; if (TheTabControl.SelectedTab == TabPagePvs) return "TabPagePvs" + modelObj; if (TheTabControl.SelectedTab == TabPageUSB) return "TabPageUSB" + modelObj; return "TabPageUnknown"; } private string getSelectedXenModelObjectType() { // for now, since there are few topics which depend on the selected object we shall just check the special cases // when more topic are added we can just return the ModelObjectName if (TheTabControl.SelectedTab == TabPageGeneral && SelectionManager.Selection.First is VM) { return "VM"; } if (TheTabControl.SelectedTab == TabPagePhysicalStorage || TheTabControl.SelectedTab == TabPageStorage || TheTabControl.SelectedTab == TabPageSR) { if (SelectionManager.Selection.FirstIs()) return "Pool"; if (SelectionManager.Selection.FirstIs()) return "Server"; if (SelectionManager.Selection.FirstIs()) return "VM"; if (SelectionManager.Selection.FirstIs()) return "Storage"; } if (TheTabControl.SelectedTab == TabPageNetwork) { if (SelectionManager.Selection.FirstIs()) return "Server"; if (SelectionManager.Selection.FirstIs()) return "VM"; } return ""; } public void ShowHelpTOC() { ShowHelpTopic(null); } public void ShowHelpTopic(string topicID) { var helpTopicUrl = HelpManager.ProduceUrl( topicID, InvisibleMessages.HELP_URL, InvisibleMessages.LOCALE, $"{Branding.XENCENTER_VERSION}.{Program.Version.Revision}", "ui_link", Messages.XENCENTER); if (!string.IsNullOrEmpty(helpTopicUrl)) Program.OpenURL(helpTopicUrl); // record help usage Properties.Settings.Default.HelpLastUsed = DateTime.UtcNow.ToString("u"); Settings.TrySaveSettings(); } public void MainWindow_HelpRequested(object sender, HelpEventArgs hlpevent) { // CA-28064. MessageBox hack to kill the hlpevent it passes to MainWindows. if (Program.MainWindow.ContainsFocus && MenuShortcutsEnabled) LaunchHelp(); } private void helpTopicsToolStripMenuItem_Click(object sender, EventArgs e) { ShowHelpTOC(); } private void helpContextMenuItem_Click(object sender, EventArgs e) { LaunchHelp(); } private void LaunchHelp() { if (alertPage.Visible) Help.HelpManager.Launch("AlertSummaryDialog"); else if (updatesPage.Visible) Help.HelpManager.Launch("ManageUpdatesDialog"); else if (eventsPage.Visible) Help.HelpManager.Launch("EventsPane"); else { if (TheTabControl.SelectedTab.Tag is TabPageFeature && ((TabPageFeature)TheTabControl.SelectedTab.Tag).HasHelp) ((TabPageFeature)TheTabControl.SelectedTab.Tag).LaunchHelp(); else Help.HelpManager.Launch(TabHelpID()); } } public bool HasHelp() { return Help.HelpManager.HasHelpFor(TabHelpID()); } private void viewApplicationLogToolStripMenuItem_Click(object sender, EventArgs e) { Program.ViewLogFiles(); } #endregion /// /// Used to select the pool or standalone host node for the specified connection which is about to appear in the tree. /// /// The connection. /// if set to true then the pool/standalone host node will be selected. /// if set to true then the pool/standalone host node will be expanded. /// if set to true then the matched node will be made visible. public void TrySelectNewNode(IXenConnection connection, bool selectNode, bool expandNode, bool ensureNodeVisible) { if (connection != null) { TrySelectNewNode(delegate(object o) { if (o == null) { return false; } else if (o.Equals(Helpers.GetPool(connection))) { return true; } Host[] hosts = connection.Cache.Hosts; return hosts.Length > 0 && o.Equals(hosts[0]); }, selectNode, expandNode, ensureNodeVisible); } } /// /// Used to select or expand a node which is about to appear in the tree. This is used so that new hosts, folders, pools /// etc. can be picked and then selected/expanded. /// /// It fires off a new thread and then repeatedly tries to select a node which matches the specified match /// delegate. It stops if it times out or is successful. /// /// A match for the tag of the node. /// if set to true then the matched node will be selected. /// if set to true then the matched node will be expanded. /// if set to true then the matched node will be made visible. public void TrySelectNewNode(Predicate tagMatch, bool selectNode, bool expandNode, bool ensureNodeVisible) { ThreadPool.QueueUserWorkItem(delegate { bool success = false; for (int i = 0; i < 20 && !success; i++) { Program.Invoke(Program.MainWindow, delegate { success = navigationPane.TryToSelectNewNode(tagMatch, selectNode, expandNode, ensureNodeVisible); }); Thread.Sleep(500); } }); } /// /// Selects the specified object in the treeview. /// /// The object to be selected. /// A value indicating whether selection was successful. public bool SelectObject(IXenObject o) { return navigationPane.SelectObject(o); } private void eventsPage_GoToXenObjectRequested(IXenObject obj) { navigationPane.SwitchToInfrastructureMode(); navigationPane.SelectObject(obj); } private void Updates_CollectionChanged(object sender, CollectionChangeEventArgs e) { Program.Invoke(this, () => { int updatesCount = Updates.UpdateAlertsCount; navigationPane.UpdateNotificationsButton(NotificationsSubMode.Updates, updatesCount); if (updatesPage.Visible) { TitleLabel.Text = NotificationsSubModeItem.GetText(NotificationsSubMode.Updates, updatesCount); TitleIcon.Image = NotificationsSubModeItem.GetImage(NotificationsSubMode.Updates, updatesCount); } }); } private void CloseWhenActionsCanceled(object o) { int i = 0; while (true) { if (i > 20) Program.ForcedExiting = true; if (i > 40 || AllActionsCompleted()) { Program.Invoke(this, Application.Exit); break; } i++; System.Threading.Thread.Sleep(500); } } private bool AllActionsCompleted() { foreach (ActionBase a in ConnectionsManager.History) { if (!a.IsCompleted) return false; } return true; } private void preferencesToolStripMenuItem_Click(object sender, EventArgs e) { OptionsDialog dialog = new OptionsDialog(pluginManager); dialog.ShowDialog(this); } internal void action_Completed(ActionBase sender) { if (Program.Exiting) return; RequestRefreshTreeView(); } private void OpenGlobalImportWizard(string param) { HelpersGUI.BringFormToFront(this); Host hostAncestor = SelectionManager.Selection.Count == 1 ? SelectionManager.Selection[0].HostAncestor : null; new ImportWizard(SelectionManager.Selection.GetConnectionOfFirstItem(), hostAncestor, param, false).Show(); } private void InstallUpdate(string path) { if (WizardHelpers.IsValidFile(path, out var failureReason)) { var wizard = new PatchingWizard(); wizard.Show(this); wizard.NextStep(); wizard.AddFile(path); } else using (var popup = new ThreeButtonDialog(new ThreeButtonDialog.Details( SystemIcons.Error, failureReason, Messages.UPDATES))) { popup.ShowDialog(); } } #region XenSearch private bool searchMode; /// /// SearchMode doesn't just mean we are looking at the Search tab. /// It's set when we import a search from a file; or when we double-click /// on a folder or tag name to search for it. /// private bool SearchMode { get { return searchMode; } set { if (searchMode == value) return; searchMode = value; navigationPane.InSearchMode = value; UpdateToolbars(); } } public bool DoSearch(string filename) { List searches = Search.LoadFile(filename); if (searches != null && searches.Count > 0) { Program.Invoke(Program.MainWindow, delegate() { DoSearch(searches[0]); }); return true; } return false; } public void DoSearch(Search search) { History.NewHistoryItem(new SearchHistoryItem(search)); SearchMode = true; SearchPage.Search = search; UpdateHeader(); } public void SearchForTag(string tag) { DoSearch(Search.SearchForTag(tag)); } public void SearchForFolder(string path) { DoSearch(Search.SearchForFolder(path)); } void SearchPanel_SearchChanged() { if (SearchMode) History.ReplaceHistoryItem(new SearchHistoryItem(SearchPage.Search)); else History.ReplaceHistoryItem(new ModifiedSearchHistoryItem( SelectionManager.Selection.FirstAsXenObject, SearchPage.Search)); } /// /// Updates the shiny gradient bar with selected object name and icon. /// Also updates 'Logged in as:'. /// private void UpdateHeader() { if (navigationPane.currentMode == NavigationPane.NavigationMode.Notifications) return; var licenseColor = Program.TitleBarForeColor; var licenseText = string.Empty; if (SearchMode && SearchPage.Search != null) { TitleLabel.Text = HelpersGUI.GetLocalizedSearchName(SearchPage.Search); TitleIcon.Image = Images.GetImage16For(SearchPage.Search); } else if (!SearchMode && SelectionManager.Selection.ContainsOneItemOfType()) { IXenObject xenObject = SelectionManager.Selection[0].XenObject; TitleLabel.Text = xenObject.NameWithLocation(); TitleIcon.Image = Images.GetImage16For(xenObject); licenseText = GetLicenseStatusText(xenObject, out licenseColor); // When in folder view only show the logged in label if it is clear to which connection the object belongs (most likely pools and hosts) if (SelectionManager.Selection[0].PoolAncestor == null && SelectionManager.Selection[0].HostAncestor == null) loggedInLabel1.Connection = null; else loggedInLabel1.Connection = xenObject.Connection; } else { TitleLabel.Text = Messages.XENCENTER; TitleIcon.Image = Properties.Resources.Logo; loggedInLabel1.Connection = null; } LicenseStatusTitleLabel.Text = licenseText; LicenseStatusTitleLabel.ForeColor = licenseColor; SetTitleLabelMaxWidth(); } private string GetLicenseStatusText(IXenObject xenObject, out Color foreColor) { foreColor = Program.TitleBarForeColor; var pool = xenObject as Pool; if (pool != null && pool.Connection != null && pool.Connection.IsConnected && pool.Connection.CacheIsPopulated) { if (pool.IsFreeLicenseOrExpired()) { foreColor = Color.Red; return Messages.MAINWINDOW_HEADER_UNLICENSED; } return string.Format(Messages.MAINWINDOW_HEADER_LICENSED_WITH, Helpers.GetFriendlyLicenseName(pool)); } var host = xenObject as Host; if (host != null && host.Connection != null && host.Connection.IsConnected && host.Connection.CacheIsPopulated) { if (host.IsFreeLicenseOrExpired()) { foreColor = Color.Red; return Messages.MAINWINDOW_HEADER_UNLICENSED; } return string.Format(Messages.MAINWINDOW_HEADER_LICENSED_WITH, Helpers.GetFriendlyLicenseName(host)); } return string.Empty; } private void SetTitleLabelMaxWidth() { TitleLabel.MaximumSize = new Size(tableLayoutPanel1.Width - loggedInLabel1.Width - LicenseStatusTitleLabel.Width - 6, TitleLabel.Height); } private void UpdateViewMenu(NavigationPane.NavigationMode mode) { //the order is the reverse from the order in which we want them to appear var items = new ToolStripItem [] { toolStripSeparator24, ShowHiddenObjectsToolStripMenuItem, localStorageToolStripMenuItem, templatesToolStripMenuItem1, customTemplatesToolStripMenuItem }; if (mode == NavigationPane.NavigationMode.Infrastructure) { foreach (var item in items) { if (!viewToolStripMenuItem.DropDownItems.Contains(item)) viewToolStripMenuItem.DropDownItems.Insert(0, item); } } else if (mode == NavigationPane.NavigationMode.Notifications) { foreach (var item in items) viewToolStripMenuItem.DropDownItems.Remove(item); } else { for (int i = 2; i < items.Length; i++) viewToolStripMenuItem.DropDownItems.Remove(items[i]); for (int i = 0; i < 2; i++) if (!viewToolStripMenuItem.DropDownItems.Contains(items[i])) viewToolStripMenuItem.DropDownItems.Insert(0, items[i]); } pluginMenuItemStartIndexes[viewToolStripMenuItem] = viewToolStripMenuItem.DropDownItems.IndexOf(toolStripSeparator24) + 1; } void navigationPane_DragDropCommandActivated(string cmdText) { SetStatusBar(null, cmdText); } private void navigationPane_TreeViewSelectionChanged() { UpdateToolbars(); // // NB do not trigger updates to the panels in this method // instead, put them in TheTabControl_SelectedIndexChanged, // so only the selected tab is updated // TheTabControl_SelectedIndexChanged(null, EventArgs.Empty); if (TheTabControl.SelectedTab != null) TheTabControl.SelectedTab.Refresh(); UpdateHeader(); } private void navigationPane_NotificationsSubModeChanged(NotificationsSubModeItem submodeItem) { switch (submodeItem.SubMode) { case NotificationsSubMode.Alerts: if (updatesPage.Visible) updatesPage.HidePage(); if (eventsPage.Visible) eventsPage.HidePage(); alertPage.ShowPage(); break; case NotificationsSubMode.Updates: if (alertPage.Visible) alertPage.HidePage(); if (eventsPage.Visible) eventsPage.HidePage(); updatesPage.ShowPage(); break; case NotificationsSubMode.Events: if (alertPage.Visible) alertPage.HidePage(); if (updatesPage.Visible) updatesPage.HidePage(); eventsPage.ShowPage(); break; } TheTabControl.Visible = false; loggedInLabel1.Connection = null; TitleLabel.Text = submodeItem.Text; TitleIcon.Image = submodeItem.Image; } private void navigationPane_NavigationModeChanged(NavigationPane.NavigationMode mode) { if (mode == NavigationPane.NavigationMode.Notifications) { LicenseStatusTitleLabel.Text = string.Empty; TheTabControl.Visible = false; } else { bool tabControlWasVisible = TheTabControl.Visible; TheTabControl.Visible = true; if (alertPage.Visible) alertPage.HidePage(); if (updatesPage.Visible) updatesPage.HidePage(); if (eventsPage.Visible) eventsPage.HidePage(); // force an update of the selected tab when switching back from Notification view, // as some tabs ignore the update events when not visible (e.g. Snapshots, HA) if (!tabControlWasVisible) TheTabControl_SelectedIndexChanged(null, null); } UpdateViewMenu(mode); } private void navigationPane_TreeNodeBeforeSelected() { SearchMode = false; } private void navigationPane_TreeNodeClicked() { if (SearchMode) { SearchMode = false; TheTabControl_SelectedIndexChanged(null, null); UpdateHeader(); } } private void navigationPane_TreeNodeRightClicked() { MainMenuBar_MenuActivate(MainMenuBar, new EventArgs()); } private void navigationPane_TreeViewRefreshed() { // This is required to update search results when things change. if (TheTabControl.SelectedTab == TabPageGeneral) GeneralPage.BuildList(); else if (TheTabControl.SelectedTab == TabPageSearch) SearchPage.BuildList(); UpdateHeader(); UpdateToolbars(); } #endregion void XenCenterAlerts_CollectionChanged(object sender, CollectionChangeEventArgs e) { Program.BeginInvoke(Program.MainWindow, () => { navigationPane.UpdateNotificationsButton( NotificationsSubMode.Alerts, Alert.NonDismissingAlertCount); if (alertPage.Visible) { TitleLabel.Text = NotificationsSubModeItem.GetText(NotificationsSubMode.Alerts, Alert.NonDismissingAlertCount); TitleIcon.Image = NotificationsSubModeItem.GetImage(NotificationsSubMode.Alerts, Alert.NonDismissingAlertCount); } }); } private void backButton_Click(object sender, EventArgs e) { History.Back(1); } private void forwardButton_Click(object sender, EventArgs e) { History.Forward(1); } private void backButton_DropDownOpening(object sender, EventArgs e) { ToolStripSplitButton button = sender as ToolStripSplitButton; if (button == null) return; History.PopulateBackDropDown(button); } private void forwardButton_DropDownOpening(object sender, EventArgs e) { ToolStripSplitButton button = sender as ToolStripSplitButton; if (button == null) return; History.PopulateForwardDropDown(button); } private void LicenseManagerMenuItem_Click(object sender, EventArgs e) { licenseManagerLauncher.LaunchIfRequired(false, ConnectionsManager.XenConnections, SelectionManager.Selection); } private void MainWindow_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.F5) { RequestRefreshTreeView(); if (TheTabControl.SelectedTab == TabPageSearch) SearchPage.PanelProd(); } } private void ShowToolbarMenuItem_Click(object sender, EventArgs e) { ToolbarsEnabled = !ToolbarsEnabled; Properties.Settings.Default.ToolbarsEnabled = ToolbarsEnabled; UpdateToolbars(); } private void MainMenuBar_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { ToolBarContextMenu.Show(Program.MainWindow, e.Location); } } /// /// Equivalent to MainController.Confirm(conn, this, msg, args). /// public bool Confirm(IXenConnection conn, string title, string msg, params object[] args) { return Confirm(conn, this, title, msg, args); } /// /// Show a MessageBox asking to confirm an operation. The MessageBox will be parented to the given Control. /// Displays default "Yes" and "No" buttons ("Yes" button is selected by default). /// The args given will be ellipsised to Helpers.DEFAULT_NAME_TRIM_LENGTH, if they are strings. /// If in automated test mode, then always returns true. /// If the user refuses the operation, then returns false. /// If the given connection has disconnected in the time it takes the user to confirm, /// then shows an information MessageBox, and returns false. /// Otherwise, the user has agreed and the connection is still alive, so /// sets MainWindow.AllowHistorySwitch to true and returns true. /// public static bool Confirm(IXenConnection conn, Control parent, string title, string msg, params object[] args) { return Confirm(conn, parent, title, null, null, null, msg, args); } /// /// Show a MessageBox asking to confirm an operation. The MessageBox will be parented to the given Control. /// "Yes" and "No" buttons can be customized. /// The args given will be ellipsised to Helpers.DEFAULT_NAME_TRIM_LENGTH, if they are strings. /// If in automated test mode, then always returns true. /// If the user refuses the operation, then returns false. /// If the given connection has disconnected in the time it takes the user to confirm, /// then shows an information MessageBox, and returns false. /// Otherwise, the user has agreed and the connection is still alive, so /// sets MainWindow.AllowHistorySwitch to true and returns true. /// public static bool Confirm(IXenConnection conn, Control parent, string title, string helpName, ThreeButtonDialog.TBDButton buttonYes, ThreeButtonDialog.TBDButton buttonNo, string msg, params object[] args) { if (Program.RunInAutomatedTestMode) return true; Trim(args); var buttons = new[] { buttonYes ?? ThreeButtonDialog.ButtonYes, buttonNo ?? ThreeButtonDialog.ButtonNo }; var details = new ThreeButtonDialog.Details(SystemIcons.Exclamation, args.Length == 0 ? msg : string.Format(msg, args), title); DialogResult dialogResult; using (var dialog = String.IsNullOrEmpty(helpName) ? new ThreeButtonDialog(details, buttons) : new ThreeButtonDialog(details, helpName, buttons)) { dialogResult = dialog.ShowDialog(parent ?? Program.MainWindow); } if (dialogResult != DialogResult.Yes) return false; if (conn != null && !conn.IsConnected) { ShowDisconnectedMessage(parent); return false; } return true; } /// /// Show a message telling the user that the connection has disappeared. We check this after /// we've shown a dialog, in case it's happened in the time it took them to click OK. /// public static void ShowDisconnectedMessage(Control parent) { // We could have done some teardown by now, so we need to be paranoid about things going away // beneath us. if (Program.Exiting) return; if (parent == null || parent.Disposing || parent.IsDisposed) { parent = Program.MainWindow; if (parent.Disposing || parent.IsDisposed) return; } using (var dlg = new ThreeButtonDialog( new ThreeButtonDialog.Details( SystemIcons.Warning, Messages.DISCONNECTED_BEFORE_ACTION_STARTED, Messages.XENCENTER))) { dlg.ShowDialog(parent); } } private static void Trim(object[] args) { int n = args.Length; for (int i = 0; i < n; i++) { if (args[i] is string) args[i] = ((string)args[i]).Ellipsise(Helpers.DEFAULT_NAME_TRIM_LENGTH); } } #region ISynchronizeInvoke Members // this explicit implementation of ISynchronizeInvoke is used to allow the model to update // its API on the main program thread while being decoupled from MainWindow. IAsyncResult ISynchronizeInvoke.BeginInvoke(Delegate method, object[] args) { return Program.BeginInvoke(this, method, args); } object ISynchronizeInvoke.EndInvoke(IAsyncResult result) { return EndInvoke(result); } object ISynchronizeInvoke.Invoke(Delegate method, object[] args) { return Program.Invoke(this, method, args); } bool ISynchronizeInvoke.InvokeRequired { get { return InvokeRequired; } } #endregion private void importSettingsToolStripMenuItem_Click(object sender, EventArgs e) { using (OpenFileDialog dialog = new OpenFileDialog()) { dialog.Filter = Messages.XENCENTER_CONFIG_FILTER; if (dialog.ShowDialog(this) != DialogResult.Cancel) { try { log.InfoFormat("Importing server list from '{0}'", dialog.FileName); XmlDocument xmlDocument = new XmlDocument(); using (var stream = dialog.OpenFile()) xmlDocument.Load(stream); foreach (XmlNode itemConnection in xmlDocument.GetElementsByTagName("XenConnection")) { var conn = new XenConnection(); foreach (XmlNode item in itemConnection.ChildNodes) { switch (item.Name) { case "Hostname": conn.Hostname = item.InnerText; break; case "Port": conn.Port = int.Parse(item.InnerText); break; case "FriendlyName": conn.FriendlyName = item.InnerText; break; } } if (null == ConnectionsManager.XenConnections.Find(existing => (existing.Hostname == conn.Hostname && existing.Port == conn.Port))) ConnectionsManager.XenConnections.Add(conn); RequestRefreshTreeView(); } log.InfoFormat("Imported server list from '{0}' successfully.", dialog.FileName); } catch (XmlException) { log.ErrorFormat("Failed to import server list from '{0}'", dialog.FileName); using (var dlg = new ThreeButtonDialog( new ThreeButtonDialog.Details(SystemIcons.Error, Messages.ERRO_IMPORTING_SERVER_LIST, Messages.XENCENTER))) { dlg.ShowDialog(this); } } } } } private void exportSettingsToolStripMenuItem_Click(object sender, EventArgs e) { using (SaveFileDialog dialog = new SaveFileDialog()) { dialog.Filter = Messages.XENCENTER_CONFIG_FILTER; dialog.Title = Messages.ACTION_SAVE_CHANGES_IN_PROGRESS; dialog.CheckPathExists = true; if (dialog.ShowDialog(this) != DialogResult.Cancel) { log.InfoFormat("Exporting server list to '{0}'", dialog.FileName); try { using (var xmlWriter = new XmlTextWriter(dialog.OpenFile(), Encoding.Unicode)) { xmlWriter.WriteStartDocument(); xmlWriter.WriteStartElement("XenConnections"); xmlWriter.WriteWhitespace("\n"); foreach (var connection in ConnectionsManager.XenConnections) { xmlWriter.WriteStartElement("XenConnection"); { xmlWriter.WriteElementString("Hostname", connection.Hostname); xmlWriter.WriteElementString("Port", connection.Port.ToString()); xmlWriter.WriteWhitespace("\n "); xmlWriter.WriteElementString("FriendlyName", connection.FriendlyName); } xmlWriter.WriteEndElement(); xmlWriter.WriteWhitespace("\n"); } xmlWriter.WriteEndElement(); xmlWriter.WriteEndDocument(); } log.InfoFormat("Exported server list to '{0}' successfully.", dialog.FileName); } catch { log.ErrorFormat("Failed to export server list to '{0}'.", dialog.FileName); throw; } } } } private void MainWindow_Load(object sender, EventArgs e) { SetSplitterDistance(); } FormWindowState lastState = FormWindowState.Normal; private void MainWindow_Resize(object sender, EventArgs e) { TabPage t = TheTabControl.SelectedTab; if (t == TabPageConsole) { if (WindowState != lastState && WindowState != FormWindowState.Minimized) { lastState = WindowState; ConsolePanel.UpdateRDPResolution(); } mainWindowResized = true; } SetSplitterDistance(); SetTitleLabelMaxWidth(); } private void SetSplitterDistance() { //CA-71697: chosen min size so the tab contents are visible int chosenPanel2MinSize = MinimumSize.Width * 3 / 5; int min = splitContainer1.Panel1MinSize; int max = splitContainer1.Width - chosenPanel2MinSize; if (max < min) return; splitContainer1.Panel2MinSize = chosenPanel2MinSize; if (splitContainer1.SplitterDistance < min) splitContainer1.SplitterDistance = min; else if (splitContainer1.SplitterDistance > max) splitContainer1.SplitterDistance = max; } private void MainWindow_ResizeEnd(object sender, EventArgs e) { TabPage t = TheTabControl.SelectedTab; if (t == TabPageConsole) { if (mainWindowResized) ConsolePanel.UpdateRDPResolution(); mainWindowResized = false; } } private void splitContainer1_SplitterMoved(object sender, SplitterEventArgs e) { TabPage t = TheTabControl.SelectedTab; if (t == TabPageConsole) ConsolePanel.UpdateRDPResolution(); SetTitleLabelMaxWidth(); } } }