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>
261 lines
6.6 KiB
C#
261 lines
6.6 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
|
|
namespace TeslaConsole;
|
|
|
|
/// <summary>
|
|
/// Defines a new catalog product (a single launch entry) and, optionally, an install
|
|
/// package to deploy. The validated result is exposed as <see cref="Product"/>; the
|
|
/// caller persists it to the catalog and (optionally) pushes it to selected pods.
|
|
/// </summary>
|
|
internal class dlgAddProduct : Form
|
|
{
|
|
private TextBox txtName;
|
|
|
|
private TextBox txtInstallDir;
|
|
|
|
private TextBox txtExe;
|
|
|
|
private TextBox txtArgs;
|
|
|
|
private TextBox txtPackage;
|
|
|
|
private CheckBox chkAutoRestart;
|
|
|
|
private CheckBox chkPushNow;
|
|
|
|
private Button cmdOK;
|
|
|
|
private Button cmdCancel;
|
|
|
|
/// <summary>The product the operator defined. Valid only after the dialog returns <see cref="DialogResult.OK"/>.</summary>
|
|
public ProductDefinition Product { get; private set; }
|
|
|
|
/// <summary>Optional install package (zip) to deploy with the entry; null/empty for entry-only.</summary>
|
|
public string PackagePath => string.IsNullOrWhiteSpace(txtPackage.Text) ? null : txtPackage.Text.Trim();
|
|
|
|
/// <summary>Whether to push the new product to the currently selected pods after adding it.</summary>
|
|
public bool PushNow => chkPushNow.Checked;
|
|
|
|
public dlgAddProduct()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
this.txtName = new TextBox();
|
|
this.txtInstallDir = new TextBox();
|
|
this.txtExe = new TextBox();
|
|
this.txtArgs = new TextBox();
|
|
this.txtPackage = new TextBox();
|
|
this.chkAutoRestart = new CheckBox();
|
|
this.chkPushNow = new CheckBox();
|
|
this.cmdOK = new Button();
|
|
this.cmdCancel = new Button();
|
|
base.SuspendLayout();
|
|
|
|
int labelX = 12;
|
|
int fieldX = 130;
|
|
int fieldW = 300;
|
|
int browseX = fieldX + fieldW + 6;
|
|
int y = 15;
|
|
int rowH = 28;
|
|
|
|
AddLabel("Product name:", labelX, y);
|
|
this.txtName.SetBounds(fieldX, y - 3, fieldW, 20);
|
|
y += rowH;
|
|
|
|
AddLabel("Install directory:", labelX, y);
|
|
this.txtInstallDir.SetBounds(fieldX, y - 3, fieldW, 20);
|
|
AddBrowseButton(browseX, y - 4, BrowseInstallDir);
|
|
y += rowH;
|
|
|
|
AddLabel("Executable:", labelX, y);
|
|
this.txtExe.SetBounds(fieldX, y - 3, fieldW, 20);
|
|
AddBrowseButton(browseX, y - 4, BrowseExe);
|
|
y += rowH;
|
|
|
|
AddLabel("Arguments:", labelX, y);
|
|
this.txtArgs.SetBounds(fieldX, y - 3, fieldW, 20);
|
|
y += rowH;
|
|
|
|
AddLabel("Install package (optional):", labelX, y);
|
|
this.txtPackage.SetBounds(fieldX, y - 3, fieldW, 20);
|
|
AddBrowseButton(browseX, y - 4, BrowsePackage);
|
|
y += rowH;
|
|
|
|
this.chkAutoRestart.SetBounds(fieldX, y, 200, 20);
|
|
this.chkAutoRestart.Text = "Auto-restart if it exits";
|
|
this.chkAutoRestart.Checked = true;
|
|
y += 24;
|
|
|
|
this.chkPushNow.SetBounds(fieldX, y, 280, 20);
|
|
this.chkPushNow.Text = "Push to selected pods now";
|
|
this.chkPushNow.Checked = true;
|
|
y += 36;
|
|
|
|
this.cmdOK.SetBounds(browseX - 169, y, 80, 24);
|
|
this.cmdOK.Text = "OK";
|
|
this.cmdOK.Click += cmdOK_Click;
|
|
|
|
this.cmdCancel.SetBounds(browseX - 83, y, 80, 24);
|
|
this.cmdCancel.Text = "Cancel";
|
|
this.cmdCancel.DialogResult = DialogResult.Cancel;
|
|
|
|
base.AcceptButton = this.cmdOK;
|
|
base.CancelButton = this.cmdCancel;
|
|
base.FormBorderStyle = FormBorderStyle.FixedDialog;
|
|
base.MaximizeBox = false;
|
|
base.MinimizeBox = false;
|
|
base.StartPosition = FormStartPosition.CenterParent;
|
|
base.ClientSize = new Size(browseX + 40, y + 36);
|
|
base.Controls.Add(this.txtName);
|
|
base.Controls.Add(this.txtInstallDir);
|
|
base.Controls.Add(this.txtExe);
|
|
base.Controls.Add(this.txtArgs);
|
|
base.Controls.Add(this.txtPackage);
|
|
base.Controls.Add(this.chkAutoRestart);
|
|
base.Controls.Add(this.chkPushNow);
|
|
base.Controls.Add(this.cmdOK);
|
|
base.Controls.Add(this.cmdCancel);
|
|
base.Name = "dlgAddProduct";
|
|
this.Text = "Add Product";
|
|
base.ResumeLayout(false);
|
|
base.PerformLayout();
|
|
}
|
|
|
|
private void AddLabel(string text, int x, int y)
|
|
{
|
|
Label label = new Label
|
|
{
|
|
Text = text,
|
|
AutoSize = true
|
|
};
|
|
label.SetBounds(x, y, 110, 16);
|
|
base.Controls.Add(label);
|
|
}
|
|
|
|
private void AddBrowseButton(int x, int y, EventHandler onClick)
|
|
{
|
|
Button button = new Button
|
|
{
|
|
Text = "..."
|
|
};
|
|
button.SetBounds(x, y, 30, 22);
|
|
button.Click += onClick;
|
|
base.Controls.Add(button);
|
|
}
|
|
|
|
private void BrowseInstallDir(object sender, EventArgs e)
|
|
{
|
|
using FolderBrowserDialog dialog = new FolderBrowserDialog();
|
|
if (!string.IsNullOrWhiteSpace(txtInstallDir.Text))
|
|
{
|
|
dialog.SelectedPath = txtInstallDir.Text;
|
|
}
|
|
if (dialog.ShowDialog(this) == DialogResult.OK)
|
|
{
|
|
txtInstallDir.Text = dialog.SelectedPath;
|
|
}
|
|
}
|
|
|
|
private void BrowseExe(object sender, EventArgs e)
|
|
{
|
|
using OpenFileDialog dialog = new OpenFileDialog
|
|
{
|
|
Filter = "Executables (*.exe)|*.exe|All Files (*.*)|*.*",
|
|
Title = "Select Executable"
|
|
};
|
|
if (dialog.ShowDialog(this) == DialogResult.OK)
|
|
{
|
|
txtExe.Text = dialog.FileName;
|
|
}
|
|
}
|
|
|
|
private void BrowsePackage(object sender, EventArgs e)
|
|
{
|
|
using OpenFileDialog dialog = new OpenFileDialog
|
|
{
|
|
Filter = "VWE Install Archive (*.zip)|*.zip|All Files (*.*)|*.*",
|
|
Title = "Select Install Package"
|
|
};
|
|
if (dialog.ShowDialog(this) == DialogResult.OK)
|
|
{
|
|
txtPackage.Text = dialog.FileName;
|
|
}
|
|
}
|
|
|
|
private void cmdOK_Click(object sender, EventArgs e)
|
|
{
|
|
string name = txtName.Text.Trim();
|
|
string installDir = txtInstallDir.Text.Trim();
|
|
string exe = txtExe.Text.Trim();
|
|
|
|
if (name.Length == 0)
|
|
{
|
|
Warn("Please enter a product name.");
|
|
return;
|
|
}
|
|
if (exe.Length == 0)
|
|
{
|
|
Warn("Please specify the executable path.");
|
|
return;
|
|
}
|
|
if (installDir.Length == 0)
|
|
{
|
|
Warn("Please specify the install directory.");
|
|
return;
|
|
}
|
|
|
|
string fullExe, fullDir;
|
|
try
|
|
{
|
|
fullExe = Path.GetFullPath(exe);
|
|
fullDir = Path.GetFullPath(installDir);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Warn("The executable or install directory path is not valid.");
|
|
return;
|
|
}
|
|
|
|
// Invariant: the exe must live under the install directory the package extracts to.
|
|
string dirWithSep = fullDir.TrimEnd('\\', '/') + "\\";
|
|
if (!fullExe.StartsWith(dirWithSep, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Warn("The executable must be located inside the install directory:\n" + fullDir);
|
|
return;
|
|
}
|
|
|
|
Guid id = Guid.NewGuid();
|
|
Product = new ProductDefinition
|
|
{
|
|
Id = id,
|
|
Name = name,
|
|
MenuText = name + "...",
|
|
HostTypeDialog = false
|
|
};
|
|
Product.Entries.Add(new LaunchEntryDefinition
|
|
{
|
|
LaunchKey = id,
|
|
DisplayName = name,
|
|
Exe = fullExe,
|
|
Args = txtArgs.Text.Trim(),
|
|
WorkingDirectory = fullDir,
|
|
AutoRestart = chkAutoRestart.Checked,
|
|
HostType = AppHostType.None
|
|
});
|
|
|
|
base.DialogResult = DialogResult.OK;
|
|
Close();
|
|
}
|
|
|
|
private void Warn(string message)
|
|
{
|
|
MessageBox.Show(this, message, "Add Product", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
}
|
|
}
|