diff --git a/OpenNest.Data/NestStorageSettings.cs b/OpenNest.Data/NestStorageSettings.cs
index f092c5d..f15cf19 100644
--- a/OpenNest.Data/NestStorageSettings.cs
+++ b/OpenNest.Data/NestStorageSettings.cs
@@ -32,6 +32,18 @@ public sealed class NestStorageSettings
public bool IsDatabaseMode =>
Mode == NestStorageMode.Database && !string.IsNullOrWhiteSpace(ServerUrl);
+ /// Validate and normalize a nest server base URL without changing storage mode.
+ public static bool TryNormalizeServerUrl(string? url, out string normalized)
+ {
+ normalized = NormalizeUrl(url);
+ if (Uri.TryCreate(normalized, UriKind.Absolute, out var uri)
+ && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
+ return true;
+
+ normalized = "";
+ return false;
+ }
+
/// %APPDATA%\OpenNest\storage.json.
public static string DefaultPath => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
diff --git a/OpenNest.Tests/Data/NestStorageTests.cs b/OpenNest.Tests/Data/NestStorageTests.cs
index 1a41586..b7a33f9 100644
--- a/OpenNest.Tests/Data/NestStorageTests.cs
+++ b/OpenNest.Tests/Data/NestStorageTests.cs
@@ -59,6 +59,31 @@ public class NestStorageSettingsTests : IDisposable
Assert.False(settings.IsDatabaseMode);
}
+ [Fact]
+ public void TryNormalizeServerUrl_AcceptsHttpsWithoutChangingMode()
+ {
+ var settings = new NestStorageSettings { Mode = NestStorageMode.File };
+
+ Assert.True(NestStorageSettings.TryNormalizeServerUrl(" https://shop.example:8090/ ", out var url));
+ settings.ServerUrl = url;
+ settings.Save(FilePath);
+
+ var loaded = NestStorageSettings.Load(FilePath);
+ Assert.Equal("https://shop.example:8090", loaded.ServerUrl);
+ Assert.Equal(NestStorageMode.File, loaded.Mode);
+ Assert.False(loaded.IsDatabaseMode);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("ftp://shop.example")]
+ [InlineData("not-a-url")]
+ public void TryNormalizeServerUrl_RejectsInvalidAddress(string input)
+ {
+ Assert.False(NestStorageSettings.TryNormalizeServerUrl(input, out var url));
+ Assert.Equal("", url);
+ }
+
public void Dispose()
{
if (Directory.Exists(_directory))
diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs
index 938e7fc..f7bf2da 100644
--- a/OpenNest/Forms/MainForm.cs
+++ b/OpenNest/Forms/MainForm.cs
@@ -646,6 +646,7 @@ namespace OpenNest.Forms
var serverUrl = NestStorage.Settings.ServerUrl;
databaseOpenInProgress = true;
storageModeMenu.Enabled = false;
+ mnuToolsOptions.Enabled = false;
var file = await repository.GetFileAsync(dlg.SelectedId);
if (file == null)
throw new FileNotFoundException("This nest no longer exists on the server.");
@@ -663,7 +664,10 @@ namespace OpenNest.Forms
{
databaseOpenInProgress = false;
if (!IsDisposed)
+ {
storageModeMenu.Enabled = !databaseSaveInProgress;
+ mnuToolsOptions.Enabled = !databaseSaveInProgress;
+ }
}
}
@@ -707,6 +711,7 @@ namespace OpenNest.Forms
{
databaseSaveInProgress = true;
storageModeMenu.Enabled = false;
+ mnuToolsOptions.Enabled = false;
EnableCheck();
try
{
@@ -729,6 +734,7 @@ namespace OpenNest.Forms
if (!IsDisposed)
{
storageModeMenu.Enabled = !databaseOpenInProgress;
+ mnuToolsOptions.Enabled = !databaseOpenInProgress;
EnableCheck();
}
}
diff --git a/OpenNest/Forms/OptionsForm.Designer.cs b/OpenNest/Forms/OptionsForm.Designer.cs
index 3425726..47e5dd0 100644
--- a/OpenNest/Forms/OptionsForm.Designer.cs
+++ b/OpenNest/Forms/OptionsForm.Designer.cs
@@ -41,9 +41,13 @@
this.strategyGroupBox = new System.Windows.Forms.GroupBox();
this.colorSchemeLabel = new System.Windows.Forms.Label();
this.colorSchemeCombo = new System.Windows.Forms.ComboBox();
+ this.serverGroupBox = new System.Windows.Forms.GroupBox();
+ this.serverUrlBox = new System.Windows.Forms.TextBox();
+ this.setServerButton = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
this.tableLayoutPanel1.SuspendLayout();
this.bottomPanel1.SuspendLayout();
+ this.serverGroupBox.SuspendLayout();
this.SuspendLayout();
//
// checkBox1
@@ -135,7 +139,7 @@
this.bottomPanel1.Controls.Add(this.cancelButton);
this.bottomPanel1.Controls.Add(this.saveButton);
this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom;
- this.bottomPanel1.Location = new System.Drawing.Point(0, 368);
+ this.bottomPanel1.Location = new System.Drawing.Point(0, 443);
this.bottomPanel1.Name = "bottomPanel1";
this.bottomPanel1.Size = new System.Drawing.Size(708, 50);
this.bottomPanel1.TabIndex = 1;
@@ -158,7 +162,7 @@
// strategyGroupBox
//
this.strategyGroupBox.Controls.Add(this.strategyGrid);
- this.strategyGroupBox.Location = new System.Drawing.Point(12, 178);
+ this.strategyGroupBox.Location = new System.Drawing.Point(12, 253);
this.strategyGroupBox.Name = "strategyGroupBox";
this.strategyGroupBox.Size = new System.Drawing.Size(684, 180);
this.strategyGroupBox.TabIndex = 2;
@@ -184,13 +188,43 @@
this.colorSchemeCombo.Size = new System.Drawing.Size(130, 24);
this.colorSchemeCombo.TabIndex = 11;
//
+ // serverGroupBox
+ //
+ this.serverGroupBox.Controls.Add(this.serverUrlBox);
+ this.serverGroupBox.Controls.Add(this.setServerButton);
+ this.serverGroupBox.Location = new System.Drawing.Point(12, 178);
+ this.serverGroupBox.Name = "serverGroupBox";
+ this.serverGroupBox.Size = new System.Drawing.Size(684, 69);
+ this.serverGroupBox.TabIndex = 1;
+ this.serverGroupBox.TabStop = false;
+ this.serverGroupBox.Text = "Nest server URL (File / Database mode is selected under File > Storage Mode)";
+ //
+ // serverUrlBox
+ //
+ this.serverUrlBox.Location = new System.Drawing.Point(12, 29);
+ this.serverUrlBox.Name = "serverUrlBox";
+ this.serverUrlBox.PlaceholderText = "http://server:8090";
+ this.serverUrlBox.Size = new System.Drawing.Size(485, 22);
+ this.serverUrlBox.TabIndex = 0;
+ //
+ // setServerButton
+ //
+ this.setServerButton.Location = new System.Drawing.Point(504, 26);
+ this.setServerButton.Name = "setServerButton";
+ this.setServerButton.Size = new System.Drawing.Size(168, 28);
+ this.setServerButton.TabIndex = 1;
+ this.setServerButton.Text = "Set / Change Server";
+ this.setServerButton.UseVisualStyleBackColor = true;
+ this.setServerButton.Click += new System.EventHandler(this.SetServer_Click);
+ //
// OptionsForm
//
this.AcceptButton = this.saveButton;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.CancelButton = this.cancelButton;
- this.ClientSize = new System.Drawing.Size(708, 418);
+ this.ClientSize = new System.Drawing.Size(708, 493);
this.Controls.Add(this.strategyGroupBox);
+ this.Controls.Add(this.serverGroupBox);
this.Controls.Add(this.tableLayoutPanel1);
this.Controls.Add(this.bottomPanel1);
this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
@@ -206,6 +240,8 @@
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.bottomPanel1.ResumeLayout(false);
+ this.serverGroupBox.ResumeLayout(false);
+ this.serverGroupBox.PerformLayout();
this.ResumeLayout(false);
}
@@ -224,5 +260,8 @@
private System.Windows.Forms.GroupBox strategyGroupBox;
private System.Windows.Forms.Label colorSchemeLabel;
private System.Windows.Forms.ComboBox colorSchemeCombo;
+ private System.Windows.Forms.GroupBox serverGroupBox;
+ private System.Windows.Forms.TextBox serverUrlBox;
+ private System.Windows.Forms.Button setServerButton;
}
}
\ No newline at end of file
diff --git a/OpenNest/Forms/OptionsForm.cs b/OpenNest/Forms/OptionsForm.cs
index 1ea4579..95fec44 100644
--- a/OpenNest/Forms/OptionsForm.cs
+++ b/OpenNest/Forms/OptionsForm.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
+using OpenNest.Data;
using OpenNest.Engine.Strategies;
using OpenNest.Properties;
@@ -72,6 +73,7 @@ namespace OpenNest.Forms
private void LoadSettings()
{
+ serverUrlBox.Text = NestStorage.Settings.ServerUrl;
checkBox1.Checked = Settings.Default.CreateNewNestOnOpen;
numericUpDown1.Value = (decimal)Settings.Default.AutoSizePlateFactor;
@@ -142,5 +144,28 @@ namespace OpenNest.Forms
{
SaveSettings();
}
+
+ private void SetServer_Click(object sender, EventArgs e)
+ {
+ if (!NestStorageSettings.TryNormalizeServerUrl(serverUrlBox.Text, out var url))
+ {
+ MessageBox.Show(this, "Enter a valid http or https server URL.",
+ "Server URL", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ try
+ {
+ NestStorage.Save(NestStorage.Settings.Mode, url);
+ serverUrlBox.Text = NestStorage.Settings.ServerUrl;
+ MessageBox.Show(this, "Server URL saved. Storage mode has not changed.",
+ "Server URL", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(this, $"Could not save the server URL: {ex.Message}",
+ "Server URL", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
}
}
diff --git a/docs/nest-storage.md b/docs/nest-storage.md
index e30b588..e831cb9 100644
--- a/docs/nest-storage.md
+++ b/docs/nest-storage.md
@@ -241,6 +241,10 @@ curl --fail http://:/healthz # {"status":"ok"}
In the desktop app choose **File > Storage Mode...**, Database, and enter the base
URL `http://:`, without `/healthz` or `/api/nests`.
+To set or change just the saved server address without changing File/Database mode,
+use **Tools > Options > Nest server URL > Set / Change Server**. This saves the URL
+immediately, independently of the Options Save button; it does not migrate nests
+or enable Database mode. Canceling Options afterward does not undo that address.
### Backup, restore, and upgrade