diff --git a/App.config b/App.config
new file mode 100644
index 0000000..49cc43e
--- /dev/null
+++ b/App.config
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/logo.png b/Assets/logo.png
new file mode 100644
index 0000000..4e1886d
Binary files /dev/null and b/Assets/logo.png differ
diff --git a/CramLink_v2.csproj b/CramLink_v2.csproj
new file mode 100644
index 0000000..f8f6072
--- /dev/null
+++ b/CramLink_v2.csproj
@@ -0,0 +1,20 @@
+
+
+ WinExe
+ net6.0-windows
+ true
+ enable
+ enable
+ C:\TestRepo\CramLink
+ app.manifest
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CramLink_v2.sln b/CramLink_v2.sln
new file mode 100644
index 0000000..019e189
--- /dev/null
+++ b/CramLink_v2.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.35013.160
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CramLink_v2", "CramLink_v2.csproj", "{3C79CF75-0152-4443-A49F-EA94FF67D2B7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3C79CF75-0152-4443-A49F-EA94FF67D2B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3C79CF75-0152-4443-A49F-EA94FF67D2B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3C79CF75-0152-4443-A49F-EA94FF67D2B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3C79CF75-0152-4443-A49F-EA94FF67D2B7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {87E85B4D-7A8D-49DB-88F0-AB28E404CE70}
+ EndGlobalSection
+EndGlobal
diff --git a/MainForm.Designer.cs b/MainForm.Designer.cs
new file mode 100644
index 0000000..7b41e2c
--- /dev/null
+++ b/MainForm.Designer.cs
@@ -0,0 +1,126 @@
+using System.Windows.Forms;
+
+namespace CramLinkClientGUI
+{
+ public partial class MainForm
+ {
+ private System.ComponentModel.IContainer components = null;
+
+ private ComboBox siteComboBox;
+ private Button connectButton;
+ private Label statusLabel;
+ private TextBox logBox;
+ private Label label1;
+ private Label label2;
+ private Label piStatus;
+ private Label plcStatus;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ components.Dispose();
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ siteComboBox = new ComboBox();
+ connectButton = new Button();
+ statusLabel = new Label();
+ logBox = new TextBox();
+ label1 = new Label();
+ label2 = new Label();
+ piStatus = new Label();
+ plcStatus = new Label();
+ SuspendLayout();
+ //
+ // siteComboBox
+ //
+ siteComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
+ siteComboBox.FormattingEnabled = true;
+ siteComboBox.Location = new Point(12, 12);
+ siteComboBox.Name = "siteComboBox";
+ siteComboBox.Size = new Size(300, 23);
+ siteComboBox.TabIndex = 0;
+ //
+ // connectButton
+ //
+ connectButton.Location = new Point(330, 12);
+ connectButton.Name = "connectButton";
+ connectButton.Size = new Size(100, 23);
+ connectButton.TabIndex = 1;
+ connectButton.Text = "Connect";
+ connectButton.Click += connectButton_Click;
+ //
+ // statusLabel
+ //
+ statusLabel.AutoSize = true;
+ statusLabel.Location = new Point(450, 17);
+ statusLabel.Name = "statusLabel";
+ statusLabel.Size = new Size(79, 15);
+ statusLabel.TabIndex = 2;
+ statusLabel.Text = "Disconnected";
+ //
+ // logBox
+ //
+ logBox.Location = new Point(12, 50);
+ logBox.Multiline = true;
+ logBox.Name = "logBox";
+ logBox.ReadOnly = true;
+ logBox.ScrollBars = ScrollBars.Vertical;
+ logBox.Size = new Size(600, 300);
+ logBox.TabIndex = 3;
+ //
+ // label1
+ //
+ label1.Location = new Point(12, 360);
+ label1.Name = "label1";
+ label1.Size = new Size(59, 23);
+ label1.TabIndex = 4;
+ label1.Text = "Pi Status:";
+ //
+ // label2
+ //
+ label2.Location = new Point(101, 360);
+ label2.Name = "label2";
+ label2.Size = new Size(70, 23);
+ label2.TabIndex = 6;
+ label2.Text = "PLC Status:";
+ //
+ // piStatus
+ //
+ piStatus.Font = new Font("Segoe UI Emoji", 12F, FontStyle.Regular, GraphicsUnit.Point);
+ piStatus.Location = new Point(65, 358);
+ piStatus.Name = "piStatus";
+ piStatus.Size = new Size(30, 23);
+ piStatus.TabIndex = 5;
+ piStatus.Text = "🔴";
+ //
+ // plcStatus
+ //
+ plcStatus.Font = new Font("Segoe UI Emoji", 12F, FontStyle.Regular, GraphicsUnit.Point);
+ plcStatus.Location = new Point(168, 358);
+ plcStatus.Name = "plcStatus";
+ plcStatus.Size = new Size(31, 23);
+ plcStatus.TabIndex = 7;
+ plcStatus.Text = "🔴";
+ //
+ // MainForm
+ //
+ ClientSize = new Size(630, 390);
+ Controls.Add(siteComboBox);
+ Controls.Add(connectButton);
+ Controls.Add(statusLabel);
+ Controls.Add(logBox);
+ Controls.Add(label1);
+ Controls.Add(piStatus);
+ Controls.Add(label2);
+ Controls.Add(plcStatus);
+ Name = "MainForm";
+ Text = "CramLink VPN Client";
+ ResumeLayout(false);
+ PerformLayout();
+ }
+ }
+}
+
diff --git a/MainForm.cs b/MainForm.cs
new file mode 100644
index 0000000..658d5fb
--- /dev/null
+++ b/MainForm.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Net.NetworkInformation;
+using System.Windows.Forms;
+
+namespace CramLinkClientGUI
+{
+ public partial class MainForm : Form
+ {
+ private Process vpnProcess;
+ private System.Windows.Forms.Timer pingTimer = new System.Windows.Forms.Timer();
+
+ public MainForm()
+ {
+ InitializeComponent();
+
+ statusLabel.Text = "Disconnected";
+ siteComboBox.Items.AddRange(new[]
+ {
+ "CramLink 1 - HQ",
+ "CramLink 2 - N/A"
+ });
+ siteComboBox.SelectedIndex = 0;
+
+ pingTimer.Interval = 5000;
+ pingTimer.Tick += PingTargets;
+ }
+
+ private void connectButton_Click(object sender, EventArgs e)
+ {
+ if (vpnProcess != null && !vpnProcess.HasExited)
+ {
+ vpnProcess.Kill();
+ vpnProcess.Dispose();
+ vpnProcess = null;
+ statusLabel.Text = "Disconnected";
+ connectButton.Text = "Connect";
+ logBox.AppendText("VPN disconnected.\r\n");
+ pingTimer.Stop();
+ return;
+ }
+
+ string configPath = "C:\\CramLinkVPN\\client.ovpn";
+ string openvpnPath = "C:\\CramLinkVPN\\openvpn.exe";
+
+ if (!File.Exists(configPath) || !File.Exists(openvpnPath))
+ {
+ MessageBox.Show("OpenVPN executable or config not found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
+
+ vpnProcess = new Process();
+ vpnProcess.StartInfo.FileName = openvpnPath;
+ vpnProcess.StartInfo.Arguments = "--config \"" + configPath + "\"";
+ vpnProcess.StartInfo.RedirectStandardOutput = true;
+ vpnProcess.StartInfo.RedirectStandardError = true;
+ vpnProcess.StartInfo.UseShellExecute = false;
+ vpnProcess.StartInfo.CreateNoWindow = true;
+
+ vpnProcess.OutputDataReceived += (s, args) => HandleVPNOutput(args.Data);
+ vpnProcess.ErrorDataReceived += (s, args) => HandleVPNOutput(args.Data);
+
+ vpnProcess.Start();
+ vpnProcess.BeginOutputReadLine();
+ vpnProcess.BeginErrorReadLine();
+
+ connectButton.Text = "Disconnect";
+ statusLabel.Text = "Connecting...";
+ logBox.AppendText("Connecting to VPN...\r\n");
+ }
+
+ private void HandleVPNOutput(string data)
+ {
+ if (data == null) return;
+ Invoke(new Action(() =>
+ {
+ logBox.AppendText(data + "\r\n");
+ if (data.Contains("Initialization Sequence Completed"))
+ {
+ statusLabel.Text = "Connected";
+ pingTimer.Start();
+ }
+ }));
+ }
+
+ private void PingTargets(object sender, EventArgs e)
+ {
+ bool piOnline = PingHost("192.168.0.53");
+ bool plcOnline = PingHost("192.168.0.98");
+ piStatus.ForeColor = piOnline ? System.Drawing.Color.Green : System.Drawing.Color.Red;
+ plcStatus.ForeColor = plcOnline ? System.Drawing.Color.Green : System.Drawing.Color.Red;
+ }
+
+ private bool PingHost(string host)
+ {
+ try
+ {
+ using Ping ping = new Ping();
+ PingReply reply = ping.Send(host, 1000);
+ return reply.Status == IPStatus.Success;
+ }
+ catch { return false; }
+ }
+
+ private void plcStatus_Click(object sender, EventArgs e)
+ {
+
+ }
+ }
+}
diff --git a/MainForm.resx b/MainForm.resx
new file mode 100644
index 0000000..af32865
--- /dev/null
+++ b/MainForm.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..59415fb
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Windows.Forms;
+
+namespace CramLinkClientGUI
+{
+ internal static class Program
+ {
+ [STAThread]
+ static void Main()
+ {
+ ApplicationConfiguration.Initialize();
+ Application.Run(new SplashContext());
+ }
+ }
+}
diff --git a/Properties/logo.png b/Properties/logo.png
new file mode 100644
index 0000000..4e1886d
Binary files /dev/null and b/Properties/logo.png differ
diff --git a/SplashContext.cs b/SplashContext.cs
new file mode 100644
index 0000000..ef6bc47
--- /dev/null
+++ b/SplashContext.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Windows.Forms;
+using System.Threading.Tasks;
+
+namespace CramLinkClientGUI
+{
+ public class SplashContext : ApplicationContext
+ {
+ private SplashForm splash;
+ private MainForm main;
+
+ public SplashContext()
+ {
+ splash = new SplashForm();
+ splash.Shown += async (s, e) =>
+ {
+ await Task.Delay(2500);
+ splash.Hide();
+ main = new MainForm();
+ main.FormClosed += (s2, e2) => ExitThread();
+ main.Show();
+ };
+
+ splash.Show();
+ }
+ }
+}
diff --git a/SplashForm.Designer.cs b/SplashForm.Designer.cs
new file mode 100644
index 0000000..dc2d6bd
--- /dev/null
+++ b/SplashForm.Designer.cs
@@ -0,0 +1,49 @@
+namespace CramLinkClientGUI
+{
+ partial class SplashForm
+ {
+ private System.ComponentModel.IContainer components = null;
+ private System.Windows.Forms.PictureBox logoBox;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ this.logoBox = new System.Windows.Forms.PictureBox();
+ ((System.ComponentModel.ISupportInitialize)(this.logoBox)).BeginInit();
+ this.SuspendLayout();
+ //
+ // logoBox
+ //
+ this.logoBox.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.logoBox.Location = new System.Drawing.Point(0, 0);
+ this.logoBox.Name = "logoBox";
+ this.logoBox.Size = new System.Drawing.Size(768, 512);
+ this.logoBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
+ this.logoBox.TabIndex = 0;
+ this.logoBox.TabStop = false;
+ //
+ // SplashForm
+ //
+ this.Opacity = 0;
+ this.TopMost = true;
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(768, 512);
+ this.Controls.Add(this.logoBox);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+ this.Name = "SplashForm";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "SplashForm";
+ ((System.ComponentModel.ISupportInitialize)(this.logoBox)).EndInit();
+ this.ResumeLayout(false);
+ }
+ }
+}
diff --git a/SplashForm.cs b/SplashForm.cs
new file mode 100644
index 0000000..54bc134
--- /dev/null
+++ b/SplashForm.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Drawing;
+using System.IO;
+using System.Reflection;
+using System.Windows.Forms;
+using Timer = System.Windows.Forms.Timer;
+
+namespace CramLinkClientGUI
+{
+ public partial class SplashForm : Form
+ {
+ private Timer fadeTimer;
+ public SplashForm()
+ {
+ InitializeComponent();
+ StartFadeIn();
+ LoadLogo();
+ }
+ private void StartFadeIn()
+ {
+ this.Opacity = 0;
+ fadeTimer = new Timer();
+ fadeTimer.Interval = 30; // ms
+ fadeTimer.Tick += (s, e) =>
+ {
+ if (this.Opacity < 1)
+ this.Opacity += 0.05;
+ else
+ fadeTimer.Stop();
+ };
+ fadeTimer.Start();
+ }
+
+ private void LoadLogo()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ using Stream stream = assembly.GetManifestResourceStream("CramLink_v2.Assets.logo.png");
+ if (stream != null)
+ logoBox.Image = Image.FromStream(stream);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SplashForm.resx b/SplashForm.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/SplashForm.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/app.manifest b/app.manifest
new file mode 100644
index 0000000..fcd4341
--- /dev/null
+++ b/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+