Reconfigure timeout (launcher reinstall of an already-configured pod): - The console pings each configured pod and connects on success, on a loop, and never tore that down — so it kept hammering the pod's old IP with a stale connection while SecureConfig tried to bring the pod up on that address, which timed out. Only a console restart (closing all sockets) cleared it. - DeletePod now Disconnect()s the pod's connection (closes the lingering socket) so delete-to-reconfigure works WITHOUT a console restart. - Reconfiguring a known pod in place now pauses that pod's ping/connect loop (OperationInProgress) and drops its stale connection before SecureConfig, and resumes on completion (success or failure) so it reconnects with the new key. (StopPing kills the ping thread irreversibly, so pause via OperationInProgress.) Note: PodInfo's static mPods registry is dead code (nothing registers), so the real fix is the disconnect, not an unregister. Console deployment: - Console/install.bat: installs the console to C:\Program Files\TeslaConsole, preps the %ProgramData%\Tesla Console data dir (Users: modify), creates a Start Menu shortcut, and adds a per-program inbound firewall rule so it can receive pod discovery beacons. net48 is in-box, no runtime install. - Console/build-package.bat: publishes the console (net48 framework-dependent) to dist\TeslaConsole\App and assembles the package with install.bat. 74 tests green; the pod-config fix is UI/network behavior, validate on a pod. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1448 lines
49 KiB
C#
1448 lines
49 KiB
C#
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<PodInstallType> Install_HostTypes = new List<PodInstallType>();
|
|
|
|
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 registerProductToolStripMenuItem;
|
|
|
|
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<PodData> mPods = new List<PodData>();
|
|
|
|
private PodData mPodInEdit;
|
|
|
|
private int mRowInEdit = -1;
|
|
|
|
private Dictionary<Guid, Pod> mReconfiguringPods = new Dictionary<Guid, Pod>();
|
|
|
|
private Dictionary<string, Button> mConfigureButtons = new Dictionary<string, Button>();
|
|
|
|
private Dictionary<Guid, Label> mConfigureLabels = new Dictionary<Guid, Label>();
|
|
|
|
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<string, byte[]>(requestId, macAddress);
|
|
}
|
|
else
|
|
{
|
|
button.Text = "Configure " + podData.Pod.Name;
|
|
button.Tag = new Tuple<string, PodData>(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<string, PodData>)
|
|
{
|
|
Tuple<string, PodData> tuple = button.Tag as Tuple<string, PodData>;
|
|
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<string, byte[]>))
|
|
{
|
|
return;
|
|
}
|
|
Tuple<string, byte[]> tuple2 = button.Tag as Tuple<string, byte[]>;
|
|
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<string, string, Pod, string>(requestId, passphrase, pod, squadName));
|
|
}
|
|
|
|
private void ConfigureWorker_DoWork(object sender, DoWorkEventArgs e)
|
|
{
|
|
Tuple<string, string, Pod, string> tuple = (Tuple<string, string, Pod, string>)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<Pod, string>(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<Pod, string>))
|
|
{
|
|
return;
|
|
}
|
|
Tuple<Pod, string> tuple = e.Result as Tuple<Pod, string>;
|
|
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<PodData> list = new List<PodData>(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<LaunchData> 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<LaunchData>();
|
|
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;
|
|
if (registerProductToolStripMenuItem != null)
|
|
{
|
|
registerProductToolStripMenuItem.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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebuilds the product-related context menus from the data-driven catalog (<see cref="AppRegistry"/>):
|
|
/// • "Install Product" → transfer a package and register its launch entries
|
|
/// • "Register Product" → push launch entries to pods over the network, no file install
|
|
/// plus the "Add New Product..." management action.
|
|
/// </summary>
|
|
private void BuildInstallProductMenu()
|
|
{
|
|
// Lazily create the "Register Product" parent and place it after "Install Product".
|
|
if (registerProductToolStripMenuItem == null)
|
|
{
|
|
registerProductToolStripMenuItem = new ToolStripMenuItem("Register Product on Pods");
|
|
int insertAt = cmnuDataGrid.Items.IndexOf(installProductToolStripMenuItem) + 1;
|
|
cmnuDataGrid.Items.Insert(insertAt, registerProductToolStripMenuItem);
|
|
}
|
|
|
|
installProductToolStripMenuItem.DropDownItems.Clear();
|
|
registerProductToolStripMenuItem.DropDownItems.Clear();
|
|
foreach (ProductDefinition product in AppRegistry.Products)
|
|
{
|
|
ToolStripMenuItem installItem = new ToolStripMenuItem(product.MenuText) { Tag = product };
|
|
installItem.Click += InstallProduct_Click;
|
|
installProductToolStripMenuItem.DropDownItems.Add(installItem);
|
|
|
|
ToolStripMenuItem registerItem = new ToolStripMenuItem(product.Name) { Tag = product };
|
|
registerItem.Click += RegisterProduct_Click;
|
|
registerProductToolStripMenuItem.DropDownItems.Add(registerItem);
|
|
}
|
|
|
|
installProductToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator());
|
|
ToolStripMenuItem addItem = new ToolStripMenuItem("Add New Product...");
|
|
addItem.Click += AddProduct_Click;
|
|
installProductToolStripMenuItem.DropDownItems.Add(addItem);
|
|
}
|
|
|
|
/// <summary>Defines a new catalog product, persists it, and optionally pushes it to the selected pods.</summary>
|
|
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();
|
|
|
|
if (!dialog.PushNow)
|
|
{
|
|
return;
|
|
}
|
|
if (dialog.PackagePath != null)
|
|
{
|
|
// Package supplied → full install (file transfer + entry registration on completion).
|
|
foreach (int podIndex in SelectedInstallablePodIndices())
|
|
{
|
|
mPods[podIndex].Install_CustomResolution = null;
|
|
mPods[podIndex].Install_HostTypes = new List<PodInstallType>();
|
|
InstallProduct(podIndex, dialog.Product.Id, dialog.PackagePath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RegisterProductOnSelectedPods(dialog.Product);
|
|
}
|
|
}
|
|
|
|
/// <summary>Pushes a catalog product's launch entries to selected connected pods without installing files.</summary>
|
|
private void RegisterProduct_Click(object sender, EventArgs e)
|
|
{
|
|
ProductDefinition product = (ProductDefinition)((ToolStripMenuItem)sender).Tag;
|
|
RegisterProductOnSelectedPods(product);
|
|
}
|
|
|
|
private void RegisterProductOnSelectedPods(ProductDefinition product)
|
|
{
|
|
int pushed = 0;
|
|
int skipped = 0;
|
|
foreach (int podIndex in SelectedInstallablePodIndices())
|
|
{
|
|
PodData pod = mPods[podIndex];
|
|
if (!pod.Conn.IsConnected)
|
|
{
|
|
skipped++;
|
|
continue;
|
|
}
|
|
foreach (LaunchEntryDefinition entry in product.Entries)
|
|
{
|
|
pod.Conn.AddApp(entry.ToLaunchData(null));
|
|
}
|
|
pod.Status = "Registered " + product.Name;
|
|
dgvPods.InvalidateCell(colOperationProgress.Index, podIndex);
|
|
pushed++;
|
|
}
|
|
if (skipped > 0)
|
|
{
|
|
MessageBox.Show(this,
|
|
$"Registered \"{product.Name}\" on {pushed} pod(s). {skipped} selected pod(s) were skipped because they are not connected.",
|
|
"Register Product", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
}
|
|
|
|
/// <summary>Indices of selected rows that are local, idle pods eligible for install/registration.</summary>
|
|
private IEnumerable<int> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Generic install handler: selects a package, optionally prompts for roles/resolution, installs to selected local pods.</summary>
|
|
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<PodInstallType> hostTypes = new List<PodInstallType>();
|
|
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<PodInstallType>(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<PodData>(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<PodData, Guid> tuple = e.UserState as Tuple<PodData, Guid>;
|
|
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<PodData, Guid> tuple = e.UserState as Tuple<PodData, Guid>;
|
|
PodData a = tuple.A;
|
|
foreach (LaunchData appData in BuildLaunchData(tuple.B, a))
|
|
{
|
|
a.Conn.AddApp(appData);
|
|
}
|
|
a.OperationInProgress = false;
|
|
a.Error = e.Error;
|
|
dgvPods.InvalidateCell(colOperationProgress.Index, mPods.IndexOf(a));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private static List<LaunchData> BuildLaunchData(Guid productId, PodData pod)
|
|
{
|
|
List<LaunchData> list = new List<LaunchData>();
|
|
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<PodData, Guid>(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<PodData> list = new List<PodData>();
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
}
|