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)