From 549842477f64d8ceb6b4aa27d9ad8ddf84f4d3ac Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Dec 2025 06:01:10 +0000 Subject: [PATCH] feat: Improve upload dialog and server asset management - Make login button always visible in upload dialog (shows "Login" or "Re-Login") - Add "Test URL" button to verify server connectivity before uploading - Automatically create assets folder structure on server startup - Creates main assets directory - Creates client and editor subdirectories This fixes the issue where users couldn't see the login button and had no way to verify their server URL was correct. The server will now automatically create the required assets folders on startup, preventing upload errors. --- .../Forms/FrmUploadToServer.Designer.cs | 17 +++- Intersect.Editor/Forms/FrmUploadToServer.cs | 99 ++++++++++++++++++- Intersect.Server/Web/ApiService.cs | 58 +++++++++++ 3 files changed, 170 insertions(+), 4 deletions(-) diff --git a/Intersect.Editor/Forms/FrmUploadToServer.Designer.cs b/Intersect.Editor/Forms/FrmUploadToServer.Designer.cs index 120935b4e4..f0aef0d4ae 100644 --- a/Intersect.Editor/Forms/FrmUploadToServer.Designer.cs +++ b/Intersect.Editor/Forms/FrmUploadToServer.Designer.cs @@ -45,6 +45,7 @@ private void InitializeComponent() this.lblStatus = new DarkUI.Controls.DarkLabel(); this.grpUploadType = new DarkUI.Controls.DarkGroupBox(); this.btnLogin = new DarkUI.Controls.DarkButton(); + this.btnTestUrl = new DarkUI.Controls.DarkButton(); this.grpUploadType.SuspendLayout(); this.SuspendLayout(); // @@ -65,7 +66,7 @@ private void InitializeComponent() this.txtServerUrl.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); this.txtServerUrl.Location = new System.Drawing.Point(15, 31); this.txtServerUrl.Name = "txtServerUrl"; - this.txtServerUrl.Size = new System.Drawing.Size(457, 20); + this.txtServerUrl.Size = new System.Drawing.Size(363, 20); this.txtServerUrl.TabIndex = 1; // // lblUploadType @@ -159,6 +160,16 @@ private void InitializeComponent() this.lblStatus.Size = new System.Drawing.Size(0, 13); this.lblStatus.TabIndex = 8; // + // btnTestUrl + // + this.btnTestUrl.Location = new System.Drawing.Point(384, 29); + this.btnTestUrl.Name = "btnTestUrl"; + this.btnTestUrl.Padding = new System.Windows.Forms.Padding(5); + this.btnTestUrl.Size = new System.Drawing.Size(88, 23); + this.btnTestUrl.TabIndex = 12; + this.btnTestUrl.Text = "Test URL"; + this.btnTestUrl.Click += new System.EventHandler(this.btnTestUrl_Click); + // // btnLogin // this.btnLogin.Location = new System.Drawing.Point(15, 260); @@ -167,7 +178,7 @@ private void InitializeComponent() this.btnLogin.Size = new System.Drawing.Size(85, 28); this.btnLogin.TabIndex = 11; this.btnLogin.Text = "Login"; - this.btnLogin.Visible = false; + this.btnLogin.Visible = true; this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click); // // btnUpload @@ -197,6 +208,7 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(484, 300); + this.Controls.Add(this.btnTestUrl); this.Controls.Add(this.btnLogin); this.Controls.Add(this.btnClose); this.Controls.Add(this.btnUpload); @@ -237,5 +249,6 @@ private void InitializeComponent() private DarkLabel lblStatus; private DarkGroupBox grpUploadType; private DarkButton btnLogin; + private DarkButton btnTestUrl; } } diff --git a/Intersect.Editor/Forms/FrmUploadToServer.cs b/Intersect.Editor/Forms/FrmUploadToServer.cs index 2497ee8818..83de87ea8e 100644 --- a/Intersect.Editor/Forms/FrmUploadToServer.cs +++ b/Intersect.Editor/Forms/FrmUploadToServer.cs @@ -94,14 +94,17 @@ private void UpdateAuthenticationStatus() if (_tokenResponse != null) { lblStatus.Text = "✓ Authenticated"; - btnLogin.Visible = false; + btnLogin.Text = "Re-Login"; } else { lblStatus.Text = "⚠ Not authenticated - click Login to authenticate"; - btnLogin.Visible = true; + btnLogin.Text = "Login"; } + // Login button is always visible now + btnLogin.Visible = true; + // Force UI refresh btnLogin.Refresh(); lblStatus.Refresh(); @@ -796,6 +799,98 @@ private async Task PerformUpload() ); } + private async void btnTestUrl_Click(object sender, EventArgs e) + { + if (string.IsNullOrWhiteSpace(txtServerUrl.Text)) + { + DarkMessageBox.ShowError( + "Please enter a server URL to test.", + "Server URL Required", + DarkDialogButton.Ok, + Icon + ); + return; + } + + btnTestUrl.Enabled = false; + var originalStatus = lblStatus.Text; + lblStatus.Text = "Testing server URL..."; + + try + { + var serverUrl = txtServerUrl.Text.TrimEnd('/'); + using var httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(10) + }; + + // Try to reach the server's API endpoint + var testEndpoint = $"{serverUrl}/api/v1/info"; + var response = await httpClient.GetAsync(testEndpoint); + + if (response.IsSuccessStatusCode) + { + lblStatus.Text = "✓ Server URL is reachable"; + DarkMessageBox.ShowInformation( + "The server URL is valid and reachable!", + "Test Successful", + DarkDialogButton.Ok, + Icon + ); + } + else + { + lblStatus.Text = $"⚠ Server responded with status: {response.StatusCode}"; + DarkMessageBox.ShowWarning( + $"The server responded but returned status code: {response.StatusCode}\n\n" + + "The URL may still work for uploading if the API is configured correctly.", + "Server Response", + DarkDialogButton.Ok, + Icon + ); + } + } + catch (HttpRequestException ex) + { + lblStatus.Text = "✗ Failed to reach server"; + DarkMessageBox.ShowError( + $"Could not connect to the server:\n\n{ex.Message}\n\n" + + "Please check the URL and ensure the server is running.", + "Connection Failed", + DarkDialogButton.Ok, + Icon + ); + } + catch (TaskCanceledException) + { + lblStatus.Text = "✗ Connection timed out"; + DarkMessageBox.ShowError( + "The connection to the server timed out.\n\n" + + "Please check the URL and ensure the server is running.", + "Connection Timeout", + DarkDialogButton.Ok, + Icon + ); + } + catch (Exception ex) + { + lblStatus.Text = "✗ Test failed"; + DarkMessageBox.ShowError( + $"An error occurred while testing the URL:\n\n{ex.Message}", + "Test Error", + DarkDialogButton.Ok, + Icon + ); + } + finally + { + btnTestUrl.Enabled = true; + // Restore authentication status after a delay + await Task.Delay(3000); + UpdateAuthenticationStatus(); + } + } + private void btnClose_Click(object sender, EventArgs e) { Close(); diff --git a/Intersect.Server/Web/ApiService.cs b/Intersect.Server/Web/ApiService.cs index b3a0878a3b..02bbb011b5 100644 --- a/Intersect.Server/Web/ApiService.cs +++ b/Intersect.Server/Web/ApiService.cs @@ -715,6 +715,9 @@ private async Task StartAsync(CancellationToken cancellationToken = default) return; } + // Ensure assets folder exists + EnsureAssetDirectoriesExist(app); + _app = app; await app.StartAsync(cancellationToken); } @@ -725,6 +728,61 @@ private async Task StartAsync(CancellationToken cancellationToken = default) } } + private void EnsureAssetDirectoriesExist(WebApplication app) + { + try + { + var updateServerSection = app.Configuration.GetSection(GetOptionsName()); + var updateServerOptions = updateServerSection.Get(); + + if (updateServerOptions?.Enabled != true) + { + return; + } + + var assetRoot = updateServerOptions.AssetRoot ?? "assets"; + var assetRootPath = Path.Combine(app.Environment.ContentRootPath, assetRoot); + + // Create the main assets directory if it doesn't exist + if (!Directory.Exists(assetRootPath)) + { + Directory.CreateDirectory(assetRootPath); + ApplicationContext.Context.Value?.Logger.LogInformation( + "Created assets directory at: {AssetRootPath}", + assetRootPath + ); + } + + // Create client and editor subdirectories + var clientAssetsPath = Path.Combine(assetRootPath, "client"); + if (!Directory.Exists(clientAssetsPath)) + { + Directory.CreateDirectory(clientAssetsPath); + ApplicationContext.Context.Value?.Logger.LogInformation( + "Created client assets directory at: {ClientAssetsPath}", + clientAssetsPath + ); + } + + var editorAssetsPath = Path.Combine(assetRootPath, "editor"); + if (!Directory.Exists(editorAssetsPath)) + { + Directory.CreateDirectory(editorAssetsPath); + ApplicationContext.Context.Value?.Logger.LogInformation( + "Created editor assets directory at: {EditorAssetsPath}", + editorAssetsPath + ); + } + } + catch (Exception exception) + { + ApplicationContext.Context.Value?.Logger.LogWarning( + exception, + "Failed to create assets directories" + ); + } + } + private async Task StopAsync(CancellationToken cancellationToken = default) { if (_app != default)