Co-locate the two cockpit-pod projects into a single repository:
- Console/ : TeslaConsole, the net48 WinForms operator console (decompiled
reconstruction) plus its differential + catalog test suite.
- Launcher/ : TeslaLauncher, the net6 pod-side Service + Agent rewrite.
Adds a combined TeslaSuite.sln, root README documenting the shared wire
contract (and its current duplication, the main follow-up), and a root
.gitignore. Histories were not preserved per request; this is a fresh start
from the current working state of both projects.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
474 lines
15 KiB
C#
474 lines
15 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using Tesla.Net;
|
|
using TeslaConsole.Properties;
|
|
|
|
namespace TeslaConsole;
|
|
|
|
public class AppControlGrid : UserControl
|
|
{
|
|
private class ApplicationInstanceGroup
|
|
{
|
|
public Guid LaunchKey { get; set; }
|
|
|
|
public string AppDisplayName { get; set; }
|
|
|
|
public List<Guid> RunningPods { get; private set; }
|
|
|
|
public List<Guid> StoppedPods { get; private set; }
|
|
|
|
public string RunningKey => "running:" + LaunchKey;
|
|
|
|
public string StoppedKey => "stopped:" + LaunchKey;
|
|
|
|
public ApplicationInstanceGroup(LaunchPair launchPair)
|
|
{
|
|
LaunchKey = launchPair.LaunchKey;
|
|
AppDisplayName = launchPair.DisplayName;
|
|
RunningPods = new List<Guid>();
|
|
StoppedPods = new List<Guid>();
|
|
}
|
|
|
|
public void AddInstance(PodAppInstance instance)
|
|
{
|
|
if (instance.IsRunning)
|
|
{
|
|
RunningPods.Add(instance.PodId);
|
|
}
|
|
else
|
|
{
|
|
StoppedPods.Add(instance.PodId);
|
|
}
|
|
}
|
|
|
|
public void StartPod(Guid podId)
|
|
{
|
|
if (StoppedPods.Contains(podId))
|
|
{
|
|
StoppedPods.Remove(podId);
|
|
if (!RunningPods.Contains(podId))
|
|
{
|
|
RunningPods.Add(podId);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void StartAllPods()
|
|
{
|
|
List<Guid> list = new List<Guid>(StoppedPods);
|
|
foreach (Guid item in list)
|
|
{
|
|
StartPod(item);
|
|
}
|
|
}
|
|
|
|
public void StopPod(Guid podId)
|
|
{
|
|
if (RunningPods.Contains(podId))
|
|
{
|
|
RunningPods.Remove(podId);
|
|
if (!StoppedPods.Contains(podId))
|
|
{
|
|
StoppedPods.Add(podId);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void StopAllPods()
|
|
{
|
|
List<Guid> list = new List<Guid>(RunningPods);
|
|
foreach (Guid item in list)
|
|
{
|
|
StopPod(item);
|
|
}
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (obj is ApplicationInstanceGroup)
|
|
{
|
|
return LaunchKey == ((ApplicationInstanceGroup)obj).LaunchKey;
|
|
}
|
|
return base.Equals(obj);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return LaunchKey.GetHashCode();
|
|
}
|
|
}
|
|
|
|
private Dictionary<Guid, ApplicationInstanceGroup> mAppGroups = new Dictionary<Guid, ApplicationInstanceGroup>();
|
|
|
|
private IContainer components;
|
|
|
|
private NoSelectDataGridView grdApps;
|
|
|
|
private DataGridViewLinkColumn colAction;
|
|
|
|
private DataGridViewTextBoxColumn colStatus;
|
|
|
|
private DataGridViewTextBoxColumn colName;
|
|
|
|
private DataGridViewImageColumn colError;
|
|
|
|
public bool ShowCounts { get; set; }
|
|
|
|
public event EventHandler<AppEventArgs> LaunchApp;
|
|
|
|
public event EventHandler<AppEventArgs> KillApp;
|
|
|
|
public AppControlGrid()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
private void grdApps_CellContentClick(object sender, DataGridViewCellEventArgs e)
|
|
{
|
|
if (e.RowIndex < 0 || e.RowIndex >= grdApps.Rows.Count)
|
|
{
|
|
return;
|
|
}
|
|
DataGridViewRow dataGridViewRow = grdApps.Rows[e.RowIndex];
|
|
string text = (string)dataGridViewRow.Tag;
|
|
ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[new Guid(text.Split(':')[1])];
|
|
if (!(dataGridViewRow.Cells[e.ColumnIndex] is DataGridViewLinkCell dataGridViewLinkCell))
|
|
{
|
|
return;
|
|
}
|
|
foreach (DataGridViewRow item in (IEnumerable)grdApps.Rows)
|
|
{
|
|
item.Cells[3].Value = Resources.Blank;
|
|
((List<string>)item.Cells[3].Tag).Clear();
|
|
}
|
|
Rectangle cellDisplayRectangle = grdApps.GetCellDisplayRectangle(0, e.RowIndex, cutOverflow: false);
|
|
PictureBox pictureBox = new PictureBox();
|
|
pictureBox.Name = text;
|
|
pictureBox.Image = Resources.square_throbber;
|
|
pictureBox.SizeMode = PictureBoxSizeMode.CenterImage;
|
|
pictureBox.Location = cellDisplayRectangle.Location;
|
|
pictureBox.Size = cellDisplayRectangle.Size;
|
|
pictureBox.Tag = e.RowIndex;
|
|
grdApps.Controls.Add(pictureBox);
|
|
pictureBox.BringToFront();
|
|
if ((string)dataGridViewLinkCell.Value == "Start")
|
|
{
|
|
if (this.LaunchApp != null)
|
|
{
|
|
this.LaunchApp(this, new AppEventArgs(applicationInstanceGroup.LaunchKey, applicationInstanceGroup.StoppedPods));
|
|
}
|
|
}
|
|
else if ((string)dataGridViewLinkCell.Value == "Stop" && this.KillApp != null)
|
|
{
|
|
this.KillApp(this, new AppEventArgs(applicationInstanceGroup.LaunchKey, applicationInstanceGroup.RunningPods));
|
|
}
|
|
}
|
|
|
|
private void grdApps_Scroll(object sender, ScrollEventArgs e)
|
|
{
|
|
foreach (Control control in grdApps.Controls)
|
|
{
|
|
if (control is PictureBox)
|
|
{
|
|
Rectangle cellDisplayRectangle = grdApps.GetCellDisplayRectangle(0, (int)control.Tag, cutOverflow: false);
|
|
control.Location = cellDisplayRectangle.Location;
|
|
control.Size = cellDisplayRectangle.Size;
|
|
control.BringToFront();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
mAppGroups.Clear();
|
|
grdApps.Rows.Clear();
|
|
grdApps.Controls.Clear();
|
|
}
|
|
|
|
public void SetData(IEnumerable<PodAppInstance> instances)
|
|
{
|
|
Clear();
|
|
foreach (PodAppInstance instance in instances)
|
|
{
|
|
if (!mAppGroups.TryGetValue(instance.LaunchPair.LaunchKey, out var value))
|
|
{
|
|
value = new ApplicationInstanceGroup(instance.LaunchPair);
|
|
mAppGroups.Add(instance.LaunchPair.LaunchKey, value);
|
|
}
|
|
value.AddInstance(instance);
|
|
}
|
|
Refresh();
|
|
grdApps.Sort(grdApps.Columns[2], ListSortDirection.Ascending);
|
|
}
|
|
|
|
public void StartApp(Guid podId, Guid appId)
|
|
{
|
|
if (mAppGroups.ContainsKey(appId))
|
|
{
|
|
ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId];
|
|
applicationInstanceGroup.StartPod(podId);
|
|
RefreshAppGroup(applicationInstanceGroup);
|
|
Resort();
|
|
}
|
|
}
|
|
|
|
public void StartAppFailed(Guid appId, string podDisplayName)
|
|
{
|
|
if (mAppGroups.ContainsKey(appId))
|
|
{
|
|
ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId];
|
|
FlagError(applicationInstanceGroup.StoppedKey, podDisplayName, "The following pods failed to stop:");
|
|
}
|
|
}
|
|
|
|
public void StopApp(Guid podId, Guid appId)
|
|
{
|
|
if (mAppGroups.ContainsKey(appId))
|
|
{
|
|
ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId];
|
|
applicationInstanceGroup.StopPod(podId);
|
|
RefreshAppGroup(applicationInstanceGroup);
|
|
Resort();
|
|
}
|
|
}
|
|
|
|
public void StopAppFailed(Guid appId, string podDisplayName)
|
|
{
|
|
if (mAppGroups.ContainsKey(appId))
|
|
{
|
|
ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId];
|
|
FlagError(applicationInstanceGroup.RunningKey, podDisplayName, "The following pods failed to stop:");
|
|
}
|
|
}
|
|
|
|
private void FlagError(string rowKey, string podDisplayName, string error)
|
|
{
|
|
Control[] array = grdApps.Controls.Find(rowKey, searchAllChildren: false);
|
|
foreach (Control value in array)
|
|
{
|
|
grdApps.Controls.Remove(value);
|
|
}
|
|
foreach (DataGridViewRow item in (IEnumerable)grdApps.Rows)
|
|
{
|
|
if (object.Equals(item.Tag, rowKey))
|
|
{
|
|
((List<string>)item.Cells[3].Tag).Add(podDisplayName);
|
|
RefreshRowError(item, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Refresh()
|
|
{
|
|
base.Refresh();
|
|
grdApps.SuspendLayout();
|
|
foreach (ApplicationInstanceGroup value in mAppGroups.Values)
|
|
{
|
|
RefreshAppGroup(value);
|
|
}
|
|
grdApps.ResumeLayout();
|
|
grdApps.AutoResizeColumns();
|
|
Resort();
|
|
}
|
|
|
|
private void RefreshAppGroup(ApplicationInstanceGroup group)
|
|
{
|
|
bool flag = false;
|
|
bool flag2 = false;
|
|
for (int num = grdApps.Rows.Count - 1; num >= 0; num--)
|
|
{
|
|
DataGridViewRow dataGridViewRow = grdApps.Rows[num];
|
|
if (object.Equals(dataGridViewRow.Tag, group.RunningKey))
|
|
{
|
|
flag = true;
|
|
if (group.RunningPods.Count > 0)
|
|
{
|
|
RefreshRow(dataGridViewRow, isRunning: true, group.RunningPods.Count);
|
|
}
|
|
else
|
|
{
|
|
RemoveRow(num);
|
|
}
|
|
}
|
|
else if (object.Equals(dataGridViewRow.Tag, group.StoppedKey))
|
|
{
|
|
flag2 = true;
|
|
if (group.StoppedPods.Count > 0)
|
|
{
|
|
RefreshRow(dataGridViewRow, isRunning: false, group.StoppedPods.Count);
|
|
}
|
|
else
|
|
{
|
|
RemoveRow(num);
|
|
}
|
|
}
|
|
}
|
|
if (!flag && group.RunningPods.Count > 0)
|
|
{
|
|
grdApps.Rows.Add(CreateRow(group, isRunning: true));
|
|
}
|
|
if (!flag2 && group.StoppedPods.Count > 0)
|
|
{
|
|
grdApps.Rows.Add(CreateRow(group, isRunning: false));
|
|
}
|
|
}
|
|
|
|
private void Resort()
|
|
{
|
|
if (grdApps.SortOrder != 0)
|
|
{
|
|
grdApps.Sort(grdApps.SortedColumn, (grdApps.SortOrder != SortOrder.Ascending) ? ListSortDirection.Descending : ListSortDirection.Ascending);
|
|
}
|
|
}
|
|
|
|
private DataGridViewRow CreateRow(ApplicationInstanceGroup group, bool isRunning)
|
|
{
|
|
DataGridViewRow dataGridViewRow = new DataGridViewRow();
|
|
dataGridViewRow.Tag = (isRunning ? group.RunningKey : group.StoppedKey);
|
|
dataGridViewRow.Cells.Add(new DataGridViewLinkCell());
|
|
dataGridViewRow.Cells.Add(new DataGridViewTextBoxCell());
|
|
DataGridViewTextBoxCell dataGridViewTextBoxCell = new DataGridViewTextBoxCell();
|
|
dataGridViewTextBoxCell.Value = group.AppDisplayName;
|
|
dataGridViewRow.Cells.Add(dataGridViewTextBoxCell);
|
|
DataGridViewImageCell dataGridViewImageCell = new DataGridViewImageCell();
|
|
dataGridViewImageCell.Value = Resources.Blank;
|
|
dataGridViewImageCell.Tag = new List<string>();
|
|
dataGridViewRow.Cells.Add(dataGridViewImageCell);
|
|
RefreshRow(dataGridViewRow, isRunning, isRunning ? group.RunningPods.Count : group.StoppedPods.Count);
|
|
return dataGridViewRow;
|
|
}
|
|
|
|
private void RefreshRow(DataGridViewRow row, bool isRunning, int count)
|
|
{
|
|
DataGridViewLinkCell dataGridViewLinkCell = (DataGridViewLinkCell)row.Cells[0];
|
|
dataGridViewLinkCell.Value = (isRunning ? "Stop" : "Start");
|
|
Color color2 = (dataGridViewLinkCell.VisitedLinkColor = (isRunning ? Color.Red : Color.Green));
|
|
Color color5 = (dataGridViewLinkCell.LinkColor = (dataGridViewLinkCell.ActiveLinkColor = color2));
|
|
DataGridViewTextBoxCell dataGridViewTextBoxCell = (DataGridViewTextBoxCell)row.Cells[1];
|
|
if (ShowCounts)
|
|
{
|
|
dataGridViewTextBoxCell.Value = count + (isRunning ? " Running" : " Stopped");
|
|
}
|
|
else
|
|
{
|
|
dataGridViewTextBoxCell.Value = (isRunning ? "Running" : "Stopped");
|
|
}
|
|
dataGridViewTextBoxCell.Style.ForeColor = (isRunning ? Color.Green : Color.Black);
|
|
}
|
|
|
|
private void RefreshRowError(DataGridViewRow row, string errorPrefix)
|
|
{
|
|
row.Cells[3].Value = Resources.Error;
|
|
StringBuilder stringBuilder = new StringBuilder(errorPrefix);
|
|
foreach (string item in (List<string>)row.Cells[3].Tag)
|
|
{
|
|
stringBuilder.AppendLine();
|
|
stringBuilder.Append(" - ");
|
|
stringBuilder.Append(item);
|
|
}
|
|
row.Cells[3].ToolTipText = stringBuilder.ToString();
|
|
}
|
|
|
|
private void RemoveRow(int index)
|
|
{
|
|
Control[] array = grdApps.Controls.Find((string)grdApps.Rows[index].Tag, searchAllChildren: false);
|
|
foreach (Control value in array)
|
|
{
|
|
grdApps.Controls.Remove(value);
|
|
}
|
|
grdApps.Rows.RemoveAt(index);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing && components != null)
|
|
{
|
|
components.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle = new System.Windows.Forms.DataGridViewCellStyle();
|
|
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
|
|
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
|
|
this.grdApps = new TeslaConsole.NoSelectDataGridView();
|
|
this.colAction = new System.Windows.Forms.DataGridViewLinkColumn();
|
|
this.colStatus = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
|
this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
|
this.colError = new System.Windows.Forms.DataGridViewImageColumn();
|
|
((System.ComponentModel.ISupportInitialize)this.grdApps).BeginInit();
|
|
base.SuspendLayout();
|
|
this.grdApps.AllowUserToAddRows = false;
|
|
this.grdApps.AllowUserToDeleteRows = false;
|
|
this.grdApps.AllowUserToResizeRows = false;
|
|
this.grdApps.BackgroundColor = System.Drawing.Color.White;
|
|
this.grdApps.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
|
this.grdApps.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None;
|
|
this.grdApps.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single;
|
|
this.grdApps.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
|
this.grdApps.Columns.AddRange(this.colAction, this.colStatus, this.colName, this.colError);
|
|
this.grdApps.Dock = System.Windows.Forms.DockStyle.Fill;
|
|
this.grdApps.EditMode = System.Windows.Forms.DataGridViewEditMode.EditProgrammatically;
|
|
this.grdApps.GridColor = System.Drawing.Color.White;
|
|
this.grdApps.Location = new System.Drawing.Point(0, 0);
|
|
this.grdApps.MultiSelect = false;
|
|
this.grdApps.Name = "grdApps";
|
|
this.grdApps.ReadOnly = true;
|
|
this.grdApps.RowHeadersVisible = false;
|
|
this.grdApps.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
|
|
this.grdApps.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
|
this.grdApps.Size = new System.Drawing.Size(366, 215);
|
|
this.grdApps.TabIndex = 8;
|
|
this.grdApps.Scroll += new System.Windows.Forms.ScrollEventHandler(grdApps_Scroll);
|
|
this.grdApps.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(grdApps_CellContentClick);
|
|
this.colAction.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
|
|
dataGridViewCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
|
|
this.colAction.DefaultCellStyle = dataGridViewCellStyle;
|
|
this.colAction.Frozen = true;
|
|
this.colAction.HeaderText = "Action";
|
|
this.colAction.LinkColor = System.Drawing.Color.Blue;
|
|
this.colAction.MinimumWidth = 50;
|
|
this.colAction.Name = "colAction";
|
|
this.colAction.ReadOnly = true;
|
|
this.colAction.VisitedLinkColor = System.Drawing.Color.Blue;
|
|
this.colAction.Width = 50;
|
|
this.colStatus.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
|
|
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
|
|
dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25f, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0);
|
|
this.colStatus.DefaultCellStyle = dataGridViewCellStyle2;
|
|
this.colStatus.Frozen = true;
|
|
this.colStatus.HeaderText = "Status";
|
|
this.colStatus.MinimumWidth = 50;
|
|
this.colStatus.Name = "colStatus";
|
|
this.colStatus.ReadOnly = true;
|
|
this.colStatus.Width = 62;
|
|
this.colName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
|
|
dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
|
this.colName.DefaultCellStyle = dataGridViewCellStyle3;
|
|
this.colName.HeaderText = "Application Name";
|
|
this.colName.MinimumWidth = 100;
|
|
this.colName.Name = "colName";
|
|
this.colName.ReadOnly = true;
|
|
this.colError.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None;
|
|
this.colError.HeaderText = "";
|
|
this.colError.MinimumWidth = 20;
|
|
this.colError.Name = "colError";
|
|
this.colError.ReadOnly = true;
|
|
this.colError.Width = 20;
|
|
base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f);
|
|
base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
|
base.Controls.Add(this.grdApps);
|
|
base.Name = "AppControlGrid";
|
|
base.Size = new System.Drawing.Size(366, 215);
|
|
((System.ComponentModel.ISupportInitialize)this.grdApps).EndInit();
|
|
base.ResumeLayout(false);
|
|
}
|
|
}
|