diff --git a/WebHandler/ServiceTrackerWebHandler.cs b/WebHandler/ServiceTrackerWebHandler.cs index b5e3a0f..080824b 100644 --- a/WebHandler/ServiceTrackerWebHandler.cs +++ b/WebHandler/ServiceTrackerWebHandler.cs @@ -13,14 +13,8 @@ namespace Disco.Plugins.ServiceTracker.WebHandler { private ServiceTrackerDataStore GetDataStore() { - var dataPath = PluginConfigurationHandler.GetPluginDataDirectory( - HostController.HttpContext.Application["Disco.Plugins.ServiceTracker"] as Plugin - ?? new ServiceTrackerPlugin()); - if (string.IsNullOrEmpty(dataPath)) - { - dataPath = System.IO.Path.Combine( - AppDomain.CurrentDomain.BaseDirectory, "App_Data", "Plugins", "Disco.Plugins.ServiceTracker"); - } + var dataPath = System.IO.Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, "App_Data", "Plugins", "Disco.Plugins.ServiceTracker"); return new ServiceTrackerDataStore(dataPath); } @@ -77,7 +71,7 @@ namespace Disco.Plugins.ServiceTracker.WebHandler DateTime etaParsed; if (DateTime.TryParse(HostController.Request.Form["eta"], out etaParsed)) eta = etaParsed; - var currentUser = HostController.HttpContext.User?.Identity?.Name ?? "system"; + var currentUser = GetCurrentUser(); service.UpdateTicket(jobId, priorityId, locationId, techId, eta, status, summary, currentUser); return new RedirectResult("/Plugin/Disco.Plugins.ServiceTracker/Dashboard"); } @@ -93,7 +87,7 @@ namespace Disco.Plugins.ServiceTracker.WebHandler return new HttpStatusCodeResult(400); var content = HostController.Request.Form["note"]; var noteType = HostController.Request.Form["noteType"] ?? "general"; - var currentUser = HostController.HttpContext.User?.Identity?.Name ?? "system"; + var currentUser = GetCurrentUser(); service.AddNote(jobId, currentUser, currentUser, content, noteType); return new RedirectResult("/Plugin/Disco.Plugins.ServiceTracker/Detail?id=" + jobId); } @@ -127,19 +121,28 @@ namespace Disco.Plugins.ServiceTracker.WebHandler sb.AppendLine("JobId,Device,User,Priority,Location,Status,AssignedTech,OpenedDate,ETA,SlaDeadline,SlaBreached,AgeDays,Summary,NoteCount"); foreach (var t in model.Tiles) { + var etaStr = t.EstimatedCompletion.HasValue ? t.EstimatedCompletion.Value.ToString("yyyy-MM-dd") : ""; + var slaStr = t.SlaDeadline.HasValue ? t.SlaDeadline.Value.ToString("yyyy-MM-dd HH:mm") : ""; sb.AppendLine(string.Join(",", t.JobId, Csv(t.DeviceSerialNumber), Csv(t.UserDisplayName), Csv(t.PriorityName), Csv(t.LocationName), Csv(t.StatusOverride), Csv(t.AssignedTechName), t.OpenedDate.ToString("yyyy-MM-dd"), - t.EstimatedCompletion?.ToString("yyyy-MM-dd") ?? "", - t.SlaDeadline?.ToString("yyyy-MM-dd HH:mm") ?? "", - t.IsSlaBreached, t.AgeDays, Csv(t.Summary), t.NoteCount)); + etaStr, slaStr, t.IsSlaBreached, t.AgeDays, Csv(t.Summary), t.NoteCount)); } var fileName = "ServiceTracker_Export_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".csv"; HostController.Response.Headers.Add("Content-Disposition", "attachment; filename=\"" + fileName + "\""); return new ContentResult { Content = sb.ToString(), ContentType = "text/csv", ContentEncoding = Encoding.UTF8 }; } + // --- Helpers --- + + private string GetCurrentUser() + { + if (HostController.HttpContext.User != null && HostController.HttpContext.User.Identity != null) + return HostController.HttpContext.User.Identity.Name ?? "system"; + return "system"; + } + private ActionResult HtmlResult(string html) { return new ContentResult { Content = html, ContentType = "text/html", ContentEncoding = Encoding.UTF8 }; @@ -147,15 +150,50 @@ namespace Disco.Plugins.ServiceTracker.WebHandler private string Csv(string v) { return "\"" + (v ?? "").Replace("\"", "\"\"") + "\""; } private string H(string v) { return string.IsNullOrEmpty(v) ? "" : HttpUtility.HtmlEncode(v); } + // --- Safe accessors for job navigation properties (C#5 compatible) --- + private string SafeDeviceDomainId(Disco.Models.Repository.Job job) + { + return job.Device != null ? job.Device.DeviceDomainId : null; + } + private string SafeDeviceModelDesc(Disco.Models.Repository.Job job) + { + return (job.Device != null && job.Device.DeviceModel != null) ? job.Device.DeviceModel.Description : null; + } + private string SafeUserDisplay(Disco.Models.Repository.Job job) + { + return job.User != null ? job.User.DisplayName : job.UserId; + } + private string SafeJobTypeDesc(Disco.Models.Repository.Job job) + { + return job.JobType != null ? job.JobType.Description : job.JobTypeId; + } + private string SafeTechDisplay(Disco.Models.Repository.Job job) + { + return job.OpenedTechUser != null ? job.OpenedTechUser.DisplayName : job.OpenedTechUserId; + } + private string SafeTicketStr(ServiceTicket ticket, string field) + { + if (ticket == null) return null; + switch (field) + { + case "PriorityId": return ticket.PriorityId; + case "LocationId": return ticket.LocationId; + case "AssignedTechId": return ticket.AssignedTechId; + case "StatusOverride": return ticket.StatusOverride; + case "Summary": return ticket.Summary; + default: return null; + } + } + + // --- HTML Builders --- + private string BuildDashboardPage(DashboardViewModel model) { var pluginUrl = "/Plugin/Disco.Plugins.ServiceTracker"; var sb = new StringBuilder(); sb.Append(""); sb.Append("Service Tracker Dashboard"); - sb.Append(""); + sb.Append(""); // Header sb.Append("
"); @@ -221,10 +259,14 @@ namespace Disco.Plugins.ServiceTracker.WebHandler // Tile Grid sb.Append("
"); if (model.Tiles.Count == 0) + { sb.Append("
No open jobs found
"); + } else + { foreach (var tile in model.Tiles) sb.Append(BuildTileHtml(tile, pluginUrl)); + } sb.Append("
"); // Tech Workload @@ -314,13 +356,14 @@ namespace Disco.Plugins.ServiceTracker.WebHandler sb.Append("

Job #" + job.Id + "

"); sb.Append("
"); - // Left column + // Left column - Job info sb.Append("

Job Details

"); - sb.Append(""); - sb.Append(""); - sb.Append(""); - sb.Append(""); - sb.Append(""); + var domainId = SafeDeviceDomainId(job); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append(""); if (job.ExpectedClosedDate.HasValue) sb.Append(""); if (job.DeviceHeld.HasValue) @@ -328,33 +371,44 @@ namespace Disco.Plugins.ServiceTracker.WebHandler sb.Append("
Device" + H(job.DeviceSerialNumber) + (job.Device?.DeviceDomainId != null ? " (" + H(job.Device.DeviceDomainId) + ")" : "") + "
Model" + H(job.Device?.DeviceModel?.Description) + "
User" + H(job.User?.DisplayName ?? job.UserId) + "
Type" + H(job.JobType?.Description ?? job.JobTypeId) + "
Opened" + job.OpenedDate.ToString("dd MMM yyyy HH:mm") + " by " + H(job.OpenedTechUser?.DisplayName ?? job.OpenedTechUserId) + "
Device" + H(job.DeviceSerialNumber) + (domainId != null ? " (" + H(domainId) + ")" : "") + "
Model" + H(SafeDeviceModelDesc(job)) + "
User" + H(SafeUserDisplay(job)) + "
Type" + H(SafeJobTypeDesc(job)) + "
Opened" + job.OpenedDate.ToString("dd MMM yyyy HH:mm") + " by " + H(SafeTechDisplay(job)) + "
Expected Close" + job.ExpectedClosedDate.Value.ToString("dd MMM yyyy") + "
"); // Edit form + var ticketPriority = SafeTicketStr(ticket, "PriorityId") ?? config.DefaultPriorityId; + var ticketLocation = SafeTicketStr(ticket, "LocationId") ?? config.DefaultLocationId; + var ticketStatus = SafeTicketStr(ticket, "StatusOverride"); + var ticketTech = SafeTicketStr(ticket, "AssignedTechId") ?? ""; + var ticketSummary = SafeTicketStr(ticket, "Summary") ?? ""; + var ticketEta = (ticket != null && ticket.EstimatedCompletion.HasValue) ? ticket.EstimatedCompletion.Value.ToString("yyyy-MM-dd") : ""; + sb.Append("

Service Tracker Settings

"); sb.Append("
"); sb.Append(""); + sb.Append("
"); + sb.Append("
"); + sb.Append("
"); - sb.Append("
"); - sb.Append("
"); - sb.Append("
"); + + sb.Append("
"); + sb.Append("
"); + sb.Append("
"); sb.Append(""); sb.Append("
"); @@ -367,15 +421,22 @@ namespace Disco.Plugins.ServiceTracker.WebHandler sb.Append(""); sb.Append(""); sb.Append("
"); - if (ticket?.Notes != null && ticket.Notes.Count > 0) + + if (ticket != null && ticket.Notes != null && ticket.Notes.Count > 0) { sb.Append("
"); foreach (var note in ticket.Notes.OrderByDescending(n => n.Timestamp)) { string tc = "#337AB7"; - switch (note.NoteType) { case "escalation": tc = "#DC3545"; break; case "resolution": tc = "#28A745"; break; case "update": tc = "#FFC107"; break; } + switch (note.NoteType) + { + case "escalation": tc = "#DC3545"; break; + case "resolution": tc = "#28A745"; break; + case "update": tc = "#FFC107"; break; + } + var authorDisplay = note.AuthorName != null ? note.AuthorName : note.AuthorId; sb.Append("
"); - sb.Append("
" + H(note.AuthorName ?? note.AuthorId) + ""); + sb.Append("
" + H(authorDisplay) + ""); sb.Append("" + H(note.NoteType) + ""); sb.Append("" + note.Timestamp.ToString("dd MMM HH:mm") + "
"); sb.Append("
" + H(note.Content) + "
"); @@ -383,7 +444,9 @@ namespace Disco.Plugins.ServiceTracker.WebHandler sb.Append("
"); } else + { sb.Append("

No notes yet.

"); + } sb.Append("
"); sb.Append(""); return sb.ToString();