using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Net; using System.Net.NetworkInformation; using System.Threading; using System.Windows.Forms; using Microsoft.VisualBasic; using Tesla.Net; using TeslaConsole.Properties; namespace TeslaConsole; public class SiteManagement : Form { private class PodData { public Pod Pod; public PodInfo Conn; public string Squad; public PingReply PingReply; public int pings; public Size? Install_CustomResolution; public List Install_HostTypes = new List(); public bool OperationInProgress; public int PercentProgress = -1; public string Status = ""; public Exception Error; private SiteManagement Parent; private Thread PingThread; private bool Running = true; public PodData(Pod pod, string squad, SiteManagement parent) { Pod = pod; Squad = squad; if (pod.IsLocal) { Conn = new PodInfo(pod); Conn.ConnectCompleted += parent.PodInfo_ConnectCompleted; Conn.InstallProductCompleted += parent.PodInfo_InstallProductCompleted; Conn.UninstallProductCompleted += parent.PodInfo_UninstallProductCompleted; } Parent = parent; PingThread = new Thread(Ping); PingThread.Name = pod.Name + " - Ping Thread"; PingThread.IsBackground = true; } public void StartPing() { PingThread.Start(); } public void StopPing() { Running = false; } private void Ping() { Ping ping = new Ping(); while (Running) { if (!OperationInProgress) { PingReply = ping.Send(Pod.IPAddress, 5000); pings++; Parent.Pod_PingCompleted(this); } Thread.Sleep(1000); } } } private enum PodInstallType { GameClient, LiveCamera, MissionReview } private IContainer components; private DataGridView dgvPods; private ContextMenuStrip cmnuDataGrid; private ToolStripMenuItem installProductToolStripMenuItem; private ToolStripMenuItem importSiteConfigurationToolStripMenuItem; private ToolStripMenuItem exportSiteConfigurationToolStripMenuItem; private OpenFileDialog dlgOpenFile; private SaveFileDialog dlgSaveFile; private OpenFileDialog dlgSelectPackage; private FlowLayoutPanel flpPodsToConfigure; private ToolStripMenuItem deletePodToolStripMenuItem; private ToolStripMenuItem resendPodConfigurationToolStripMenuItem; private DataGridViewCheckBoxColumn colIsLocal; private DataGridViewTextBoxColumn colPodName; private DataGridViewComboBoxColumn colSquad; private DataGridViewComboBoxColumn colType; private DataGridViewTextBoxColumn colMacAddress; private DataGridViewTextBoxColumn colIPAddress; private DataGridViewTextBoxColumn colSubnet; private DataGridViewTextBoxColumn colGateway; private DataGridViewTextBoxColumn colDNSServer; private DataGridViewTextBoxColumn colHostName; private DataGridViewTextBoxColumn colOperationProgress; private ToolStripMenuItem uninstallProductToolStripMenuItem; public static readonly Guid RPAppGuid = new Guid("7D241B1F-AB6D-4e08-9C20-12294E743D94"); public static readonly Guid RPMRAppGuid = new Guid("8F71D5C2-38E4-413c-8E22-88CAD08774D2"); public static readonly Guid RPLCAppGuid = new Guid("57A0B3C2-D5CF-46d6-ABED-A8F4A26AB086"); public static readonly Guid MW4AppGuid = new Guid("CC8500ED-A653-45a7-BEF8-C332D30371A6"); public static readonly Guid MW4LCAppGuid = new Guid("8EE93A6C-F16A-49be-B867-37FAE9087FFF"); private Site mSite; private List mPods = new List(); private PodData mPodInEdit; private int mRowInEdit = -1; private Dictionary mReconfiguringPods = new Dictionary(); private Dictionary mConfigureButtons = new Dictionary(); private Dictionary mConfigureLabels = new Dictionary(); private static bool mIsOpen = false; public static bool IsOpen => mIsOpen; protected override void Dispose(bool disposing) { if (disposing && components != null) { components.Dispose(); } base.Dispose(disposing); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle = new System.Windows.Forms.DataGridViewCellStyle(); this.dgvPods = new System.Windows.Forms.DataGridView(); this.colIsLocal = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.colPodName = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colSquad = new System.Windows.Forms.DataGridViewComboBoxColumn(); this.colType = new System.Windows.Forms.DataGridViewComboBoxColumn(); this.colMacAddress = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colIPAddress = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colSubnet = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colGateway = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colDNSServer = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colHostName = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colOperationProgress = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.cmnuDataGrid = new System.Windows.Forms.ContextMenuStrip(this.components); this.deletePodToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.installProductToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.uninstallProductToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.resendPodConfigurationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.importSiteConfigurationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exportSiteConfigurationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.dlgOpenFile = new System.Windows.Forms.OpenFileDialog(); this.dlgSaveFile = new System.Windows.Forms.SaveFileDialog(); this.dlgSelectPackage = new System.Windows.Forms.OpenFileDialog(); this.flpPodsToConfigure = new System.Windows.Forms.FlowLayoutPanel(); ((System.ComponentModel.ISupportInitialize)this.dgvPods).BeginInit(); this.cmnuDataGrid.SuspendLayout(); base.SuspendLayout(); this.dgvPods.AllowUserToResizeRows = false; dataGridViewCellStyle.BackColor = System.Drawing.Color.FromArgb(225, 225, 255); this.dgvPods.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle; this.dgvPods.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; this.dgvPods.BackgroundColor = System.Drawing.Color.White; this.dgvPods.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.dgvPods.ClipboardCopyMode = System.Windows.Forms.DataGridViewClipboardCopyMode.Disable; this.dgvPods.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dgvPods.Columns.AddRange(this.colIsLocal, this.colPodName, this.colSquad, this.colType, this.colMacAddress, this.colIPAddress, this.colSubnet, this.colGateway, this.colDNSServer, this.colHostName, this.colOperationProgress); this.dgvPods.ContextMenuStrip = this.cmnuDataGrid; this.dgvPods.Location = new System.Drawing.Point(12, 12); this.dgvPods.Name = "dgvPods"; this.dgvPods.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; this.dgvPods.Size = new System.Drawing.Size(1106, 395); this.dgvPods.TabIndex = 0; this.dgvPods.VirtualMode = true; this.dgvPods.UserDeletingRow += new System.Windows.Forms.DataGridViewRowCancelEventHandler(dgvPods_UserDeletingRow); this.dgvPods.CellMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(dgvPods_CellMouseClick); this.dgvPods.CancelRowEdit += new System.Windows.Forms.QuestionEventHandler(dgvPods_CancelRowEdit); this.dgvPods.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(dgvPods_CellValueNeeded); this.dgvPods.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(dgvPods_CellFormatting); this.dgvPods.CellValidating += new System.Windows.Forms.DataGridViewCellValidatingEventHandler(dgvPods_CellValidating); this.dgvPods.RowValidated += new System.Windows.Forms.DataGridViewCellEventHandler(dgvPods_RowValidated); this.dgvPods.CellPainting += new System.Windows.Forms.DataGridViewCellPaintingEventHandler(dgvPods_CellPainting); this.dgvPods.CellValuePushed += new System.Windows.Forms.DataGridViewCellValueEventHandler(dgvPods_CellValuePushed); this.dgvPods.NewRowNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(dgvPods_NewRowNeeded); this.colIsLocal.HeaderText = "Local"; this.colIsLocal.MinimumWidth = 40; this.colIsLocal.Name = "colIsLocal"; this.colIsLocal.ReadOnly = true; this.colIsLocal.Resizable = System.Windows.Forms.DataGridViewTriState.False; this.colIsLocal.Width = 40; this.colPodName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colPodName.HeaderText = "Pod"; this.colPodName.MinimumWidth = 100; this.colPodName.Name = "colPodName"; this.colSquad.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colSquad.HeaderText = "Squad"; this.colSquad.MinimumWidth = 75; this.colSquad.Name = "colSquad"; this.colSquad.Width = 75; this.colType.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colType.HeaderText = "Type"; this.colType.MinimumWidth = 100; this.colType.Name = "colType"; this.colMacAddress.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colMacAddress.HeaderText = "MAC Address"; this.colMacAddress.MinimumWidth = 75; this.colMacAddress.Name = "colMacAddress"; this.colMacAddress.ReadOnly = true; this.colMacAddress.Width = 88; this.colIPAddress.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colIPAddress.HeaderText = "IP Address"; this.colIPAddress.MinimumWidth = 75; this.colIPAddress.Name = "colIPAddress"; this.colIPAddress.ReadOnly = true; this.colIPAddress.Width = 77; this.colSubnet.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colSubnet.HeaderText = "Subnet Mask"; this.colSubnet.MinimumWidth = 75; this.colSubnet.Name = "colSubnet"; this.colSubnet.ReadOnly = true; this.colSubnet.Width = 88; this.colGateway.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colGateway.HeaderText = "Default Gateway"; this.colGateway.MinimumWidth = 75; this.colGateway.Name = "colGateway"; this.colGateway.ReadOnly = true; this.colGateway.Width = 102; this.colDNSServer.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colDNSServer.HeaderText = "DNS Server"; this.colDNSServer.MinimumWidth = 75; this.colDNSServer.Name = "colDNSServer"; this.colDNSServer.ReadOnly = true; this.colDNSServer.Width = 82; this.colHostName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colHostName.HeaderText = "Host Name"; this.colHostName.MinimumWidth = 75; this.colHostName.Name = "colHostName"; this.colHostName.ReadOnly = true; this.colHostName.Width = 79; this.colOperationProgress.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.colOperationProgress.HeaderText = "Operation Progress"; this.colOperationProgress.MinimumWidth = 150; this.colOperationProgress.Name = "colOperationProgress"; this.colOperationProgress.ReadOnly = true; this.colOperationProgress.Resizable = System.Windows.Forms.DataGridViewTriState.True; this.colOperationProgress.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; this.cmnuDataGrid.Items.AddRange(new System.Windows.Forms.ToolStripItem[6] { this.deletePodToolStripMenuItem, this.installProductToolStripMenuItem, this.uninstallProductToolStripMenuItem, this.resendPodConfigurationToolStripMenuItem, this.importSiteConfigurationToolStripMenuItem, this.exportSiteConfigurationToolStripMenuItem }); this.cmnuDataGrid.Name = "cmnuDataGrid"; this.cmnuDataGrid.Size = new System.Drawing.Size(219, 136); this.cmnuDataGrid.Opening += new System.ComponentModel.CancelEventHandler(cmnuDataGrid_Opening); this.deletePodToolStripMenuItem.Image = TeslaConsole.Properties.Resources.DeleteHS; this.deletePodToolStripMenuItem.Name = "deletePodToolStripMenuItem"; this.deletePodToolStripMenuItem.Size = new System.Drawing.Size(218, 22); this.deletePodToolStripMenuItem.Text = "Delete"; this.deletePodToolStripMenuItem.Click += new System.EventHandler(deletePodToolStripMenuItem_Click); this.installProductToolStripMenuItem.Image = TeslaConsole.Properties.Resources.install; this.installProductToolStripMenuItem.Name = "installProductToolStripMenuItem"; this.installProductToolStripMenuItem.Size = new System.Drawing.Size(218, 22); this.installProductToolStripMenuItem.Text = "Install Product"; // Install Product children are built dynamically from AppRegistry (see BuildInstallProductMenu). this.uninstallProductToolStripMenuItem.Name = "uninstallProductToolStripMenuItem"; this.uninstallProductToolStripMenuItem.Size = new System.Drawing.Size(218, 22); this.uninstallProductToolStripMenuItem.Text = "Uninstall Product"; this.resendPodConfigurationToolStripMenuItem.Name = "resendPodConfigurationToolStripMenuItem"; this.resendPodConfigurationToolStripMenuItem.Size = new System.Drawing.Size(218, 22); this.resendPodConfigurationToolStripMenuItem.Text = "Reconfigure..."; this.resendPodConfigurationToolStripMenuItem.Visible = false; this.resendPodConfigurationToolStripMenuItem.Click += new System.EventHandler(resendPodConfigurationToolStripMenuItem_Click); this.importSiteConfigurationToolStripMenuItem.Image = TeslaConsole.Properties.Resources.openHS; this.importSiteConfigurationToolStripMenuItem.Name = "importSiteConfigurationToolStripMenuItem"; this.importSiteConfigurationToolStripMenuItem.Size = new System.Drawing.Size(218, 22); this.importSiteConfigurationToolStripMenuItem.Text = "Import Site Configuration..."; this.importSiteConfigurationToolStripMenuItem.Click += new System.EventHandler(importSiteConfigurationToolStripMenuItem_Click); this.exportSiteConfigurationToolStripMenuItem.Image = TeslaConsole.Properties.Resources.saveHS; this.exportSiteConfigurationToolStripMenuItem.Name = "exportSiteConfigurationToolStripMenuItem"; this.exportSiteConfigurationToolStripMenuItem.Size = new System.Drawing.Size(218, 22); this.exportSiteConfigurationToolStripMenuItem.Text = "Export Site Configuration..."; this.exportSiteConfigurationToolStripMenuItem.Click += new System.EventHandler(exportSiteConfigurationToolStripMenuItem_Click); this.dlgOpenFile.DefaultExt = "siteconfig"; this.dlgOpenFile.Filter = "Site Configuration Files (*.siteconfig)|*.siteconfig|All Files (*.*)|*.*"; this.dlgOpenFile.Title = "Import Configuration"; this.dlgSaveFile.DefaultExt = "siteconfig"; this.dlgSaveFile.Filter = "Site Configuration Files (*.siteconfig)|*.siteconfig|All Files (*.*)|*.*"; this.dlgSaveFile.Title = "Export Configuration"; this.dlgSelectPackage.Filter = "VWE Install Archive (*.zip)|*.zip|All Files (*.*)|*.*"; this.dlgSelectPackage.Title = "Select Package to Install"; this.flpPodsToConfigure.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; this.flpPodsToConfigure.Location = new System.Drawing.Point(12, 413); this.flpPodsToConfigure.Name = "flpPodsToConfigure"; this.flpPodsToConfigure.Size = new System.Drawing.Size(1106, 27); this.flpPodsToConfigure.TabIndex = 1; base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; base.ClientSize = new System.Drawing.Size(1130, 452); base.Controls.Add(this.flpPodsToConfigure); base.Controls.Add(this.dgvPods); base.Name = "SiteManagement"; base.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Site Management"; base.Load += new System.EventHandler(SiteManagement_Load); base.FormClosing += new System.Windows.Forms.FormClosingEventHandler(SiteManagement_FormClosing); ((System.ComponentModel.ISupportInitialize)this.dgvPods).EndInit(); this.cmnuDataGrid.ResumeLayout(false); base.ResumeLayout(false); } public SiteManagement() { InitializeComponent(); LoadConfiguration(TeslaConsole.Site.Active); BuildInstallProductMenu(); foreach (string activeConfigRequest in PodManager.ActiveConfigRequests) { PodManager_PodReadyForConfig(activeConfigRequest, PodManager.GetMacAddressForRequest(activeConfigRequest)); } PodManager.PodReadyForConfig += PodManager_PodReadyForConfig; PodManager.PodConfigured += PodManager_PodConfigured; } protected override void OnClosing(CancelEventArgs e) { if (dgvPods.IsCurrentCellInEditMode) { MessageBox.Show("You can not close this window while a cell is being edited.", "Currently Editing", MessageBoxButtons.OK); e.Cancel = true; } base.OnClosing(e); } private void PodManager_PodReadyForConfig(string requestId, byte[] macAddress) { if (base.InvokeRequired) { Invoke(new PodManager.PodReadyConfigHandler(PodManager_PodReadyForConfig), requestId, macAddress); return; } PodData podData = null; foreach (PodData mPod in mPods) { if (mPod.Pod.MacAddressEquals(macAddress)) { podData = mPod; break; } } Button button = new Button(); button.Image = Resources.PodOffline16; if (podData == null) { button.Text = "Configure " + requestId; button.Tag = new Tuple(requestId, macAddress); } else { button.Text = "Configure " + podData.Pod.Name; button.Tag = new Tuple(requestId, podData); } button.ImageAlign = ContentAlignment.MiddleLeft; button.TextAlign = ContentAlignment.MiddleLeft; button.TextImageRelation = TextImageRelation.ImageBeforeText; button.AutoSize = true; button.AutoSizeMode = AutoSizeMode.GrowAndShrink; button.Click += ConfigureButton_Click; flpPodsToConfigure.Controls.Add(button); } private void PodManager_PodConfigured(string requestId, byte[] macAddress) { if (mConfigureButtons.ContainsKey(requestId)) { Button value = mConfigureButtons[requestId]; mConfigureButtons.Remove(requestId); flpPodsToConfigure.Controls.Remove(value); } } private void ConfigureButton_Click(object sender, EventArgs e) { Button button = sender as Button; if (button.Tag is Tuple) { Tuple tuple = button.Tag as Tuple; string text = Interaction.InputBox("Enter passphrase for " + tuple.B.Pod.Name, "Configure " + tuple.B.Pod.Name); if (!string.IsNullOrEmpty(text)) { Label label = new Label(); label.Text = "Configuring " + tuple.B.Pod.Name + "..."; label.AutoSize = false; label.Size = new Size((int)((double)button.Width * 1.5), button.Height); label.BorderStyle = BorderStyle.FixedSingle; label.BackColor = Color.SkyBlue; label.TextAlign = ContentAlignment.MiddleLeft; flpPodsToConfigure.Controls.Remove(button); mConfigureButtons.Remove(tuple.A); flpPodsToConfigure.Controls.Add(label); mConfigureLabels.Add(tuple.B.Pod.ID, label); // Reconfiguring an already-known pod (e.g. after a launcher reinstall): // pause its ping/connect loop and drop any stale connection to its old IP, // so the console isn't racing SecureConfig for that address. Resumed when // configuration completes (see ConfigureWorker_RunWorkerCompleted). tuple.B.OperationInProgress = true; tuple.B.Conn?.Disconnect(); ConfigurePod(tuple.A, text.ToUpper(), tuple.B.Pod, tuple.B.Squad); } } else { if (!(button.Tag is Tuple)) { return; } Tuple tuple2 = button.Tag as Tuple; Pod pod = new Pod(); pod.MacAddress = tuple2.B; SetupPod setupPod; string text2; while (true) { setupPod = new SetupPod(tuple2.A, pod, TeslaConsole.Site.Active.Squads); if (setupPod.ShowDialog(this) == DialogResult.OK) { Label label2 = new Label(); label2.Text = "Configuring " + pod.Name + "..."; label2.AutoSize = false; label2.Size = new Size((int)((double)button.Width * 1.5), button.Height); label2.BorderStyle = BorderStyle.FixedSingle; label2.BackColor = Color.SlateBlue; flpPodsToConfigure.Controls.Remove(button); mConfigureButtons.Remove(tuple2.A); flpPodsToConfigure.Controls.Add(label2); mConfigureLabels.Add(pod.ID, label2); text2 = setupPod.Squad.Name; if (!(setupPod.Squad.Guid == Guid.Empty)) { break; } text2 = Interaction.InputBox("Enter the name of the new squad:", "New Squad"); if (!string.IsNullOrEmpty(text2)) { Squad squad = new Squad(setupPod.Squad.Name); TeslaConsole.Site.Active.Squads.Add(squad); colSquad.Items.Add(squad.Name); break; } } } ConfigurePod(tuple2.A, setupPod.Passphrase, setupPod.Pod, text2); } } private void ConfigurePod(string requestId, string passphrase, Pod pod, string squadName) { BackgroundWorker backgroundWorker = new BackgroundWorker(); backgroundWorker.DoWork += ConfigureWorker_DoWork; backgroundWorker.RunWorkerCompleted += ConfigureWorker_RunWorkerCompleted; backgroundWorker.RunWorkerAsync(new Tuple(requestId, passphrase, pod, squadName)); } private void ConfigureWorker_DoWork(object sender, DoWorkEventArgs e) { Tuple tuple = (Tuple)e.Argument; tuple.C.Key = PodManager.ConfigurePod(tuple.A, tuple.B, tuple.C); if (tuple.C.Key == null) { e.Result = new PodConfigurationException("Passphrase was incorrect.", tuple.C); } else { e.Result = new Tuple(tuple.C, tuple.D); } } private void ConfigureWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Result is PodConfigurationException) { PodConfigurationException ex = e.Result as PodConfigurationException; MessageBox.Show(this, "There was an error configuring " + ex.Pod.Name + ":" + Environment.NewLine + ex.Message, "Configure " + ex.Pod.Name, MessageBoxButtons.OK, MessageBoxIcon.Hand); Label value = mConfigureLabels[ex.Pod.ID]; flpPodsToConfigure.Controls.Remove(value); mConfigureLabels.Remove(ex.Pod.ID); // If this was a known pod, resume its ping/connect loop (paused before SecureConfig). foreach (PodData mPod in mPods) { if (mPod.Pod.ID == ex.Pod.ID) { mPod.OperationInProgress = false; break; } } } else { if (!(e.Result is Tuple)) { return; } Tuple tuple = e.Result as Tuple; MessageBox.Show(this, tuple.A.Name + " successfully configured!", "Configure " + tuple.A.Name, MessageBoxButtons.OK, MessageBoxIcon.Asterisk); Label value2 = mConfigureLabels[tuple.A.ID]; flpPodsToConfigure.Controls.Remove(value2); mConfigureLabels.Remove(tuple.A.ID); foreach (PodData mPod in mPods) { if (mPod.Pod.ID == tuple.A.ID) { // Existing pod reconfigured: resume its ping/connect loop so the console // reconnects with the new session key (paused before SecureConfig). mPod.OperationInProgress = false; return; } } PodData podData = new PodData(tuple.A, tuple.B, this); podData.StartPing(); mPods.Add(podData); List list = new List(mPods); dgvPods.Rows.Clear(); mPods.Clear(); mPods = list; dgvPods.RowCount = mPods.Count + 1; } } private void SiteManagement_Load(object sender, EventArgs e) { mIsOpen = true; } private void SiteManagement_FormClosing(object sender, FormClosingEventArgs e) { foreach (PodData mPod in mPods) { if (e.CloseReason == CloseReason.UserClosing && mPod.OperationInProgress && mPod.Status.StartsWith("Installing")) { MessageBox.Show(this, "Site Management cannot be closed while installations are in progress.", "Site Management", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); e.Cancel = true; return; } } PodManager.PodReadyForConfig -= PodManager_PodReadyForConfig; foreach (Squad squad in mSite.Squads) { squad.Pods.Clear(); } foreach (PodData mPod2 in mPods) { mPod2.StopPing(); foreach (Squad squad2 in mSite.Squads) { if (squad2.Name == mPod2.Squad) { squad2.Pods.Add(mPod2.Pod); } } } mSite.Save(); mIsOpen = false; } private void dgvPods_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (colOperationProgress.Index != e.ColumnIndex || e.RowIndex < 0 || e.RowIndex >= mPods.Count || (string)e.FormattedValue != "draw_progress") { return; } e.Handled = true; e.PaintBackground(e.ClipBounds, cellsPaintSelectionBackground: false); PodData podData = mPods[e.RowIndex]; Rectangle rectangle = Rectangle.Inflate(e.CellBounds, -3, -3); Rectangle rectangle2 = new Rectangle(rectangle.Location, rectangle.Size); rectangle2.Inflate(-1, -1); if (podData.PercentProgress >= 0) { rectangle2.Width = (int)Math.Round((double)podData.PercentProgress / 100.0 * (double)rectangle2.Width); if (ProgressBarRenderer.IsSupported) { ProgressBarRenderer.DrawHorizontalBar(e.Graphics, rectangle); ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, rectangle2); } else { e.Graphics.FillRectangle(Brushes.LightGray, rectangle); e.Graphics.FillRectangle(Brushes.Green, rectangle2); e.Graphics.DrawRectangle(Pens.Black, rectangle); } } StringFormat stringFormat = new StringFormat(StringFormatFlags.NoWrap); stringFormat.Alignment = StringAlignment.Near; stringFormat.LineAlignment = StringAlignment.Center; e.Graphics.DrawString(podData.Status, Font, Brushes.Black, rectangle, stringFormat); } private void dgvPods_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (e.RowIndex < 0) { return; } if (colMacAddress.Index == e.ColumnIndex) { if (e.Value is byte[] array) { string[] array2 = new string[array.Length]; for (int i = 0; i < array2.Length; i++) { array2[i] = array[i].ToString("x2"); } e.Value = string.Join("-", array2); e.FormattingApplied = true; } } else { if (colOperationProgress.Index != e.ColumnIndex) { return; } if (e.RowIndex < mPods.Count && mPods[e.RowIndex].Pod.IsLocal) { PodData podData = mPods[e.RowIndex]; if (podData.Error != null) { e.Value = "Install Failed"; e.CellStyle.BackColor = Color.Red; e.CellStyle.ForeColor = Color.White; } else if (!podData.OperationInProgress) { if (podData.PingReply == null) { e.Value = "Idle. [pinging...]"; } else if (podData.PingReply.Status == IPStatus.Success) { if (!podData.Conn.IsConnected) { e.Value = "Could not connect to pod service."; } else { e.Value = $"Idle [{podData.PingReply.RoundtripTime} ms]"; } } else { e.Value = $"Pod unreachable. Ping result: {podData.PingReply.Status}"; } } else { e.Value = "draw_progress"; } } else { e.Value = ""; } e.FormattingApplied = true; } } private void dgvPods_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { if (e.ColumnIndex < 0 || e.RowIndex < 0 || e.RowIndex >= dgvPods.Rows.Count) { return; } DataGridViewRow dataGridViewRow = dgvPods.Rows[e.RowIndex]; if (e.ColumnIndex >= dataGridViewRow.Cells.Count) { return; } DataGridViewCell dataGridViewCell = dataGridViewRow.Cells[e.ColumnIndex]; if (e.ColumnIndex == colPodName.Index) { if (!dgvPods.Rows[e.RowIndex].IsNewRow && (e.FormattedValue == null || (string)e.FormattedValue == "")) { dataGridViewCell.ErrorText = "Pod name cannot be blank"; } else { dataGridViewCell.ErrorText = ""; } } } private void dgvPods_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e) { if (e.Button == MouseButtons.Right && e.RowIndex >= 0 && e.RowIndex < dgvPods.Rows.Count && e.RowIndex != dgvPods.NewRowIndex) { if (dgvPods.SelectedRows.Count > 0) { dgvPods.SelectedRows[0].Selected = false; } dgvPods.Rows[e.RowIndex].Selected = true; int num = ((e.ColumnIndex >= 0) ? dgvPods.GetColumnDisplayRectangle(e.ColumnIndex, cutOverflow: true).X : (num = e.Location.X)); int num2 = dgvPods.GetRowDisplayRectangle(e.RowIndex, cutOverflow: true).Y; cmnuDataGrid.Show(dgvPods, num + e.X, num2 + e.Y); } } private void dgvPods_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) { if (e.RowIndex != dgvPods.Rows.Count - 1) { PodData podData = ((e.RowIndex != mRowInEdit) ? mPods[e.RowIndex] : mPodInEdit); if (e.ColumnIndex == colIsLocal.Index) { e.Value = podData.Pod.IsLocal; } else if (e.ColumnIndex == colPodName.Index) { e.Value = podData.Pod.Name; } else if (e.ColumnIndex == colSquad.Index) { e.Value = podData.Squad; } else if (e.ColumnIndex == colType.Index) { e.Value = (HostTypeHelper)podData.Pod.HostType; } else if (e.ColumnIndex == colMacAddress.Index) { e.Value = podData.Pod.MacAddress; } else if (e.ColumnIndex == colIPAddress.Index) { e.Value = podData.Pod.IPAddress; dgvPods.Rows[e.RowIndex].Cells[e.ColumnIndex].ReadOnly = podData.Pod.IsLocal; } else if (e.ColumnIndex == colSubnet.Index) { e.Value = podData.Pod.Subnet; } else if (e.ColumnIndex == colGateway.Index) { e.Value = podData.Pod.Gateway; } else if (e.ColumnIndex == colDNSServer.Index) { e.Value = podData.Pod.DNS; } else if (e.ColumnIndex == colHostName.Index) { e.Value = podData.Pod.HostName; } } } private void dgvPods_CellValuePushed(object sender, DataGridViewCellValueEventArgs e) { PodData podData = null; if (e.RowIndex < mPods.Count) { if (mPodInEdit == null) { mPodInEdit = new PodData(mPods[e.RowIndex].Pod.Clone(), mPods[e.RowIndex].Squad, this); } podData = mPodInEdit; mRowInEdit = e.RowIndex; } else { podData = mPodInEdit; } if (e.ColumnIndex == colPodName.Index) { podData.Pod.Name = (string)e.Value; } else if (e.ColumnIndex == colSquad.Index) { podData.Squad = (string)e.Value; } else if (e.ColumnIndex == colType.Index) { podData.Pod.HostType = ((e.Value is string) ? HostTypeHelper.Parse((string)e.Value).HostType : ((HostTypeHelper)e.Value).HostType); } else if (e.ColumnIndex == colIPAddress.Index) { if (IPAddress.TryParse((string)e.Value, out var address)) { podData.Pod.IPAddress = address; } else { MessageBox.Show("Address could not be parsed.", "Parsing Error", MessageBoxButtons.OK); } } } private void dgvPods_NewRowNeeded(object sender, DataGridViewRowEventArgs e) { mPodInEdit = new PodData(new Pod(), (mSite.Squads.Count > 0) ? mSite.Squads[0].Name : "", this); mRowInEdit = dgvPods.Rows.Count - 1; e.Row.Cells[colIPAddress.Index].ReadOnly = false; } private void dgvPods_RowValidated(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex >= mPods.Count && e.RowIndex != dgvPods.Rows.Count - 1) { if (mPodInEdit.Pod.IsLocal) { mPodInEdit.StartPing(); } mPods.Add(mPodInEdit); mPodInEdit = null; mRowInEdit = -1; } else if (mPodInEdit != null && e.RowIndex < mPods.Count) { if (mPodInEdit.Pod.IsLocal) { mPodInEdit.StartPing(); } if (mPods[e.RowIndex] != null) { mPods[e.RowIndex].StopPing(); } mPods[e.RowIndex] = mPodInEdit; mPodInEdit = null; mRowInEdit = -1; } else if (dgvPods.ContainsFocus) { mPodInEdit = null; mRowInEdit = -1; } } private void dgvPods_CancelRowEdit(object sender, QuestionEventArgs e) { if (mRowInEdit == dgvPods.Rows.Count - 2 && mRowInEdit == mPods.Count) { mPodInEdit = new PodData(new Pod(), mSite.Squads[0].Name, this); return; } mPodInEdit = null; mRowInEdit = -1; } private void dgvPods_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e) { if (e.Row.Index < mPods.Count) { if (mPods[e.Row.Index].OperationInProgress) { e.Cancel = true; return; } Pod pod = mPods[e.Row.Index].Pod; if (MessageBox.Show(this, "Are you sure you want to delete " + pod.Name + "?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { e.Cancel = true; return; } mPods[e.Row.Index].StopPing(); mPods.RemoveAt(e.Row.Index); } if (e.Row.Index == mRowInEdit) { mRowInEdit = -1; mPodInEdit = null; } } private void cmnuDataGrid_Opening(object sender, CancelEventArgs e) { int num = 0; int num2 = 0; List list = null; foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) { if (selectedRow.Index >= mPods.Count) { continue; } if (mPods[selectedRow.Index].Pod.IsLocal && !mPods[selectedRow.Index].OperationInProgress && mPods[selectedRow.Index].PingReply != null && mPods[selectedRow.Index].PingReply.Status == IPStatus.Success && mPods[selectedRow.Index].Conn.IsConnected) { num++; } if (!mPods[selectedRow.Index].OperationInProgress) { num2++; } if (list == null) { list = new List(); if (mPods[selectedRow.Index].Pod.IsLocal) { LaunchData[] allApplications = mPods[selectedRow.Index].Conn.AllApplications; if (allApplications != null) { list.AddRange(allApplications); } } } else if (mPods[selectedRow.Index].Pod.IsLocal) { for (int num3 = list.Count - 1; num3 >= 0; num3--) { bool flag = false; LaunchData[] allApplications2 = mPods[selectedRow.Index].Conn.AllApplications; for (int i = 0; i < allApplications2.Length; i++) { LaunchData launchData = allApplications2[i]; if (list[num3].LaunchPair.LaunchKey == launchData.LaunchPair.LaunchKey) { flag = true; break; } } if (!flag) { list.RemoveAt(num3); } } } else { list.Clear(); } } deletePodToolStripMenuItem.Enabled = num2 > 0; installProductToolStripMenuItem.Enabled = num > 0; resendPodConfigurationToolStripMenuItem.Enabled = num > 0; uninstallProductToolStripMenuItem.Enabled = list != null && list.Count > 0; uninstallProductToolStripMenuItem.DropDownItems.Clear(); if (list == null) { return; } foreach (LaunchData item in list) { ToolStripMenuItem toolStripMenuItem = new ToolStripMenuItem(); toolStripMenuItem.Text = item.LaunchPair.DisplayName; toolStripMenuItem.Tag = item; toolStripMenuItem.Click += UninstallProduct_Click; uninstallProductToolStripMenuItem.DropDownItems.Add(toolStripMenuItem); } } /// /// Rebuilds the "Install Product" context menu from the data-driven catalog /// () — one entry per product plus the "Add New Product..." /// management action. /// private void BuildInstallProductMenu() { installProductToolStripMenuItem.DropDownItems.Clear(); foreach (ProductDefinition product in AppRegistry.Products) { ToolStripMenuItem installItem = new ToolStripMenuItem(product.MenuText) { Tag = product }; installItem.Click += InstallProduct_Click; installProductToolStripMenuItem.DropDownItems.Add(installItem); } installProductToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator()); ToolStripMenuItem addItem = new ToolStripMenuItem("Add New Product..."); addItem.Click += AddProduct_Click; installProductToolStripMenuItem.DropDownItems.Add(addItem); } /// Defines a new catalog product, persists it, and optionally pushes it to the selected pods. private void AddProduct_Click(object sender, EventArgs e) { using dlgAddProduct dialog = new dlgAddProduct(); if (dialog.ShowDialog(this) != DialogResult.OK) { return; } AppRegistry.AddProduct(dialog.Product); BuildInstallProductMenu(); // A package means "deploy now" — install it (file transfer + entry registration on // completion) to the selected pods. No package → the product is only added to the catalog. if (dialog.PackagePath == null) { return; } foreach (int podIndex in SelectedInstallablePodIndices()) { mPods[podIndex].Install_CustomResolution = null; mPods[podIndex].Install_HostTypes = new List(); InstallProduct(podIndex, dialog.Product.Id, dialog.PackagePath); } } /// Indices of selected rows that are local, idle pods eligible for install. private IEnumerable SelectedInstallablePodIndices() { foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) { if (selectedRow.Index < mPods.Count && mPods[selectedRow.Index].Pod.IsLocal && !mPods[selectedRow.Index].OperationInProgress) { yield return selectedRow.Index; } } } /// Generic install handler: selects a package, optionally prompts for roles/resolution, installs to selected local pods. private void InstallProduct_Click(object sender, EventArgs e) { ProductDefinition product = (ProductDefinition)((ToolStripMenuItem)sender).Tag; dlgSelectPackage.Title = "Select " + product.Name + " Install Package"; if (dlgSelectPackage.ShowDialog(this) != DialogResult.OK) { return; } Size? resolution = null; List hostTypes = new List(); if (product.HostTypeDialog) { using dlgOptionalStartParams startParams = new dlgOptionalStartParams(); if (startParams.ShowDialog(this) != DialogResult.OK) { return; } resolution = startParams.Resolution; if (startParams.IsGameClient) { hostTypes.Add(PodInstallType.GameClient); } if (startParams.IsLiveCam) { hostTypes.Add(PodInstallType.LiveCamera); } if (startParams.IsMissionReview) { hostTypes.Add(PodInstallType.MissionReview); } } foreach (int podIndex in SelectedInstallablePodIndices()) { mPods[podIndex].Install_CustomResolution = resolution; mPods[podIndex].Install_HostTypes = new List(hostTypes); InstallProduct(podIndex, product.Id, dlgSelectPackage.FileName); } } private void Pod_PingCompleted(object sender, PingCompletedEventArgs e) { PodData podData = e.UserState as PodData; podData.OperationInProgress = false; podData.Error = e.Error; podData.PingReply = e.Reply; if (e.Reply.Status == IPStatus.Success) { podData.Conn.ConnectAsync(podData); } podData.StartPing(); int num = mPods.IndexOf(podData); if (num >= 0 && num < dgvPods.Rows.Count) { dgvPods.InvalidateRow(num); } } private void Pod_PingCompleted(PodData pod) { if (base.InvokeRequired) { BeginInvoke(new Action(Pod_PingCompleted), pod); return; } if (!pod.Conn.IsConnecting && !pod.Conn.IsConnected && pod.PingReply != null && pod.PingReply.Status == IPStatus.Success) { pod.Conn.ConnectAsync(pod); } int num = mPods.IndexOf(pod); if (num >= 0 && num < dgvPods.Rows.Count) { dgvPods.InvalidateRow(num); } } private void PodInfo_InstallProductProgress(InstallProductProgressEventArgs e) { Tuple tuple = e.UserState as Tuple; tuple.A.PercentProgress = e.ProgressPercentage; ProductDefinition product = AppRegistry.GetProduct(tuple.B); tuple.A.Status = "Installing " + (product?.Name ?? "product") + ": " + e.Status; dgvPods.InvalidateCell(colOperationProgress.Index, mPods.IndexOf(tuple.A)); } private void PodInfo_InstallProductCompleted(object sender, InstallProductCompletedEventArgs e) { Tuple tuple = e.UserState as Tuple; PodData a = tuple.A; Exception error = e.Error; // Register the launch entries only when the transfer succeeded, and never // let a pod that dropped mid-install take the console down (AddApp throws // when the connection is gone). if (error == null) { try { foreach (LaunchData appData in BuildLaunchData(tuple.B, a)) { a.Conn.AddApp(appData); } } catch (Exception ex) { Program.LogException(ex, "Registering launch entries failed."); error = ex; } } a.OperationInProgress = false; a.Error = error; dgvPods.InvalidateCell(colOperationProgress.Index, mPods.IndexOf(a)); } /// /// Builds the launch entries a product registers on a pod, honoring (for host-type products) /// the roles selected during install. Returns empty if the product is unknown. /// private static List BuildLaunchData(Guid productId, PodData pod) { List list = new List(); ProductDefinition product = AppRegistry.GetProduct(productId); if (product == null) { return list; } foreach (LaunchEntryDefinition entry in product.Entries) { if (product.HostTypeDialog && entry.HostType != AppHostType.None && !pod.Install_HostTypes.Contains(ToInstallType(entry.HostType))) { continue; } list.Add(entry.ToLaunchData(pod.Install_CustomResolution)); } return list; } private static PodInstallType ToInstallType(AppHostType hostType) { return hostType switch { AppHostType.LiveCamera => PodInstallType.LiveCamera, AppHostType.MissionReview => PodInstallType.MissionReview, _ => PodInstallType.GameClient, }; } private void PodInfo_UninstallProductCompleted(object sender, AsyncCompletedEventArgs e) { PodData podData = (PodData)e.UserState; podData.OperationInProgress = false; podData.Error = e.Error; podData.Status = "Uninstall completed!"; dgvPods.InvalidateCell(colOperationProgress.Index, mPods.IndexOf(podData)); } private void PodInfo_ConnectCompleted(object sender, AsyncCompletedEventArgs e) { PodData podData = e.UserState as PodData; if (e.Error != null) { podData.Error = e.Error; if (podData.PingReply != null && podData.PingReply.Status == IPStatus.Success) { new System.Threading.Timer(delegate(object state) { PodData podData2 = (PodData)state; if (!podData2.Conn.IsConnected && !podData2.Conn.IsConnecting) { ((PodData)state).Conn.ConnectAsync((PodData)state); } }, podData, 5000, -1); } } else { podData.OperationInProgress = false; } int num = mPods.IndexOf(podData); if (num >= 0 && num < dgvPods.Rows.Count) { dgvPods.InvalidateRow(num); } } private void UninstallProduct_Click(object sender, EventArgs e) { ToolStripMenuItem toolStripMenuItem = (ToolStripMenuItem)sender; LaunchData launchData = (LaunchData)toolStripMenuItem.Tag; foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) { if (selectedRow.Index < mPods.Count && mPods[selectedRow.Index].Pod.IsLocal && !mPods[selectedRow.Index].OperationInProgress) { UninstallProduct(selectedRow.Index, launchData.LaunchPair.LaunchKey); } } } private void InstallProduct(int podIndex, Guid appId, string packageFilename) { PodInfo conn = mPods[podIndex].Conn; mPods[podIndex].OperationInProgress = true; mPods[podIndex].Error = null; mPods[podIndex].PercentProgress = 0; mPods[podIndex].Status = ""; conn.InstallProductAsync(packageFilename, new Tuple(mPods[podIndex], appId), PodInfo_InstallProductProgress); } private void UninstallProduct(int podIndex, Guid appId) { PodInfo conn = mPods[podIndex].Conn; mPods[podIndex].OperationInProgress = true; mPods[podIndex].Error = null; mPods[podIndex].PercentProgress = 0; mPods[podIndex].Status = "Uninstalling product(s)..."; dgvPods.InvalidateCell(colOperationProgress.Index, podIndex); conn.UninstallProductAsync(appId, mPods[podIndex]); } private void LoadConfiguration(Site site) { mSite = site; dgvPods.Rows.Clear(); mPods.Clear(); foreach (Squad squad in site.Squads) { colSquad.Items.Add(squad.Name); foreach (Pod pod in squad.Pods) { PodData podData = new PodData(pod, squad.Name, this); mPods.Add(podData); if (pod.IsLocal) { podData.PercentProgress = -1; podData.OperationInProgress = true; podData.Status = "Pinging pod..."; Ping ping = new Ping(); ping.PingCompleted += Pod_PingCompleted; ping.SendAsync(pod.IPAddress, 10000, podData); } } } colType.Items.Add((HostTypeHelper)HostType.GameMachineHostType); colType.Items.Add((HostTypeHelper)HostType.MissionReviewHostType); dgvPods.RowCount = mPods.Count + 1; dgvPods.AutoResizeColumns(); } private void importSiteConfigurationToolStripMenuItem_Click(object sender, EventArgs e) { if (MessageBox.Show(this, "Importing a site configuration will replace the current configuration. Are you sure you want to continue?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes && dlgOpenFile.ShowDialog(this) == DialogResult.OK) { LoadConfiguration(TeslaConsole.Site.LoadFromFile(dlgOpenFile.FileName)); } } private void exportSiteConfigurationToolStripMenuItem_Click(object sender, EventArgs e) { if (dlgSaveFile.ShowDialog(this) == DialogResult.OK) { mSite.SaveAs(dlgSaveFile.FileName); } } private void DeletePod(int rowIndex) { if (rowIndex < mPods.Count && !mPods[rowIndex].OperationInProgress) { dgvPods.Rows.RemoveAt(rowIndex); mPods[rowIndex].StopPing(); // Close any lingering/half-open connection to the pod's IP. Without this the // socket + connection state survive until the console process exits — which is // why deleting a pod previously still required a console restart before it // could be reconfigured. mPods[rowIndex].Conn?.Disconnect(); mPods.RemoveAt(rowIndex); } } private void deletePodToolStripMenuItem_Click(object sender, EventArgs e) { if (dgvPods.SelectedRows.Count > 1) { if (MessageBox.Show(this, "Are you sure you want to delete these pods?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { return; } } else if (dgvPods.CurrentRow.Index < mPods.Count && MessageBox.Show(this, "Are you sure you want to delete " + mPods[dgvPods.CurrentRow.Index].Pod.Name + "?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { return; } for (int num = dgvPods.SelectedRows.Count - 1; num >= 0; num--) { DeletePod(dgvPods.SelectedRows[num].Index); } } private void resendPodConfigurationToolStripMenuItem_Click(object sender, EventArgs e) { if (dgvPods.SelectedRows.Count == 1) { if (MessageBox.Show(this, "Reconfiguring this pod will remove it from your site, allowing you to reconfigure it. Are you sure you want to continue?", "Reconfigure", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { return; } } else if (MessageBox.Show(this, "Reconfiguring these pods will remove them from your site, allowing you to reconfigure them. Are you sure you want to continue?", "Reconfigure", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { return; } List list = new List(); foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) { if (selectedRow.Index >= mPods.Count) { continue; } PodData podData = mPods[selectedRow.Index]; list.Add(podData); DeletePod(selectedRow.Index); foreach (Squad squad in mSite.Squads) { if (!(squad.Name == podData.Squad)) { continue; } for (int num = squad.Pods.Count - 1; num >= 0; num--) { if (squad.Pods[num].ID == podData.Pod.ID) { squad.Pods.RemoveAt(num); } } } } mSite.Save(); foreach (PodData item in list) { try { item.Conn.ClearStore(); } catch (ConnectionLostException) { MessageBox.Show($"The data store on {item.Pod.Name} could not be cleared because the connection has been lost. Please try again.", "Connection Lost"); } } } }