diff --git a/Disco.BI/BI/DeviceBI/Enrol.cs b/Disco.BI/BI/DeviceBI/Enrol.cs index 4b1f56a0..6dd8c8f6 100644 --- a/Disco.BI/BI/DeviceBI/Enrol.cs +++ b/Disco.BI/BI/DeviceBI/Enrol.cs @@ -501,9 +501,12 @@ namespace Disco.BI.DeviceBI } else { - var computerId = Disco.Services.UserExtensions.SplitUserId(RepoDevice.DeviceDomainId); - response.DeviceDomainName = computerId.Item1; - response.DeviceComputerName = computerId.Item2; + string accountUsername; + ADDomain accountDomain; + ActiveDirectory.ParseDomainAccountId(RepoDevice.DeviceDomainId, out accountUsername, out accountDomain); + + response.DeviceDomainName = accountDomain == null ? null : accountDomain.NetBiosName; + response.DeviceComputerName = accountUsername; } } else @@ -527,16 +530,17 @@ namespace Disco.BI.DeviceBI domain = ActiveDirectory.Context.GetDomainFromDistinguishedName(RepoDevice.DeviceProfile.OrganisationalUnit); var calculatedComputerName = RepoDevice.ComputerNameRender(Database, domain); - var computerNameSplit = Disco.Services.UserExtensions.SplitUserId(calculatedComputerName); + string calculatedAccountUsername; + ActiveDirectory.ParseDomainAccountId(calculatedComputerName, out calculatedAccountUsername); - if (!Request.DeviceComputerName.Equals(computerNameSplit.Item2, StringComparison.OrdinalIgnoreCase)) + if (!Request.DeviceComputerName.Equals(calculatedAccountUsername, StringComparison.OrdinalIgnoreCase)) { EnrolmentLog.LogSessionProgress(sessionId, 50, string.Format("Renaming Device: {0} -> {1}", Request.DeviceComputerName, calculatedComputerName)); EnrolmentLog.LogSessionTaskRenamingDevice(sessionId, Request.DeviceComputerName, calculatedComputerName); RepoDevice.DeviceDomainId = calculatedComputerName; - response.DeviceDomainName = computerNameSplit.Item1; - response.DeviceComputerName = computerNameSplit.Item2; + response.DeviceDomainName = domain.NetBiosName; + response.DeviceComputerName = calculatedAccountUsername; // Create New Account string offlineProvisionDiagnosicInfo; diff --git a/Disco.BI/BI/DocumentTemplateBI/DocumentUniqueIdentifier.cs b/Disco.BI/BI/DocumentTemplateBI/DocumentUniqueIdentifier.cs index c209cb11..41a35f1b 100644 --- a/Disco.BI/BI/DocumentTemplateBI/DocumentUniqueIdentifier.cs +++ b/Disco.BI/BI/DocumentTemplateBI/DocumentUniqueIdentifier.cs @@ -66,14 +66,10 @@ namespace Disco.BI.DocumentTemplateBI } public DocumentUniqueIdentifier(string TemplateTypeId, string DataId, string CreatorId, DateTime TimeStamp, int? Page = null, string Tag = null) { - var creatorId = (string.IsNullOrEmpty(CreatorId) || CreatorId.IndexOf('\\') > -1) - ? CreatorId - : string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, CreatorId); - this.Tag = Tag; this.TemplateTypeId = TemplateTypeId; this.DataId = DataId; - this.CreatorId = creatorId; + this.CreatorId = ActiveDirectory.ParseDomainAccountId(CreatorId); this.TimeStamp = TimeStamp; this.Page = Page ?? 0; } @@ -98,11 +94,7 @@ namespace Disco.BI.DocumentTemplateBI } if (s.Length >= 5) { - var creatorId = s[4]; - if (!string.IsNullOrWhiteSpace(creatorId) && creatorId.IndexOf('\\') < 0) - creatorId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, creatorId); - - this.CreatorId = creatorId; + this.CreatorId = ActiveDirectory.ParseDomainAccountId(s[4]); } if (s.Length >= 6) { @@ -189,13 +181,7 @@ namespace Disco.BI.DocumentTemplateBI } break; case DocumentTemplate.DocumentTemplateScopes.User: - - // Patch for existing documents (before DBv13 - Multi-Domain Support) - // Add default domain to User Ids - if (this.DataId.IndexOf('\\') < 0) - this.DataId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, this.DataId); - - User u = Database.Users.Find(this.DataId); + User u = Database.Users.Find(ActiveDirectory.ParseDomainAccountId(this.DataId)); if (u != null) { this._data = u; diff --git a/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateDevicesManagedGroup.cs b/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateDevicesManagedGroup.cs new file mode 100644 index 00000000..0fba884e --- /dev/null +++ b/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateDevicesManagedGroup.cs @@ -0,0 +1,441 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.BI.DocumentTemplateBI.ManagedGroups +{ + public class DocumentTemplateDevicesManagedGroup : ADManagedGroup + { + private const string KeyFormat = "DocumentTemplate_{0}_Devices"; + private const string DeviceDescriptionFormat = "Devices with a {0} attachment will be added to this Active Directory group."; + private const string DescriptionFormat = "{0}s with a {1} attachment will have any associated devices added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Related Devices Linked Group"; + private const string GroupDescriptionFormat = "{0} [Document Template Devices]"; + + private IDisposable repositorySubscription; + private IDisposable deviceRenameRepositorySubscription; + private IDisposable jobCloseRepositorySubscription; + private IDisposable deviceAssignmentRepositorySubscription; + private string DocumentTemplateId; + private string DocumentTemplateDescription; + private string DocumentTemplateScope; + + public override string Description { get { return GetDescription(DocumentTemplateScope, DocumentTemplateDescription); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, DocumentTemplateDescription); } } + public override bool IncludeFilterBeginDate { get { return true; } } + + private DocumentTemplateDevicesManagedGroup(string Key, ADManagedGroupConfiguration Configuration, DocumentTemplate DocumentTemplate) + : base(Key, Configuration) + { + this.DocumentTemplateId = DocumentTemplate.Id; + this.DocumentTemplateDescription = DocumentTemplate.Description; + this.DocumentTemplateScope = DocumentTemplate.Scope; + } + + public override void Initialize() + { + // Subscribe to changes + switch (DocumentTemplateScope) + { + case DocumentTemplate.DocumentTemplateScopes.Device: + // Observe Device Attachments + repositorySubscription = DocumentTemplateManagedGroups.DeviceScopeRepositoryEvents.Value + .Where(e => ((DeviceAttachment)e.Entity).DocumentTemplateId == DocumentTemplateId) + .Subscribe(ProcessDeviceRepositoryEvent); + break; + case DocumentTemplate.DocumentTemplateScopes.Job: + // Observe Job Attachments + repositorySubscription = DocumentTemplateManagedGroups.UserScopeRepositoryEvents.Value + .Where(e => ((JobAttachment)e.Entity).DocumentTemplateId == DocumentTemplateId) + .Subscribe(ProcessJobRepositoryEvent); + // Observe Job Close/Reopen + jobCloseRepositorySubscription = DocumentTemplateManagedGroups.JobCloseRepositoryEvents.Value + .Subscribe(ProcessJobCloseRepositoryEvent); + break; + case DocumentTemplate.DocumentTemplateScopes.User: + // Observe User Attachments + repositorySubscription = DocumentTemplateManagedGroups.UserScopeRepositoryEvents.Value + .Where(e => ((UserAttachment)e.Entity).DocumentTemplateId == DocumentTemplateId) + .Subscribe(ProcessUserRepositoryEvent); + // Observe Device Assignments + deviceAssignmentRepositorySubscription = DocumentTemplateManagedGroups.DeviceAssignmentRepositoryEvents.Value + .Subscribe(ProcessDeviceAssignmentRepositoryEvent); + break; + } + + // Observe Device Renaming (DeviceDomainId) + deviceRenameRepositorySubscription = DocumentTemplateManagedGroups.DeviceRenameRepositoryEvents.Value + .Subscribe(ProcessDeviceRenameRepositoryEvent); + } + + public static string GetKey(DocumentTemplate DocumentTemplate) + { + return string.Format(KeyFormat, DocumentTemplate.Id); + } + private static string GetDescription(string DocumentTemplateScope, string DocumentTemplateDescription) + { + switch (DocumentTemplateScope) + { + case DocumentTemplate.DocumentTemplateScopes.Device: + return string.Format(DeviceDescriptionFormat, DocumentTemplateDescription); + case DocumentTemplate.DocumentTemplateScopes.Job: + case DocumentTemplate.DocumentTemplateScopes.User: + return string.Format(DescriptionFormat, DocumentTemplateScope, DocumentTemplateDescription); + default: + throw new ArgumentException("Unknown Document Template Scope", "Scope"); + } + } + public static string GetDescription(DocumentTemplate DocumentTemplate) + { + return GetDescription(DocumentTemplate.Scope, DocumentTemplate.Description); + } + public static string GetCategoryDescription(DocumentTemplate DocumentTemplate) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(DocumentTemplate DocumentTemplate, out DocumentTemplateDevicesManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(DocumentTemplate); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (DocumentTemplateDevicesManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static DocumentTemplateDevicesManagedGroup Initialize(DocumentTemplate Template) + { + var key = GetKey(Template); + + if (!string.IsNullOrEmpty(Template.DevicesLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(Template.DevicesLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new DocumentTemplateDevicesManagedGroup( + key, + config, + Template); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + + return null; + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + switch (DocumentTemplateScope) + { + case DocumentTemplate.DocumentTemplateScopes.Device: + return Database.Devices + .Where(d => d.DeviceDomainId != null && d.DeviceAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)) + .Select(d => d.DeviceDomainId) + .ToList() + .Where(ActiveDirectory.IsValidDomainAccountId) + .Select(id => id + "$"); + case DocumentTemplate.DocumentTemplateScopes.Job: + return Database.Jobs + .Where(j => !j.ClosedDate.HasValue && j.Device.DeviceDomainId != null && j.JobAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)) + .Select(j => j.Device.DeviceDomainId) + .Distinct() + .ToList() + .Where(ActiveDirectory.IsValidDomainAccountId) + .Select(id => id + "$"); + case DocumentTemplate.DocumentTemplateScopes.User: + return Database.Users + .Where(u => u.UserAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)) + .SelectMany(u => u.DeviceUserAssignments.Where(dua => !dua.UnassignedDate.HasValue && dua.Device.DeviceDomainId != null), (u, dua) => dua.Device.DeviceDomainId) + .ToList() + .Where(ActiveDirectory.IsValidDomainAccountId) + .Select(id => id + "$"); + default: + return Enumerable.Empty(); + } + } + + #region Device Scope + private bool DeviceContainsAttachment(DiscoDataContext Database, string DeviceSerialNumber, out string DeviceAccountId) + { + var result = Database.Devices + .Where(d => d.SerialNumber == DeviceSerialNumber && d.DeviceDomainId != null) + .Select(d => new Tuple(d.DeviceDomainId, d.DeviceAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId))) + .FirstOrDefault(); + + if (result == null) + { + DeviceAccountId = null; + return false; + } + else + { + if (ActiveDirectory.IsValidDomainAccountId(result.Item1)) + { + DeviceAccountId = result.Item1 + "$"; + return result.Item2; + } + else + { + DeviceAccountId = result.Item1 + "$"; + return false; + } + } + } + + private void ProcessDeviceRepositoryEvent(RepositoryMonitorEvent e) + { + var attachment = (DeviceAttachment)e.Entity; + + string deviceAccountId; + if (DeviceContainsAttachment(e.Database, attachment.DeviceSerialNumber, out deviceAccountId)) + AddMember(attachment.DeviceSerialNumber, (database) => new string[] { deviceAccountId }); + else if (deviceAccountId != null) + RemoveMember(attachment.DeviceSerialNumber, (database) => new string[] { deviceAccountId }); + } + #endregion + + #region Job Scope + private bool JobsContainAttachment(DiscoDataContext Database, int JobId, out string DeviceAccountId, out string DeviceSerialNumber) + { + var result = Database.Jobs + .Where(j => j.Id == JobId && j.Device.DeviceDomainId != null) + .Select(j => new Tuple( + j.Device.DeviceDomainId, + j.Device.SerialNumber, + j.Device.Jobs.Where(dj => !dj.ClosedDate.HasValue).Any(dj => dj.JobAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId))) + ).FirstOrDefault(); + + if (result == null) + { + DeviceAccountId = null; + DeviceSerialNumber = null; + return false; + } + else + { + if (ActiveDirectory.IsValidDomainAccountId(result.Item1)) + { + DeviceAccountId = result.Item1 + "$"; + DeviceSerialNumber = result.Item2; + return result.Item3; + } + else + { + DeviceAccountId = result.Item1 + "$"; + DeviceSerialNumber = result.Item2; + return false; + } + } + } + + private void ProcessJobRepositoryEvent(RepositoryMonitorEvent e) + { + var attachment = (JobAttachment)e.Entity; + + string deviceAccountId; + string deviceSerialNumber; + if (JobsContainAttachment(e.Database, attachment.JobId, out deviceAccountId, out deviceSerialNumber)) + AddMember(deviceSerialNumber, (database) => new string[] { deviceAccountId }); + else if (deviceSerialNumber != null && deviceAccountId != null) + RemoveMember(deviceSerialNumber, (database) => new string[] { deviceAccountId }); + } + #endregion + + #region User Scope + private bool DeviceUserContainAttachment(DiscoDataContext Database, string UserId, out List> Devices) + { + var result = Database.Users + .Where(u => u.UserId == UserId) + .Select(u => new Tuple>>( + u.UserAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId), + u.DeviceUserAssignments + .Where(dua => !dua.UnassignedDate.HasValue && dua.Device.DeviceDomainId != null) + .Select(dua => new Tuple(dua.Device.DeviceDomainId, dua.Device.SerialNumber))) + ).FirstOrDefault(); + + if (result == null) + { + Devices = null; + return false; + } + else + { + Devices = result.Item2 + .Where(d => ActiveDirectory.IsValidDomainAccountId(d.Item1)) + .Select(d => Tuple.Create(d.Item1 + "$", d.Item2)) + .ToList(); + return result.Item1; + } + } + + private void ProcessUserRepositoryEvent(RepositoryMonitorEvent e) + { + var attachment = (UserAttachment)e.Entity; + + List> devices; + if (DeviceUserContainAttachment(e.Database, attachment.UserId, out devices)) + { + if (devices != null) + devices.ForEach(d => AddMember(d.Item2, (database) => new string[] { d.Item1 })); + } + else + { + if (devices != null) + devices.ForEach(d => RemoveMember(d.Item2, (database) => new string[] { d.Item1 })); + } + } + #endregion + + private void ProcessDeviceRenameRepositoryEvent(RepositoryMonitorEvent Event) + { + var device = (Device)Event.Entity; + var deviceSerialNumber = device.SerialNumber; + var deviceAccountId = device.DeviceDomainId; + var deviceAccountIdValid = ActiveDirectory.IsValidDomainAccountId(deviceAccountId); + var devicePreviousAccountId = Event.GetPreviousPropertyValue("DeviceDomainId"); + var devicePreviousAccountIdValid = ActiveDirectory.IsValidDomainAccountId(devicePreviousAccountId); + + if (deviceAccountIdValid || devicePreviousAccountIdValid) + { + Event.ExecuteAfterCommit(e => + { + switch (DocumentTemplateScope) + { + case DocumentTemplate.DocumentTemplateScopes.Device: + if (DeviceContainsAttachment(e.Database, device.SerialNumber, out deviceAccountId)) + { + if (deviceAccountIdValid) + AddMember(device.SerialNumber, (database) => new string[] { deviceAccountId }); + if (devicePreviousAccountIdValid) + RemoveMember(device.SerialNumber, (database) => new string[] { devicePreviousAccountId + "$" }); + } + break; + case DocumentTemplate.DocumentTemplateScopes.Job: + var jobsHaveTemplate = e.Database.Jobs + .Where(j => !j.ClosedDate.HasValue && j.DeviceSerialNumber == deviceSerialNumber) + .Any(j => j.JobAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)); + + if (jobsHaveTemplate) + { + if (deviceAccountIdValid) + AddMember(device.SerialNumber, (database) => new string[] { deviceAccountId + "$" }); + if (devicePreviousAccountIdValid) + RemoveMember(device.SerialNumber, (database) => new string[] { devicePreviousAccountId + "$" }); + } + break; + case DocumentTemplate.DocumentTemplateScopes.User: + var userHasTemplate = e.Database.Devices + .Where(d => d.SerialNumber == deviceSerialNumber) + .Select(d => d.AssignedUser) + .Any(u => u.UserAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)); + + if (userHasTemplate) + { + if (deviceAccountIdValid) + AddMember(device.SerialNumber, (database) => new string[] { deviceAccountId + "$" }); + if (devicePreviousAccountIdValid) + RemoveMember(device.SerialNumber, (database) => new string[] { devicePreviousAccountId + "$" }); + } + break; + } + }); + } + } + + private void ProcessJobCloseRepositoryEvent(RepositoryMonitorEvent e) + { + var job = (Job)e.Entity; + + if (job.DeviceSerialNumber != null) + { + var jobId = job.Id; + + var relevantJob = e.Database.Jobs + .Where(j => j.Id == jobId && j.JobAttachments.Any(ja => ja.DocumentTemplateId == this.DocumentTemplateId)) + .Any(); + + if (relevantJob) + { + string deviceAccountId; + string deviceSerialNumber; + if (JobsContainAttachment(e.Database, jobId, out deviceAccountId, out deviceSerialNumber)) + AddMember(deviceSerialNumber, (database) => new string[] { deviceAccountId }); + else + RemoveMember(deviceSerialNumber, (database) => new string[] { deviceAccountId }); + } + } + } + + private void ProcessDeviceAssignmentRepositoryEvent(RepositoryMonitorEvent Event) + { + var device = (Device)Event.Entity; + var deviceSerialNumber = device.SerialNumber; + var deviceAccountId = device.DeviceDomainId; + + if (ActiveDirectory.IsValidDomainAccountId(deviceAccountId)) + { + var deviceCurrentAssignedUserId = device.AssignedUserId; + var devicePreviousAssignedUserId = Event.GetPreviousPropertyValue("AssignedUserId"); + + Event.ExecuteAfterCommit(e => + { + bool previousUserHasTemplate = false; + bool currentUserHasTemplate = false; + + if (devicePreviousAssignedUserId != null) + previousUserHasTemplate = e.Database.Users + .Where(u => u.UserId == devicePreviousAssignedUserId && u.UserAttachments.Any(ua => ua.DocumentTemplateId == this.DocumentTemplateId)) + .Any(); + + if (deviceCurrentAssignedUserId != null) + currentUserHasTemplate = e.Database.Users + .Where(u => u.UserId == deviceCurrentAssignedUserId && u.UserAttachments.Any(ua => ua.DocumentTemplateId == this.DocumentTemplateId)) + .Any(); + + if (!previousUserHasTemplate && currentUserHasTemplate) + AddMember(deviceSerialNumber, (database) => new string[] { deviceAccountId + "$" }); + else if (previousUserHasTemplate && !currentUserHasTemplate) + RemoveMember(deviceSerialNumber, (database) => new string[] { deviceAccountId + "$" }); + }); + } + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + + if (deviceRenameRepositorySubscription != null) + deviceRenameRepositorySubscription.Dispose(); + + if (jobCloseRepositorySubscription != null) + jobCloseRepositorySubscription.Dispose(); + + if (deviceAssignmentRepositorySubscription != null) + deviceAssignmentRepositorySubscription.Dispose(); + } + } +} diff --git a/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateManagedGroups.cs b/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateManagedGroups.cs new file mode 100644 index 00000000..4538a19f --- /dev/null +++ b/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateManagedGroups.cs @@ -0,0 +1,84 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using System; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.BI.DocumentTemplateBI.ManagedGroups +{ + public static class DocumentTemplateManagedGroups + { + internal static Lazy> DeviceScopeRepositoryEvents; + internal static Lazy> JobScopeRepositoryEvents; + internal static Lazy> UserScopeRepositoryEvents; + + internal static Lazy> DeviceRenameRepositoryEvents; + internal static Lazy> JobCloseRepositoryEvents; + internal static Lazy> DeviceAssignmentRepositoryEvents; + + static DocumentTemplateManagedGroups() + { + DeviceScopeRepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamAfterCommit.Where(e => + e.EntityType == typeof(DeviceAttachment) && + ((DeviceAttachment)e.Entity).DocumentTemplateId != null && ( + (e.EventType == RepositoryMonitorEventType.Added) || + (e.EventType == RepositoryMonitorEventType.Deleted) + ) + )); + JobScopeRepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamAfterCommit.Where(e => + e.EntityType == typeof(JobAttachment) && ( + (e.EventType == RepositoryMonitorEventType.Added) || + (e.EventType == RepositoryMonitorEventType.Deleted) + ) + )); + UserScopeRepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamAfterCommit.Where(e => + e.EntityType == typeof(UserAttachment) && ( + (e.EventType == RepositoryMonitorEventType.Added) || + (e.EventType == RepositoryMonitorEventType.Deleted) + ) + )); + + DeviceRenameRepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamBeforeCommit.Where(e => + e.EntityType == typeof(Device) && + e.EventType == RepositoryMonitorEventType.Modified && + e.ModifiedProperties.Contains("DeviceDomainId") + )); + JobCloseRepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamAfterCommit.Where(e => + e.EntityType == typeof(Job) && + (((Job)e.Entity).DeviceSerialNumber != null || ((Job)e.Entity).UserId != null) && + e.EventType == RepositoryMonitorEventType.Modified && + e.ModifiedProperties.Contains("ClosedDate") + )); + DeviceAssignmentRepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamBeforeCommit.Where(e => + e.EntityType == typeof(Device) && + e.EventType == RepositoryMonitorEventType.Modified && + e.ModifiedProperties.Contains("AssignedUserId") + )); + } + + public static void Initialize(DiscoDataContext Database) + { + Database.DocumentTemplates + .Where(dp => dp.DevicesLinkedGroup != null || dp.UsersLinkedGroup != null) + .ToList() + .ForEach(dp => + { + DocumentTemplateDevicesManagedGroup.Initialize(dp); + DocumentTemplateUsersManagedGroup.Initialize(dp); + }); + } + } +} diff --git a/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateUsersManagedGroup.cs b/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateUsersManagedGroup.cs new file mode 100644 index 00000000..c9bd2506 --- /dev/null +++ b/Disco.BI/BI/DocumentTemplateBI/ManagedGroups/DocumentTemplateUsersManagedGroup.cs @@ -0,0 +1,314 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.BI.DocumentTemplateBI.ManagedGroups +{ + public class DocumentTemplateUsersManagedGroup : ADManagedGroup + { + private const string KeyFormat = "DocumentTemplate_{0}_Users"; + private const string UserDescriptionFormat = "Users with a {0} attachment will be added to this Active Directory group."; + private const string DescriptionFormat = "{0}s with a {1} attachment will have any associated users added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Related Users Linked Group"; + private const string GroupDescriptionFormat = "{0} [Document Template Users]"; + + private IDisposable repositorySubscription; + private IDisposable jobCloseRepositorySubscription; + private IDisposable deviceAssignmentRepositorySubscription; + private string DocumentTemplateId; + private string DocumentTemplateDescription; + private string DocumentTemplateScope; + + public override string Description { get { return GetDescription(DocumentTemplateScope, DocumentTemplateDescription); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, DocumentTemplateDescription); } } + public override bool IncludeFilterBeginDate { get { return true; } } + + private DocumentTemplateUsersManagedGroup(string Key, ADManagedGroupConfiguration Configuration, DocumentTemplate DocumentTemplate) + : base(Key, Configuration) + { + this.DocumentTemplateId = DocumentTemplate.Id; + this.DocumentTemplateDescription = DocumentTemplate.Description; + this.DocumentTemplateScope = DocumentTemplate.Scope; + } + + public override void Initialize() + { + // Subscribe to changes + switch (DocumentTemplateScope) + { + case DocumentTemplate.DocumentTemplateScopes.Device: + // Observe Device Attachments + repositorySubscription = DocumentTemplateManagedGroups.DeviceScopeRepositoryEvents.Value + .Where(e => ((DeviceAttachment)e.Entity).DocumentTemplateId == DocumentTemplateId) + .Subscribe(ProcessDeviceRepositoryEvent); + // Observe Device Assignments + deviceAssignmentRepositorySubscription = DocumentTemplateManagedGroups.DeviceAssignmentRepositoryEvents.Value + .Subscribe(ProcessDeviceAssignmentRepositoryEvent); + break; + case DocumentTemplate.DocumentTemplateScopes.Job: + // Observe Job Attachments + repositorySubscription = DocumentTemplateManagedGroups.UserScopeRepositoryEvents.Value + .Where(e => ((JobAttachment)e.Entity).DocumentTemplateId == DocumentTemplateId) + .Subscribe(ProcessJobRepositoryEvent); + // Observe Job Close/Reopen + jobCloseRepositorySubscription = DocumentTemplateManagedGroups.JobCloseRepositoryEvents.Value + .Subscribe(ProcessJobCloseRepositoryEvent); + break; + case DocumentTemplate.DocumentTemplateScopes.User: + // Observe User Attachments + repositorySubscription = DocumentTemplateManagedGroups.UserScopeRepositoryEvents.Value + .Where(e => ((UserAttachment)e.Entity).DocumentTemplateId == DocumentTemplateId) + .Subscribe(ProcessUserRepositoryEvent); + break; + } + } + + public static string GetKey(DocumentTemplate DocumentTemplate) + { + return string.Format(KeyFormat, DocumentTemplate.Id); + } + private static string GetDescription(string DocumentTemplateScope, string DocumentTemplateDescription) + { + switch (DocumentTemplateScope) + { + case DocumentTemplate.DocumentTemplateScopes.Device: + case DocumentTemplate.DocumentTemplateScopes.Job: + return string.Format(DescriptionFormat, DocumentTemplateScope, DocumentTemplateDescription); + case DocumentTemplate.DocumentTemplateScopes.User: + return string.Format(UserDescriptionFormat, DocumentTemplateDescription); + default: + throw new ArgumentException("Unknown Document Template Scope", "Scope"); + } + } + public static string GetDescription(DocumentTemplate DocumentTemplate) + { + return GetDescription(DocumentTemplate.Scope, DocumentTemplate.Description); + } + public static string GetCategoryDescription(DocumentTemplate DocumentTemplate) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(DocumentTemplate DocumentTemplate, out DocumentTemplateUsersManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(DocumentTemplate); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (DocumentTemplateUsersManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static DocumentTemplateUsersManagedGroup Initialize(DocumentTemplate Template) + { + var key = GetKey(Template); + + if (!string.IsNullOrEmpty(Template.UsersLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(Template.UsersLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new DocumentTemplateUsersManagedGroup( + key, + config, + Template); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + + return null; + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + switch (DocumentTemplateScope) + { + case DocumentTemplate.DocumentTemplateScopes.Device: + return Database.Devices + .Where(d => d.AssignedUserId != null && d.DeviceAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)) + .Select(d => d.AssignedUserId); + case DocumentTemplate.DocumentTemplateScopes.Job: + return Database.Jobs + .Where(j => !j.ClosedDate.HasValue && j.UserId != null && j.JobAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)) + .Select(j => j.UserId) + .Distinct(); + case DocumentTemplate.DocumentTemplateScopes.User: + return Database.Users + .Where(u => u.UserAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)) + .Select(u => u.UserId); + default: + return Enumerable.Empty(); + } + } + + #region Device Scope + private bool DeviceContainsAttachment(DiscoDataContext Database, string DeviceSerialNumber, out string UserId) + { + var result = Database.Devices + .Where(d => d.SerialNumber == DeviceSerialNumber && d.AssignedUser != null) + .Select(d => new Tuple(d.AssignedUserId, d.DeviceAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId))) + .FirstOrDefault(); + + if (result == null) + { + UserId = null; + return false; + } + else + { + UserId = result.Item1; + return result.Item2; + } + } + + private void ProcessDeviceRepositoryEvent(RepositoryMonitorEvent e) + { + var attachment = (DeviceAttachment)e.Entity; + + string userId; + if (DeviceContainsAttachment(e.Database, attachment.DeviceSerialNumber, out userId)) + AddMember(userId, (database) => new string[] { userId }); + else if (userId != null) + RemoveMember(userId, (database) => new string[] { userId }); + } + #endregion + + #region Job Scope + private bool JobsContainAttachment(DiscoDataContext Database, int JobId, out string UserId) + { + var result = Database.Jobs + .Where(j => j.Id == JobId && j.UserId != null) + .Select(j => new Tuple( + j.UserId, + j.User.Jobs.Where(uj => !uj.ClosedDate.HasValue).Any(uj => uj.JobAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId))) + ).FirstOrDefault(); + + if (result == null) + { + UserId = null; + return false; + } + else + { + UserId = result.Item1; + return result.Item2; + } + } + + private void ProcessJobRepositoryEvent(RepositoryMonitorEvent e) + { + var attachment = (JobAttachment)e.Entity; + + string userId; + if (JobsContainAttachment(e.Database, attachment.JobId, out userId)) + AddMember(userId, (database) => new string[] { userId }); + else if (userId != null) + RemoveMember(userId, (database) => new string[] { userId }); + } + #endregion + + #region User Scope + private bool UserContainAttachment(DiscoDataContext Database, string UserId) + { + var result = Database.Users + .Where(u => u.UserId == UserId) + .Any(u => u.UserAttachments.Any(a => a.DocumentTemplateId == this.DocumentTemplateId)); + + return result; + } + + private void ProcessUserRepositoryEvent(RepositoryMonitorEvent e) + { + var attachment = (UserAttachment)e.Entity; + var userId = attachment.UserId; + + if (UserContainAttachment(e.Database, userId)) + AddMember(userId, (database) => new string[] { userId }); + else + RemoveMember(userId, (database) => new string[] { userId }); + } + #endregion + + private void ProcessJobCloseRepositoryEvent(RepositoryMonitorEvent e) + { + var job = (Job)e.Entity; + + if (job.UserId != null) + { + var jobId = job.Id; + + var relevantJob = e.Database.Jobs + .Where(j => j.Id == jobId && j.JobAttachments.Any(ja => ja.DocumentTemplateId == this.DocumentTemplateId)) + .Any(); + + if (relevantJob) + { + string userId; + if (JobsContainAttachment(e.Database, jobId, out userId)) + AddMember(userId, (database) => new string[] { userId }); + else + RemoveMember(userId, (database) => new string[] { userId }); + } + } + } + + private void ProcessDeviceAssignmentRepositoryEvent(RepositoryMonitorEvent Event) + { + var device = (Device)Event.Entity; + var deviceSerialNumber = device.SerialNumber; + + var relevantDevice = Event.Database.Devices + .Where(d => d.SerialNumber == deviceSerialNumber && d.DeviceAttachments.Any(ja => ja.DocumentTemplateId == this.DocumentTemplateId)) + .Any(); + + if (relevantDevice) + { + var deviceCurrentAssignedUserId = device.AssignedUserId; + var devicePreviousAssignedUserId = Event.GetPreviousPropertyValue("AssignedUserId"); + + Event.ExecuteAfterCommit(e => + { + if (devicePreviousAssignedUserId != null) + RemoveMember(devicePreviousAssignedUserId, (database) => new string[] { devicePreviousAssignedUserId }); + + if (deviceCurrentAssignedUserId != null) + AddMember(deviceCurrentAssignedUserId, (database) => new string[] { deviceCurrentAssignedUserId }); + }); + } + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + + if (jobCloseRepositorySubscription != null) + jobCloseRepositorySubscription.Dispose(); + + if (deviceAssignmentRepositorySubscription != null) + deviceAssignmentRepositorySubscription.Dispose(); + } + } +} diff --git a/Disco.BI/BI/Expressions/ExpressionCachePreloadTask.cs b/Disco.BI/BI/Expressions/ExpressionCachePreloadTask.cs index 3efa8fda..fc3c6ce5 100644 --- a/Disco.BI/BI/Expressions/ExpressionCachePreloadTask.cs +++ b/Disco.BI/BI/Expressions/ExpressionCachePreloadTask.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Disco.Services.Tasks; +using Disco.BI.Extensions; using Disco.Data.Repository; +using Disco.Services.Tasks; using Quartz; -using Disco.BI.Extensions; -using System.Diagnostics; +using System; +using System.Linq; namespace Disco.BI.Expressions { @@ -20,7 +17,7 @@ namespace Disco.BI.Expressions public override void InitalizeScheduledTask(DiscoDataContext Database) { - // Run in Background 1 Second after Scheduled (on App Startup) + // Run in Background 5 Second after Scheduled (on App Startup) TriggerBuilder triggerBuilder = TriggerBuilder.Create().StartAt(new DateTimeOffset(DateTime.Now).AddSeconds(5)); this.ScheduleTask(triggerBuilder); @@ -37,8 +34,6 @@ namespace Disco.BI.Expressions documentTemplate.FilterExpressionFromCache(); } } - - } } } diff --git a/Disco.BI/BI/Expressions/Extensions/DeviceExt.cs b/Disco.BI/BI/Expressions/Extensions/DeviceExt.cs index c147f49e..c07d17f8 100644 --- a/Disco.BI/BI/Expressions/Extensions/DeviceExt.cs +++ b/Disco.BI/BI/Expressions/Extensions/DeviceExt.cs @@ -2,6 +2,7 @@ using Disco.Models.Repository; using Disco.Services.Interop.ActiveDirectory; using System; +using System.Linq; namespace Disco.BI.Expressions.Extensions { @@ -11,7 +12,7 @@ namespace Disco.BI.Expressions.Extensions { var adMachineAccount = Device.ActiveDirectoryAccount(PropertyName); if (adMachineAccount != null) - return adMachineAccount.GetPropertyValue(PropertyName, Index); + return adMachineAccount.GetPropertyValues(PropertyName).Skip(Index).FirstOrDefault(); else return null; } diff --git a/Disco.BI/BI/Expressions/Extensions/UserExt.cs b/Disco.BI/BI/Expressions/Extensions/UserExt.cs index a207ae61..e8ece94b 100644 --- a/Disco.BI/BI/Expressions/Extensions/UserExt.cs +++ b/Disco.BI/BI/Expressions/Extensions/UserExt.cs @@ -1,7 +1,7 @@ using Disco.BI.Extensions; using Disco.Models.Repository; -using Disco.Services.Interop.ActiveDirectory; using System; +using System.Linq; namespace Disco.BI.Expressions.Extensions { @@ -11,7 +11,7 @@ namespace Disco.BI.Expressions.Extensions { var adUserAccount = User.ActiveDirectoryAccount(PropertyName); if (adUserAccount != null) - return adUserAccount.GetPropertyValue(PropertyName, Index); + return adUserAccount.GetPropertyValues(PropertyName).Skip(Index).FirstOrDefault(); else return null; } diff --git a/Disco.BI/BI/Extensions/DeviceExtensions.cs b/Disco.BI/BI/Extensions/DeviceExtensions.cs index 6a292939..2559f7cd 100644 --- a/Disco.BI/BI/Extensions/DeviceExtensions.cs +++ b/Disco.BI/BI/Extensions/DeviceExtensions.cs @@ -55,7 +55,7 @@ namespace Disco.BI.Extensions public static bool UpdateLastNetworkLogonDate(this Device Device) { - return Disco.Services.Interop.ActiveDirectory.ADTaskUpdateNetworkLogonDates.UpdateLastNetworkLogonDate(Device); + return Disco.Services.Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDate(Device); } public static DeviceAttachment CreateAttachment(this Device Device, DiscoDataContext Database, User CreatorUser, string Filename, string MimeType, string Comments, Stream Content, DocumentTemplate DocumentTemplate = null, byte[] PdfThumbnail = null) @@ -142,10 +142,7 @@ namespace Disco.BI.Extensions Database.Devices.Add(d2); if (!string.IsNullOrEmpty(d.AssignedUserId)) { - if (!d.AssignedUserId.Contains('\\')) - d.AssignedUserId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, d.AssignedUserId); - - User u = UserService.GetUser(d.AssignedUserId, Database, true); + User u = UserService.GetUser(ActiveDirectory.ParseDomainAccountId(d.AssignedUserId), Database, true); d2.AssignDevice(Database, u); } diff --git a/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs b/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs index d7c2b106..5197b364 100644 --- a/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs +++ b/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs @@ -66,10 +66,8 @@ namespace Disco.BI.Interop.Pdf for (int idIndex = 0; idIndex < DataObjectsIds.Length; idIndex++) { string dataObjectId = DataObjectsIds[idIndex]; - if (!dataObjectId.Contains('\\')) - dataObjectId = ActiveDirectory.Context.PrimaryDomain.NetBiosName + @"\" + dataObjectId; - DataObjects[idIndex] = UserService.GetUser(dataObjectId, Database, true); + DataObjects[idIndex] = UserService.GetUser(ActiveDirectory.ParseDomainAccountId(dataObjectId), Database, true); if (DataObjects[idIndex] == null) throw new Exception(string.Format("Unknown Username specified: {0}", dataObjectId)); } diff --git a/Disco.BI/Disco.BI.csproj b/Disco.BI/Disco.BI.csproj index b0ee39a7..3896446a 100644 --- a/Disco.BI/Disco.BI.csproj +++ b/Disco.BI/Disco.BI.csproj @@ -98,6 +98,9 @@ + + + @@ -204,7 +207,7 @@ - + diff --git a/Disco.Data/Disco.Data.csproj b/Disco.Data/Disco.Data.csproj index 7ffc950b..fb317773 100644 --- a/Disco.Data/Disco.Data.csproj +++ b/Disco.Data/Disco.Data.csproj @@ -139,9 +139,9 @@ 201404080227546_DBv13.cs - - - 201406090652547_DBv14.cs + + + 201406160912525_DBv14.cs @@ -197,8 +197,8 @@ 201404080227546_DBv13.cs - - 201406090652547_DBv14.cs + + 201406160912525_DBv14.cs ResXFileCodeGenerator diff --git a/Disco.Data/Migrations/201406090652547_DBv14.resx b/Disco.Data/Migrations/201406090652547_DBv14.resx deleted file mode 100644 index cc734d75..00000000 --- a/Disco.Data/Migrations/201406090652547_DBv14.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - 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 - - \ No newline at end of file diff --git a/Disco.Data/Migrations/201406090652547_DBv14.Designer.cs b/Disco.Data/Migrations/201406160912525_DBv14.Designer.cs similarity index 92% rename from Disco.Data/Migrations/201406090652547_DBv14.Designer.cs rename to Disco.Data/Migrations/201406160912525_DBv14.Designer.cs index 9bd62ca6..ea58140d 100644 --- a/Disco.Data/Migrations/201406090652547_DBv14.Designer.cs +++ b/Disco.Data/Migrations/201406160912525_DBv14.Designer.cs @@ -11,7 +11,7 @@ namespace Disco.Data.Migrations string IMigrationMetadata.Id { - get { return "201406090652547_DBv14"; } + get { return "201406160912525_DBv14"; } } string IMigrationMetadata.Source diff --git a/Disco.Data/Migrations/201406090652547_DBv14.cs b/Disco.Data/Migrations/201406160912525_DBv14.cs similarity index 71% rename from Disco.Data/Migrations/201406090652547_DBv14.cs rename to Disco.Data/Migrations/201406160912525_DBv14.cs index 20b1d472..e41be3af 100644 --- a/Disco.Data/Migrations/201406090652547_DBv14.cs +++ b/Disco.Data/Migrations/201406160912525_DBv14.cs @@ -39,9 +39,17 @@ namespace Disco.Data.Migrations Description = c.String(maxLength: 500), Icon = c.String(nullable: false, maxLength: 25), IconColour = c.String(nullable: false, maxLength: 10), + UsersLinkedGroup = c.String(), + UserDevicesLinkedGroup = c.String(), }) .PrimaryKey(t => t.Id); + AddColumn("dbo.DocumentTemplates", "DevicesLinkedGroup", c => c.String()); + AddColumn("dbo.DocumentTemplates", "UsersLinkedGroup", c => c.String()); + AddColumn("dbo.DeviceProfiles", "DevicesLinkedGroup", c => c.String()); + AddColumn("dbo.DeviceProfiles", "AssignedUsersLinkedGroup", c => c.String()); + AddColumn("dbo.DeviceBatches", "DevicesLinkedGroup", c => c.String()); + AddColumn("dbo.DeviceBatches", "AssignedUsersLinkedGroup", c => c.String()); } public override void Down() @@ -54,6 +62,12 @@ namespace Disco.Data.Migrations DropForeignKey("dbo.UserFlagAssignments", "AddedUserId", "dbo.Users"); DropForeignKey("dbo.UserFlagAssignments", "UserId", "dbo.Users"); DropForeignKey("dbo.UserFlagAssignments", "UserFlagId", "dbo.UserFlags"); + DropColumn("dbo.DeviceBatches", "AssignedUsersLinkedGroup"); + DropColumn("dbo.DeviceBatches", "DevicesLinkedGroup"); + DropColumn("dbo.DeviceProfiles", "AssignedUsersLinkedGroup"); + DropColumn("dbo.DeviceProfiles", "DevicesLinkedGroup"); + DropColumn("dbo.DocumentTemplates", "UsersLinkedGroup"); + DropColumn("dbo.DocumentTemplates", "DevicesLinkedGroup"); DropTable("dbo.UserFlags"); DropTable("dbo.UserFlagAssignments"); } diff --git a/Disco.Data/Migrations/201406160912525_DBv14.resx b/Disco.Data/Migrations/201406160912525_DBv14.resx new file mode 100644 index 00000000..d204d8ab --- /dev/null +++ b/Disco.Data/Migrations/201406160912525_DBv14.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 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 + + \ No newline at end of file diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj index 08c29a56..d6383f5b 100644 --- a/Disco.Models/Disco.Models.csproj +++ b/Disco.Models/Disco.Models.csproj @@ -111,6 +111,7 @@ + @@ -181,7 +182,7 @@ - + diff --git a/Disco.Models/Repository/Device/DeviceBatch.cs b/Disco.Models/Repository/Device/DeviceBatch.cs index f5b16bc2..be2ffd72 100644 --- a/Disco.Models/Repository/Device/DeviceBatch.cs +++ b/Disco.Models/Repository/Device/DeviceBatch.cs @@ -44,6 +44,9 @@ namespace Disco.Models.Repository [DataType(DataType.MultilineText)] public string Comments { get; set; } + public string DevicesLinkedGroup { get; set; } + public string AssignedUsersLinkedGroup { get; set; } + [ForeignKey("DefaultDeviceModelId")] public virtual DeviceModel DefaultDeviceModel { get; set; } diff --git a/Disco.Models/Repository/Device/DeviceProfile.cs b/Disco.Models/Repository/Device/DeviceProfile.cs index 37ff8608..63419186 100644 --- a/Disco.Models/Repository/Device/DeviceProfile.cs +++ b/Disco.Models/Repository/Device/DeviceProfile.cs @@ -47,6 +47,9 @@ namespace Disco.Models.Repository public bool AllowUntrustedReimageJobEnrolment { get; set; } + public string DevicesLinkedGroup { get; set; } + public string AssignedUsersLinkedGroup { get; set; } + public virtual IList Devices { get; set; } public override string ToString() diff --git a/Disco.Models/Repository/DocumentTemplate/DocumentTemplate.cs b/Disco.Models/Repository/DocumentTemplate/DocumentTemplate.cs index b34336b0..558d3f08 100644 --- a/Disco.Models/Repository/DocumentTemplate/DocumentTemplate.cs +++ b/Disco.Models/Repository/DocumentTemplate/DocumentTemplate.cs @@ -18,13 +18,16 @@ namespace Disco.Models.Repository public string Description { get; set; } [Required, StringLength(6)] public string Scope { get; set; } - [StringLength(250)] + [StringLength(250), DataType(DataType.MultilineText)] public string FilterExpression { get; set; } // Feature Request 2012-05-10 by G#: https://disco.uservoice.com/forums/159707-feedback/suggestions/2811092-document-template-option-flatten-form-on-generate public bool FlattenForm { get; set; } // End Feature Request + public string DevicesLinkedGroup { get; set; } + public string UsersLinkedGroup { get; set; } + [InverseProperty("DocumentTemplates")] public virtual IList JobSubTypes { get; set; } diff --git a/Disco.Models/Repository/User/Flag/UserFlag.cs b/Disco.Models/Repository/User/Flag/UserFlag.cs index e7143b29..83758359 100644 --- a/Disco.Models/Repository/User/Flag/UserFlag.cs +++ b/Disco.Models/Repository/User/Flag/UserFlag.cs @@ -19,6 +19,9 @@ namespace Disco.Models.Repository [Required, StringLength(10)] public string IconColour { get; set; } + public string UsersLinkedGroup { get; set; } + public string UserDevicesLinkedGroup { get; set; } + public virtual IList UserFlagAssignments { get; set; } public override string ToString() diff --git a/Disco.Models/Services/Interop/ActiveDirectory/ADManagedGroupConfiguration.cs b/Disco.Models/Services/Interop/ActiveDirectory/ADManagedGroupConfiguration.cs new file mode 100644 index 00000000..815e38a0 --- /dev/null +++ b/Disco.Models/Services/Interop/ActiveDirectory/ADManagedGroupConfiguration.cs @@ -0,0 +1,10 @@ +using System; + +namespace Disco.Models.Services.Interop.ActiveDirectory +{ + public class ADManagedGroupConfiguration + { + public string GroupId { get; set; } + public DateTime? FilterBeginDate { get; set; } + } +} diff --git a/Disco.Services/Authorization/Roles/RoleCache.cs b/Disco.Services/Authorization/Roles/RoleCache.cs index eb2e241e..dd4d82f7 100644 --- a/Disco.Services/Authorization/Roles/RoleCache.cs +++ b/Disco.Services/Authorization/Roles/RoleCache.cs @@ -50,8 +50,7 @@ namespace Disco.Services.Authorization.Roles private static IEnumerable GenerateAdministratorSubjectIds(DiscoDataContext Database) { - var domainNetBiosName = ActiveDirectory.Context.PrimaryDomain.NetBiosName; - var configuredSubjectIds = Database.DiscoConfiguration.Administrators.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Contains(@"\") ? s : string.Format(@"{0}\{1}", domainNetBiosName, s)); + var configuredSubjectIds = Database.DiscoConfiguration.Administrators.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => ActiveDirectory.ParseDomainAccountId(s)); return RequiredAdministratorSubjectIds .Concat(configuredSubjectIds) @@ -62,8 +61,7 @@ namespace Disco.Services.Authorization.Roles { get { - var domainNetBiosName = ActiveDirectory.Context.PrimaryDomain.NetBiosName; - return _RequiredAdministratorSubjectIds.Select(s => string.Format(@"{0}\{1}", domainNetBiosName, s)); + return _RequiredAdministratorSubjectIds.Select(s => ActiveDirectory.ParseDomainAccountId(s)); } } public static IEnumerable AdministratorSubjectIds diff --git a/Disco.Services/Devices/Exporting/DeviceExport.cs b/Disco.Services/Devices/Exporting/DeviceExport.cs index 2d2b2ec1..a164e0d4 100644 --- a/Disco.Services/Devices/Exporting/DeviceExport.cs +++ b/Disco.Services/Devices/Exporting/DeviceExport.cs @@ -55,7 +55,7 @@ namespace Disco.Services.Devices.Exporting TaskStatus.ProgressMultiplier = 20 / 100; TaskStatus.ProgressOffset = 40; - Interop.ActiveDirectory.ADTaskUpdateNetworkLogonDates.UpdateLastNetworkLogonDates(Database, TaskStatus); + Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDates(Database, TaskStatus); Database.SaveChanges(); TaskStatus.IgnoreCurrentProcessChanges = false; diff --git a/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs b/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs index fd355641..c3db2345 100644 --- a/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs @@ -1,6 +1,7 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; +using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Users; using System; using System.Collections.Generic; @@ -34,8 +35,7 @@ namespace Disco.Services.Devices.Importing.Fields { parsedValue = Value.Trim(); - if (!parsedValue.Contains('\\')) - parsedValue = string.Format(@"{0}\{1}", Interop.ActiveDirectory.ActiveDirectory.Context.PrimaryDomain.NetBiosName, parsedValue); + parsedValue = ActiveDirectory.ParseDomainAccountId(parsedValue); friendlyValue = parsedValue; diff --git a/Disco.Services/Devices/ManagedGroups/DeviceBatchAssignedUsersManagedGroup.cs b/Disco.Services/Devices/ManagedGroups/DeviceBatchAssignedUsersManagedGroup.cs new file mode 100644 index 00000000..f2c74c57 --- /dev/null +++ b/Disco.Services/Devices/ManagedGroups/DeviceBatchAssignedUsersManagedGroup.cs @@ -0,0 +1,186 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.Services.Devices.ManagedGroups +{ + public class DeviceBatchAssignedUsersManagedGroup : ADManagedGroup + { + private const string KeyFormat = "DeviceBatch_{0}_AssignedUsers"; + private const string DescriptionFormat = "Devices within the {0} Batch will have their assigned users added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Assigned Users Linked Group"; + private const string GroupDescriptionFormat = "{0} [Device Batch Assigned Users]"; + + private static Lazy> RepositoryEvents; + + private IDisposable repositorySubscription; + private int DeviceBatchId; + private string DeviceBatchName; + + public override string Description { get { return string.Format(DescriptionFormat, DeviceBatchName); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, DeviceBatchName); } } + public override bool IncludeFilterBeginDate { get { return false; } } + + static DeviceBatchAssignedUsersManagedGroup() + { + RepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamBeforeCommit.Where(e => + e.EntityType == typeof(Device) && ( + (e.EventType == RepositoryMonitorEventType.Added && + ((Device)e.Entity).AssignedUserId != null) || + (e.EventType == RepositoryMonitorEventType.Modified && + (e.ModifiedProperties.Contains("DeviceBatchId") || e.ModifiedProperties.Contains("AssignedUserId"))) || + (e.EventType == RepositoryMonitorEventType.Deleted && + ((Device)e.Entity).AssignedUserId != null)) + ) + ); + } + + private DeviceBatchAssignedUsersManagedGroup(string Key, ADManagedGroupConfiguration Configuration, DeviceBatch DeviceBatch) + : base(Key, Configuration) + { + this.DeviceBatchId = DeviceBatch.Id; + this.DeviceBatchName = DeviceBatch.Name; + } + + public override void Initialize() + { + // Subscribe to changes + repositorySubscription = RepositoryEvents.Value + .Where(e => + (((Device)e.Entity).DeviceBatchId == DeviceBatchId) || + (e.EventType == RepositoryMonitorEventType.Modified && e.GetPreviousPropertyValue("DeviceBatchId") == DeviceBatchId)) + .Subscribe(ProcessRepositoryEvent); + } + + public static string GetKey(DeviceBatch DeviceBatch) + { + return string.Format(KeyFormat, DeviceBatch.Id); + } + public static string GetDescription(DeviceBatch DeviceBatch) + { + return string.Format(DescriptionFormat, DeviceBatch.Name); + } + public static string GetCategoryDescription(DeviceBatch DeviceBatch) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(DeviceBatch DeviceBatch, out DeviceBatchAssignedUsersManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(DeviceBatch); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (DeviceBatchAssignedUsersManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static DeviceBatchAssignedUsersManagedGroup Initialize(DeviceBatch DeviceBatch) + { + if (DeviceBatch.Id > 0) + { + var key = GetKey(DeviceBatch); + + if (!string.IsNullOrEmpty(DeviceBatch.AssignedUsersLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(DeviceBatch.AssignedUsersLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new DeviceBatchAssignedUsersManagedGroup( + key, + config, + DeviceBatch); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + } + + return null; + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + return Database.Devices + .Where(d => d.DeviceBatchId == this.DeviceBatchId) + .Where(d => d.AssignedUserId != null) + .Select(d => d.AssignedUserId); + } + + private void ProcessRepositoryEvent(RepositoryMonitorEvent Event) + { + var device = (Device)Event.Entity; + string previousUserId = Event.GetPreviousPropertyValue("AssignedUserId"); + + Event.ExecuteAfterCommit(e => + { + switch (e.EventType) + { + case RepositoryMonitorEventType.Added: + AddMember(device.AssignedUserId); + break; + case RepositoryMonitorEventType.Modified: + if (device.DeviceBatchId == this.DeviceBatchId) + { + if (device.AssignedUserId != null) + AddMember(device.AssignedUserId); + + if (e.ModifiedProperties.Contains("AssignedUserId")) + { + if (previousUserId != null) + RemoveMember(previousUserId, (database) => + !database.Devices.Any(d => d.DeviceBatchId == this.DeviceBatchId && d.AssignedUserId == previousUserId) + ? new string[] { previousUserId } + : null); + } + } + else + { + if (previousUserId != null) + RemoveMember(previousUserId, (database) => + !database.Devices.Any(d => d.DeviceBatchId == this.DeviceBatchId && d.AssignedUserId == previousUserId) + ? new string[] { previousUserId } + : null); + } + break; + case RepositoryMonitorEventType.Deleted: + if (previousUserId != null) + RemoveMember(previousUserId, (database) => + !database.Devices.Any(d => d.DeviceBatchId == this.DeviceBatchId && d.AssignedUserId == previousUserId) + ? new string[] { previousUserId } + : null); + break; + } + }); + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + } + } +} diff --git a/Disco.Services/Devices/ManagedGroups/DeviceBatchDevicesManagedGroup.cs b/Disco.Services/Devices/ManagedGroups/DeviceBatchDevicesManagedGroup.cs new file mode 100644 index 00000000..7a76662f --- /dev/null +++ b/Disco.Services/Devices/ManagedGroups/DeviceBatchDevicesManagedGroup.cs @@ -0,0 +1,186 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.Services.Devices.ManagedGroups +{ + public class DeviceBatchDevicesManagedGroup : ADManagedGroup + { + private const string KeyFormat = "DeviceBatch_{0}_Devices"; + private const string DescriptionFormat = "Devices within the {0} Batch will be added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Devices Linked Group"; + private const string GroupDescriptionFormat = "{0} [Device Batch Devices]"; + private static Lazy> RepositoryEvents; + + private IDisposable repositorySubscription; + private int DeviceBatchId; + private string DeviceBatchName; + + public override string Description { get { return string.Format(DescriptionFormat, DeviceBatchName); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, DeviceBatchName); } } + public override bool IncludeFilterBeginDate { get { return false; } } + + static DeviceBatchDevicesManagedGroup() + { + RepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamBeforeCommit.Where(e => + e.EntityType == typeof(Device) && ( + (e.EventType == RepositoryMonitorEventType.Added && + ActiveDirectory.IsValidDomainAccountId(((Device)e.Entity).DeviceDomainId)) || + (e.EventType == RepositoryMonitorEventType.Modified && + (e.ModifiedProperties.Contains("DeviceBatchId") || e.ModifiedProperties.Contains("DeviceDomainId"))) || + (e.EventType == RepositoryMonitorEventType.Deleted) + ) + )); + } + + private DeviceBatchDevicesManagedGroup(string Key, ADManagedGroupConfiguration Configuration, DeviceBatch DeviceBatch) + : base(Key, Configuration) + { + this.DeviceBatchId = DeviceBatch.Id; + this.DeviceBatchName = DeviceBatch.Name; + } + + public override void Initialize() + { + // Subscribe to changes + repositorySubscription = RepositoryEvents.Value + .Where(e => + (((Device)e.Entity).DeviceBatchId == DeviceBatchId) || + (e.EventType == RepositoryMonitorEventType.Modified && e.GetPreviousPropertyValue("DeviceBatchId") == DeviceBatchId)) + .Subscribe(ProcessRepositoryEvent); + } + + public static string GetKey(DeviceBatch DeviceBatch) + { + return string.Format(KeyFormat, DeviceBatch.Id); + } + public static string GetDescription(DeviceBatch DeviceBatch) + { + return string.Format(DescriptionFormat, DeviceBatch.Name); + } + public static string GetCategoryDescription(DeviceBatch DeviceBatch) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(DeviceBatch DeviceBatch, out DeviceBatchDevicesManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(DeviceBatch); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (DeviceBatchDevicesManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static DeviceBatchDevicesManagedGroup Initialize(DeviceBatch DeviceBatch) + { + if (DeviceBatch.Id > 0) + { + var key = GetKey(DeviceBatch); + + if (!string.IsNullOrEmpty(DeviceBatch.DevicesLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(DeviceBatch.DevicesLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new DeviceBatchDevicesManagedGroup( + key, + config, + DeviceBatch); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + } + + return null; + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + return Database.Devices + .Where(d => d.DeviceBatchId == this.DeviceBatchId) + .Where(d => d.DeviceDomainId != null) + .Select(d => d.DeviceDomainId) + .ToList() + .Where(ActiveDirectory.IsValidDomainAccountId) + .Select(id => id + "$"); + } + + private void ProcessRepositoryEvent(RepositoryMonitorEvent Event) + { + var device = (Device)Event.Entity; + string previousDeviceDomainId = Event.GetPreviousPropertyValue("DeviceDomainId"); + + Event.ExecuteAfterCommit(e => + { + switch (e.EventType) + { + case RepositoryMonitorEventType.Added: + AddMember(device.DeviceDomainId + "$"); + break; + case RepositoryMonitorEventType.Modified: + if (device.DeviceBatchId == this.DeviceBatchId) + { + if (ActiveDirectory.IsValidDomainAccountId(device.DeviceDomainId)) + AddMember(device.DeviceDomainId + "$"); + + if (e.ModifiedProperties.Contains("DeviceDomainId")) + { + if (ActiveDirectory.IsValidDomainAccountId(previousDeviceDomainId)) + RemoveMember(previousDeviceDomainId + "$"); + } + } + else + { + if (e.ModifiedProperties.Contains("DeviceDomainId")) + { + if (ActiveDirectory.IsValidDomainAccountId(previousDeviceDomainId)) + RemoveMember(previousDeviceDomainId + "$"); + } + else + { + if (ActiveDirectory.IsValidDomainAccountId(device.DeviceDomainId)) + RemoveMember(device.DeviceDomainId + "$"); + } + } + break; + case RepositoryMonitorEventType.Deleted: + if (ActiveDirectory.IsValidDomainAccountId(previousDeviceDomainId)) + RemoveMember(previousDeviceDomainId + "$"); + break; + } + }); + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + } + } +} diff --git a/Disco.Services/Devices/ManagedGroups/DeviceManagedGroups.cs b/Disco.Services/Devices/ManagedGroups/DeviceManagedGroups.cs new file mode 100644 index 00000000..e295f9cf --- /dev/null +++ b/Disco.Services/Devices/ManagedGroups/DeviceManagedGroups.cs @@ -0,0 +1,31 @@ +using Disco.Data.Repository; +using System.Linq; + +namespace Disco.Services.Devices.ManagedGroups +{ + public static class DeviceManagedGroups + { + public static void Initialize(DiscoDataContext Database) + { + // Device Profiles + Database.DeviceProfiles + .Where(dp => dp.DevicesLinkedGroup != null || dp.AssignedUsersLinkedGroup != null) + .ToList() + .ForEach(dp => + { + DeviceProfileDevicesManagedGroup.Initialize(dp); + DeviceProfileAssignedUsersManagedGroup.Initialize(dp); + }); + + // Device Batches + Database.DeviceBatches + .Where(db => db.DevicesLinkedGroup != null || db.AssignedUsersLinkedGroup != null) + .ToList() + .ForEach(db => + { + DeviceBatchDevicesManagedGroup.Initialize(db); + DeviceBatchAssignedUsersManagedGroup.Initialize(db); + }); + } + } +} diff --git a/Disco.Services/Devices/ManagedGroups/DeviceProfileAssignedUsersManagedGroup.cs b/Disco.Services/Devices/ManagedGroups/DeviceProfileAssignedUsersManagedGroup.cs new file mode 100644 index 00000000..71ebb916 --- /dev/null +++ b/Disco.Services/Devices/ManagedGroups/DeviceProfileAssignedUsersManagedGroup.cs @@ -0,0 +1,186 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.Services.Devices.ManagedGroups +{ + public class DeviceProfileAssignedUsersManagedGroup : ADManagedGroup + { + private const string KeyFormat = "DeviceProfile_{0}_AssignedUsers"; + private const string DescriptionFormat = "Devices within the {0} Profile will have their assigned users added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Assigned Users Linked Group"; + private const string GroupDescriptionFormat = "{0} [Device Profile Assigned Users]"; + + private static Lazy> RepositoryEvents; + + private IDisposable repositorySubscription; + private int DeviceProfileId; + private string DeviceProfileName; + + public override string Description { get { return string.Format(DescriptionFormat, DeviceProfileName); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, DeviceProfileName); } } + public override bool IncludeFilterBeginDate { get { return false; } } + + static DeviceProfileAssignedUsersManagedGroup() + { + RepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamBeforeCommit.Where(e => + e.EntityType == typeof(Device) && ( + (e.EventType == RepositoryMonitorEventType.Added && + ((Device)e.Entity).AssignedUserId != null) || + (e.EventType == RepositoryMonitorEventType.Modified && + (e.ModifiedProperties.Contains("DeviceProfileId") || e.ModifiedProperties.Contains("AssignedUserId"))) || + (e.EventType == RepositoryMonitorEventType.Deleted && + ((Device)e.Entity).AssignedUserId != null)) + ) + ); + } + + private DeviceProfileAssignedUsersManagedGroup(string Key, ADManagedGroupConfiguration Configuration, DeviceProfile DeviceProfile) + : base(Key, Configuration) + { + this.DeviceProfileId = DeviceProfile.Id; + this.DeviceProfileName = DeviceProfile.Name; + } + + public override void Initialize() + { + // Subscribe to changes + repositorySubscription = RepositoryEvents.Value + .Where(e => + (((Device)e.Entity).DeviceProfileId == DeviceProfileId) || + (e.EventType == RepositoryMonitorEventType.Modified && e.GetPreviousPropertyValue("DeviceProfileId") == DeviceProfileId)) + .Subscribe(ProcessRepositoryEvent); + } + + public static string GetKey(DeviceProfile DeviceProfile) + { + return string.Format(KeyFormat, DeviceProfile.Id); + } + public static string GetDescription(DeviceProfile DeviceProfile) + { + return string.Format(DescriptionFormat, DeviceProfile.Name); + } + public static string GetCategoryDescription(DeviceProfile DeviceProfile) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(DeviceProfile DeviceProfile, out DeviceProfileAssignedUsersManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(DeviceProfile); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (DeviceProfileAssignedUsersManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static DeviceProfileAssignedUsersManagedGroup Initialize(DeviceProfile DeviceProfile) + { + if (DeviceProfile.Id > 0) + { + var key = GetKey(DeviceProfile); + + if (!string.IsNullOrEmpty(DeviceProfile.AssignedUsersLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(DeviceProfile.AssignedUsersLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new DeviceProfileAssignedUsersManagedGroup( + key, + config, + DeviceProfile); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + } + + return null; + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + return Database.Devices + .Where(d => d.DeviceProfileId == this.DeviceProfileId) + .Where(d => d.AssignedUserId != null) + .Select(d => d.AssignedUserId); + } + + private void ProcessRepositoryEvent(RepositoryMonitorEvent Event) + { + var device = (Device)Event.Entity; + string previousUserId = Event.GetPreviousPropertyValue("AssignedUserId"); + + Event.ExecuteAfterCommit(e => + { + switch (e.EventType) + { + case RepositoryMonitorEventType.Added: + AddMember(device.AssignedUserId); + break; + case RepositoryMonitorEventType.Modified: + if (device.DeviceProfileId == this.DeviceProfileId) + { + if (device.AssignedUserId != null) + AddMember(device.AssignedUserId); + + if (e.ModifiedProperties.Contains("AssignedUserId")) + { + if (previousUserId != null) + RemoveMember(previousUserId, (database) => + !database.Devices.Any(d => d.DeviceProfileId == this.DeviceProfileId && d.AssignedUserId == previousUserId) + ? new string[] { previousUserId } + : null); + } + } + else + { + if (previousUserId != null) + RemoveMember(previousUserId, (database) => + !database.Devices.Any(d => d.DeviceProfileId == this.DeviceProfileId && d.AssignedUserId == previousUserId) + ? new string[] { previousUserId } + : null); + } + break; + case RepositoryMonitorEventType.Deleted: + if (previousUserId != null) + RemoveMember(previousUserId, (database) => + !database.Devices.Any(d => d.DeviceProfileId == this.DeviceProfileId && d.AssignedUserId == previousUserId) + ? new string[] { previousUserId } + : null); + break; + } + }); + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + } + } +} diff --git a/Disco.Services/Devices/ManagedGroups/DeviceProfileDevicesManagedGroup.cs b/Disco.Services/Devices/ManagedGroups/DeviceProfileDevicesManagedGroup.cs new file mode 100644 index 00000000..8bfc53bd --- /dev/null +++ b/Disco.Services/Devices/ManagedGroups/DeviceProfileDevicesManagedGroup.cs @@ -0,0 +1,187 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.Services.Devices.ManagedGroups +{ + public class DeviceProfileDevicesManagedGroup : ADManagedGroup + { + private const string KeyFormat = "DeviceProfile_{0}_Devices"; + private const string DescriptionFormat = "Devices within the {0} Profile will be added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Devices Linked Group"; + private const string GroupDescriptionFormat = "{0} [Device Profile Devices]"; + + private static Lazy> RepositoryEvents; + + private IDisposable repositorySubscription; + private int DeviceProfileId; + private string DeviceProfileName; + + public override string Description { get { return string.Format(DescriptionFormat, DeviceProfileName); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, DeviceProfileName); } } + public override bool IncludeFilterBeginDate { get { return false; } } + + static DeviceProfileDevicesManagedGroup() + { + RepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamBeforeCommit.Where(e => + e.EntityType == typeof(Device) && ( + (e.EventType == RepositoryMonitorEventType.Added && + ActiveDirectory.IsValidDomainAccountId(((Device)e.Entity).DeviceDomainId)) || + (e.EventType == RepositoryMonitorEventType.Modified && + (e.ModifiedProperties.Contains("DeviceProfileId") || e.ModifiedProperties.Contains("DeviceDomainId"))) || + (e.EventType == RepositoryMonitorEventType.Deleted) + ) + )); + } + + private DeviceProfileDevicesManagedGroup(string Key, ADManagedGroupConfiguration Configuration, DeviceProfile DeviceProfile) + : base(Key, Configuration) + { + this.DeviceProfileId = DeviceProfile.Id; + this.DeviceProfileName = DeviceProfile.Name; + } + + public override void Initialize() + { + // Subscribe to changes + repositorySubscription = RepositoryEvents.Value + .Where(e => + (((Device)e.Entity).DeviceProfileId == DeviceProfileId) || + (e.EventType == RepositoryMonitorEventType.Modified && e.GetPreviousPropertyValue("DeviceProfileId") == DeviceProfileId)) + .Subscribe(ProcessRepositoryEvent); + } + + public static string GetKey(DeviceProfile DeviceProfile) + { + return string.Format(KeyFormat, DeviceProfile.Id); + } + public static string GetDescription(DeviceProfile DeviceProfile) + { + return string.Format(DescriptionFormat, DeviceProfile.Name); + } + public static string GetCategoryDescription(DeviceProfile DeviceProfile) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(DeviceProfile DeviceProfile, out DeviceProfileDevicesManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(DeviceProfile); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (DeviceProfileDevicesManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static DeviceProfileDevicesManagedGroup Initialize(DeviceProfile DeviceProfile) + { + if (DeviceProfile.Id > 0) + { + var key = GetKey(DeviceProfile); + + if (!string.IsNullOrEmpty(DeviceProfile.DevicesLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(DeviceProfile.DevicesLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new DeviceProfileDevicesManagedGroup( + key, + config, + DeviceProfile); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + } + + return null; + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + return Database.Devices + .Where(d => d.DeviceProfileId == this.DeviceProfileId) + .Where(d => d.DeviceDomainId != null) + .Select(d => d.DeviceDomainId) + .ToList() + .Where(ActiveDirectory.IsValidDomainAccountId) + .Select(id => id + "$"); + } + + private void ProcessRepositoryEvent(RepositoryMonitorEvent Event) + { + var device = (Device)Event.Entity; + string previousDeviceDomainId = Event.GetPreviousPropertyValue("DeviceDomainId"); + + Event.ExecuteAfterCommit(e => + { + switch (e.EventType) + { + case RepositoryMonitorEventType.Added: + AddMember(device.DeviceDomainId + "$"); + break; + case RepositoryMonitorEventType.Modified: + if (device.DeviceProfileId == this.DeviceProfileId) + { + if (ActiveDirectory.IsValidDomainAccountId(device.DeviceDomainId)) + AddMember(device.DeviceDomainId + "$"); + + if (e.ModifiedProperties.Contains("DeviceDomainId")) + { + if (ActiveDirectory.IsValidDomainAccountId(previousDeviceDomainId)) + RemoveMember(previousDeviceDomainId + "$"); + } + } + else + { + if (e.ModifiedProperties.Contains("DeviceDomainId")) + { + if (ActiveDirectory.IsValidDomainAccountId(previousDeviceDomainId)) + RemoveMember(previousDeviceDomainId + "$"); + } + else + { + if (ActiveDirectory.IsValidDomainAccountId(device.DeviceDomainId)) + RemoveMember(device.DeviceDomainId + "$"); + } + } + break; + case RepositoryMonitorEventType.Deleted: + if (ActiveDirectory.IsValidDomainAccountId(previousDeviceDomainId)) + RemoveMember(previousDeviceDomainId + "$"); + break; + } + }); + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + } + } +} diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj index 26871d5b..f0453aee 100644 --- a/Disco.Services/Disco.Services.csproj +++ b/Disco.Services/Disco.Services.csproj @@ -204,7 +204,13 @@ + + + + + + @@ -212,6 +218,7 @@ + @@ -219,11 +226,14 @@ + + - + + @@ -300,6 +310,8 @@ + + diff --git a/Disco.Services/Extensions/EnumerableExtensions.cs b/Disco.Services/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..4bd07917 --- /dev/null +++ b/Disco.Services/Extensions/EnumerableExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Disco +{ + public static class EnumerableExtensions + { + + public static IEnumerable> Chunk(this IEnumerable source, int ChunkSize) + { + List buffer = new List(ChunkSize); + + foreach (var item in source) + { + buffer.Add(item); + + if (buffer.Count == ChunkSize) + { + yield return buffer; + buffer = new List(); + } + } + + // Return any additional items + if (buffer.Count > 0) + yield return buffer; + } + + } +} diff --git a/Disco.Services/Interop/ActiveDirectory/ADDomainController.cs b/Disco.Services/Interop/ActiveDirectory/ADDomainController.cs index 948cdc81..c5dddf9f 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADDomainController.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADDomainController.cs @@ -193,26 +193,34 @@ namespace Disco.Services.Interop.ActiveDirectory #endregion #region Groups - public ADGroup RetrieveADGroup(string Id) + public ADGroup RetrieveADGroup(string Id, string[] AdditionalProperties = null) { - var result = RetrieveBySamAccountName(Id, ADGroup.LdapSamAccountNameFilterTemplate, ADGroup.LoadProperties); + string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0) + ? ADGroup.LoadProperties.Concat(AdditionalProperties).ToArray() + : ADGroup.LoadProperties; + + var result = RetrieveBySamAccountName(Id, ADGroup.LdapSamAccountNameFilterTemplate, loadProperites); if (result == null) return null; else - return result.AsADGroup(); + return result.AsADGroup(AdditionalProperties); } - public ADGroup RetrieveADGroupByDistinguishedName(string DistinguishedName) + public ADGroup RetrieveADGroupByDistinguishedName(string DistinguishedName, string[] AdditionalProperties = null) { - using (var groupEntry = this.RetrieveDirectoryEntry(DistinguishedName, ADGroup.LoadProperties)) + string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0) + ? ADGroup.LoadProperties.Concat(AdditionalProperties).ToArray() + : ADGroup.LoadProperties; + + using (var groupEntry = this.RetrieveDirectoryEntry(DistinguishedName, loadProperites)) { if (groupEntry == null) return null; - return groupEntry.AsADGroup(); + return groupEntry.AsADGroup(AdditionalProperties); } } - public ADGroup RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier SecurityIdentifier) + public ADGroup RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier SecurityIdentifier, string[] AdditionalProperties = null) { if (SecurityIdentifier == null) throw new ArgumentNullException("SecurityIdentifier"); @@ -222,12 +230,15 @@ namespace Disco.Services.Interop.ActiveDirectory var sidBinaryString = SecurityIdentifier.ToBinaryString(); string ldapFilter = string.Format(ADGroup.LdapSecurityIdentifierFilterTemplate, sidBinaryString); + string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0) + ? ADGroup.LoadProperties.Concat(AdditionalProperties).ToArray() + : ADGroup.LoadProperties; - var result = this.SearchEntireDomain(ldapFilter, ADGroup.LoadProperties, ActiveDirectory.SingleSearchResult).FirstOrDefault(); + var result = this.SearchEntireDomain(ldapFilter, loadProperites, ActiveDirectory.SingleSearchResult).FirstOrDefault(); if (result == null) return null; else - return result.AsADGroup(); + return result.AsADGroup(AdditionalProperties); } #endregion @@ -236,7 +247,7 @@ namespace Disco.Services.Interop.ActiveDirectory private static readonly string[] ObjectLoadProperties = { "objectCategory" }; private static readonly string[] ObjectLoadPropertiesAll = ObjectLoadProperties.Concat(ADUserAccount.LoadProperties).Concat(ADMachineAccount.LoadProperties).Concat(ADGroup.LoadProperties).Distinct().ToArray(); - public IADObject RetrieveADObject(string Id, bool Quick) + public IADObject RetrieveADObject(string Id, bool Quick, string[] AdditionalProperties = null) { var result = RetrieveBySamAccountName(Id, ObjectLdapSamAccountNameFilter, ObjectLoadPropertiesAll); @@ -249,11 +260,11 @@ namespace Disco.Services.Interop.ActiveDirectory switch (objectCategory) { case "cn=person": - return result.AsADUserAccount(Quick, null); + return result.AsADUserAccount(Quick, AdditionalProperties); case "cn=computer": - return result.AsADMachineAccount(null); + return result.AsADMachineAccount(AdditionalProperties); case "cn=group": - return result.AsADGroup(); + return result.AsADGroup(AdditionalProperties); default: throw new InvalidOperationException("Unexpected objectCategory"); } @@ -294,12 +305,12 @@ namespace Disco.Services.Interop.ActiveDirectory private ADSearchResult RetrieveBySamAccountName(string Id, string LdapFilterTemplate, string[] LoadProperties) { - var splitId = UserExtensions.SplitUserId(Id); + var slashIndex = Id.IndexOf('\\'); - if (!this.Domain.NetBiosName.Equals(splitId.Item1, StringComparison.OrdinalIgnoreCase)) + if (!this.Domain.NetBiosName.Equals(Id.Substring(0, slashIndex), StringComparison.OrdinalIgnoreCase)) throw new ArgumentException(string.Format("The Id [{0}] is invalid for this domain [{1}]", Id, this.Domain.Name), "Id"); - var ldapFilter = string.Format(LdapFilterTemplate, splitId.Item2); + var ldapFilter = string.Format(LdapFilterTemplate, Id.Substring(slashIndex + 1)); return this.SearchEntireDomain(ldapFilter, LoadProperties, ActiveDirectory.SingleSearchResult).FirstOrDefault(); } diff --git a/Disco.Services/Interop/ActiveDirectory/ADGroup.cs b/Disco.Services/Interop/ActiveDirectory/ADGroup.cs index 771a022f..fd351026 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADGroup.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADGroup.cs @@ -17,7 +17,7 @@ namespace Disco.Services.Interop.ActiveDirectory public string DistinguishedName { get; private set; } public SecurityIdentifier SecurityIdentifier { get; private set; } - + public string Id { get { return string.Format(@"{0}\{1}", Domain.NetBiosName, SamAccountName); } } public string SamAccountName { get; private set; } @@ -26,7 +26,9 @@ namespace Disco.Services.Interop.ActiveDirectory public List MemberOf { get; private set; } - private ADGroup(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName, string Name, List MemberOf) + public Dictionary LoadedProperties { get; private set; } + + private ADGroup(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName, string Name, List MemberOf, Dictionary LoadedProperties) { this.Domain = Domain; this.DistinguishedName = DistinguishedName; @@ -34,9 +36,10 @@ namespace Disco.Services.Interop.ActiveDirectory this.SamAccountName = SamAccountName; this.Name = Name; this.MemberOf = MemberOf; + this.LoadedProperties = LoadedProperties; } - public static ADGroup FromSearchResult(ADSearchResult SearchResult) + public static ADGroup FromSearchResult(ADSearchResult SearchResult, string[] AdditionalProperties) { if (SearchResult == null) throw new ArgumentNullException("SearchResult"); @@ -47,10 +50,21 @@ namespace Disco.Services.Interop.ActiveDirectory var objectSid = new SecurityIdentifier(SearchResult.Value("objectSid"), 0); var memberOf = SearchResult.Values("memberOf").ToList(); - return new ADGroup(SearchResult.Domain, distinguishedName, objectSid, sAMAccountName, name, memberOf); + // Additional Properties + Dictionary additionalProperties; + if (AdditionalProperties != null) + additionalProperties = AdditionalProperties + .Select(p => Tuple.Create(p, SearchResult.Values(p).ToArray())) + .ToDictionary(t => t.Item1, t => t.Item2); + else + { + additionalProperties = new Dictionary(); + } + + return new ADGroup(SearchResult.Domain, distinguishedName, objectSid, sAMAccountName, name, memberOf, additionalProperties); } - public static ADGroup FromDirectoryEntry(ADDirectoryEntry DirectoryEntry) + public static ADGroup FromDirectoryEntry(ADDirectoryEntry DirectoryEntry, string[] AdditionalProperties) { if (DirectoryEntry == null) throw new ArgumentNullException("DirectoryEntry"); @@ -63,7 +77,50 @@ namespace Disco.Services.Interop.ActiveDirectory var objectSid = new SecurityIdentifier(properties.Value("objectSid"), 0); var memberOf = properties.Values("memberOf").ToList(); - return new ADGroup(DirectoryEntry.Domain, distinguishedName, objectSid, sAMAccountName, name, memberOf); + Dictionary additionalProperties; + if (AdditionalProperties != null) + additionalProperties = AdditionalProperties + .Select(p => Tuple.Create(p, properties.Values(p).ToArray())) + .ToDictionary(t => t.Item1, t => t.Item2); + else + { + additionalProperties = new Dictionary(); + } + + return new ADGroup(DirectoryEntry.Domain, distinguishedName, objectSid, sAMAccountName, name, memberOf, additionalProperties); + } + + [Obsolete("Use generic equivalents: GetPropertyValue(string PropertyName)")] + public object GetPropertyValue(string PropertyName, int Index = 0) + { + return GetPropertyValues(PropertyName).Skip(Index).FirstOrDefault(); + } + + public T GetPropertyValue(string PropertyName) + { + return GetPropertyValues(PropertyName).FirstOrDefault(); + } + public IEnumerable GetPropertyValues(string PropertyName) + { + switch (PropertyName.ToLower()) + { + case "name": + return new string[] { this.Name }.OfType(); + case "samaccountname": + return new string[] { this.SamAccountName }.OfType(); + case "distinguishedname": + return new string[] { this.DistinguishedName }.OfType(); + case "objectsid": + return new SecurityIdentifier[] { this.SecurityIdentifier }.OfType(); + case "memberof": + return this.MemberOf.OfType(); + default: + object[] adProperty; + if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty)) + return adProperty.OfType(); + else + return Enumerable.Empty(); + } } public override string ToString() diff --git a/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs b/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs index 91a19efb..fa87d456 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs @@ -104,26 +104,35 @@ namespace Disco.Services.Interop.ActiveDirectory }; } + [Obsolete("Use generic equivalents: GetPropertyValue(string PropertyName)")] public object GetPropertyValue(string PropertyName, int Index = 0) + { + return GetPropertyValues(PropertyName).Skip(Index).FirstOrDefault(); + } + public T GetPropertyValue(string PropertyName) + { + return GetPropertyValues(PropertyName).FirstOrDefault(); + } + public IEnumerable GetPropertyValues(string PropertyName) { switch (PropertyName.ToLower()) { case "name": - return this.Name; + return new string[] { this.Name }.OfType(); case "samaccountname": - return this.SamAccountName; + return new string[] { this.SamAccountName }.OfType(); case "distinguishedname": - return this.DistinguishedName; + return new string[] { this.DistinguishedName }.OfType(); case "objectsid": - return this.SecurityIdentifier.ToString(); + return new SecurityIdentifier[] { this.SecurityIdentifier }.OfType(); case "netbootguid": - return this.NetbootGUID; + return new Guid[] { this.NetbootGUID }.OfType(); default: object[] adProperty; - if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty) && Index <= adProperty.Length) - return adProperty[Index]; + if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty)) + return adProperty.OfType(); else - return null; + return Enumerable.Empty(); } } diff --git a/Disco.Services/Interop/ActiveDirectory/ADManagedGroup.cs b/Disco.Services/Interop/ActiveDirectory/ADManagedGroup.cs new file mode 100644 index 00000000..0ae325b7 --- /dev/null +++ b/Disco.Services/Interop/ActiveDirectory/ADManagedGroup.cs @@ -0,0 +1,102 @@ +using Disco.Data.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace Disco.Services.Interop.ActiveDirectory +{ + public abstract class ADManagedGroup : IDisposable + { + public string Key { get; private set; } + public ADManagedGroupConfiguration Configuration { get; private set; } + + internal ActiveDirectoryManagedGroups Context { get; set; } + + public abstract string Description { get; } + public abstract string CategoryDescription { get; } + public abstract string GroupDescription { get; } + public abstract bool IncludeFilterBeginDate { get; } + + public ADManagedGroup(string Key, ADManagedGroupConfiguration Configuration) + { + if (string.IsNullOrWhiteSpace(Key)) + throw new ArgumentNullException("Key"); + if (Configuration == null) + throw new ArgumentNullException("Configuration"); + if (!ActiveDirectory.IsValidDomainAccountId(Configuration.GroupId)) + throw new ArgumentException("Configuration.GroupId is not a valid Domain Account Id", "Configuration"); + + this.Key = Key; + this.Configuration = Configuration; + } + + public abstract void Initialize(); + public abstract IEnumerable DetermineMembers(DiscoDataContext Database); + + public ADGroup GetGroup() + { + return ActiveDirectory.RetrieveADGroup(this.Configuration.GroupId, "member"); + } + + protected void AddMember(string Id) + { + AddMember(Id, null); + } + protected void AddMember(string InvokingIdentifier, Func> MemberResolver) + { + if (Context == null) + return; // Must be added to ActiveDirectoryManagedGroups + + var action = new ADManagedGroupScheduledAction( + this, + ADManagedGroupScheduledActionType.AddGroupMember, + InvokingIdentifier, + MemberResolver); + + Context.ScheduleAction(action); + } + + protected void RemoveMember(string Id) + { + RemoveMember(Id, null); + } + protected void RemoveMember(string InvokingIdentifier, Func> MemberResolver) + { + if (Context == null) + return; // Must be added to ActiveDirectoryManagedGroups + + var action = new ADManagedGroupScheduledAction( + this, + ADManagedGroupScheduledActionType.RemoveGroupMember, + InvokingIdentifier, + MemberResolver); + + Context.ScheduleAction(action); + } + + public static ADManagedGroupConfiguration ConfigurationFromJson(string ConfigurationJson) + { + return JsonConvert.DeserializeObject(ConfigurationJson); + } + public static string ValidConfigurationToJson(string GroupKey, string GroupId, DateTime? FilterBeginDate) + { + if (string.IsNullOrWhiteSpace(GroupId)) + GroupId = null; + + if (GroupId != null) + GroupId = ActiveDirectory.Context.ManagedGroups.ValidateGroupId(GroupId, GroupKey); + + if (GroupId == null) + return null; + else + return JsonConvert.SerializeObject(new ADManagedGroupConfiguration() + { + GroupId = GroupId, + FilterBeginDate = FilterBeginDate + }, new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore }); + } + + public abstract void Dispose(); + } +} diff --git a/Disco.Services/Interop/ActiveDirectory/ADManagedGroupsSyncTask.cs b/Disco.Services/Interop/ActiveDirectory/ADManagedGroupsSyncTask.cs new file mode 100644 index 00000000..3e505ec2 --- /dev/null +++ b/Disco.Services/Interop/ActiveDirectory/ADManagedGroupsSyncTask.cs @@ -0,0 +1,76 @@ +using Disco.Data.Repository; +using Disco.Services.Logging; +using Disco.Services.Tasks; +using Quartz; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Disco.Services.Interop.ActiveDirectory +{ + public class ADManagedGroupsSyncTask : ScheduledTask + { + public override string TaskName { get { return "Active Directory - Synchronise Managed Groups"; } } + public override bool SingleInstanceTask { get { return true; } } + public override bool CancelInitiallySupported { get { return false; } } + + public override void InitalizeScheduledTask(DiscoDataContext Database) + { + // ADManagedGroupsSyncTask @ 11:00pm + TriggerBuilder triggerBuilder = TriggerBuilder.Create(). + WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(23, 0)); + + this.ScheduleTask(triggerBuilder); + } + + protected override void ExecuteTask() + { + int changeCount; + + List managedGroups = this.ExecutionContext.JobDetail.JobDataMap["ManagedGroups"] as List; + if (managedGroups == null) + managedGroups = ActiveDirectory.Context.ManagedGroups.Values; + + this.Status.UpdateStatus(0, "Synchronising Active Directory Managed Groups", "Starting"); + + changeCount = ActiveDirectory.Context.ManagedGroups.SyncManagedGroups(managedGroups, this.Status); + + SystemLog.LogInformation(new string[] + { + "Synchronised Active Directory Managed Groups", + changeCount.ToString() + }); + this.Status.SetFinishedMessage(string.Format("Made {0} Changes to Active Directory Groups", changeCount)); + } + + public static ScheduledTaskStatus ScheduleSync(ADManagedGroup ManagedGroup) + { + if (ManagedGroup == null) + throw new ArgumentNullException("ManagedGroup"); + + JobDataMap taskData = new JobDataMap() { + {"ManagedGroups", new List { ManagedGroup } } + }; + + var instance = new ADManagedGroupsSyncTask(); + return instance.ScheduleTask(taskData); + } + public static ScheduledTaskStatus ScheduleSync(IEnumerable ManagedGroups) + { + if (ManagedGroups == null) + throw new ArgumentNullException("ManagedGroups"); + + JobDataMap taskData = new JobDataMap() { + {"ManagedGroups", ManagedGroups.ToList() } + }; + + var instance = new ADManagedGroupsSyncTask(); + return instance.ScheduleTask(taskData); + } + public static ScheduledTaskStatus ScheduleSyncAll() + { + var instance = new ADManagedGroupsSyncTask(); + return instance.ScheduleTask(); + } + } +} diff --git a/Disco.Services/Interop/ActiveDirectory/ADTaskUpdateNetworkLogonDates.cs b/Disco.Services/Interop/ActiveDirectory/ADNetworkLogonDatesUpdateTask.cs similarity index 92% rename from Disco.Services/Interop/ActiveDirectory/ADTaskUpdateNetworkLogonDates.cs rename to Disco.Services/Interop/ActiveDirectory/ADNetworkLogonDatesUpdateTask.cs index 237e73d6..a7ae9cc2 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADTaskUpdateNetworkLogonDates.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADNetworkLogonDatesUpdateTask.cs @@ -12,16 +12,15 @@ using System.Linq; namespace Disco.Services.Interop.ActiveDirectory { - public class ADTaskUpdateNetworkLogonDates : ScheduledTask + public class ADNetworkLogonDatesUpdateTask : ScheduledTask { - public override string TaskName { get { return "Active Directory - Update Last Network Logon Dates Task"; } } public override bool SingleInstanceTask { get { return true; } } public override bool CancelInitiallySupported { get { return false; } } public override void InitalizeScheduledTask(DiscoDataContext Database) { - // ActiveDirectoryUpdateLastNetworkLogonDateJob @ 11:30pm + // ADNetworkLogonDatesUpdateTask @ 11:30pm TriggerBuilder triggerBuilder = TriggerBuilder.Create(). WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(23, 30)); @@ -50,11 +49,11 @@ namespace Disco.Services.Interop.ActiveDirectory public static ScheduledTaskStatus ScheduleImmediately() { - var existingTask = ScheduledTasks.GetTaskStatuses(typeof(ADTaskUpdateNetworkLogonDates)).Where(s => s.IsRunning).FirstOrDefault(); + var existingTask = ScheduledTasks.GetTaskStatuses(typeof(ADNetworkLogonDatesUpdateTask)).Where(s => s.IsRunning).FirstOrDefault(); if (existingTask != null) return existingTask; - var instance = new ADTaskUpdateNetworkLogonDates(); + var instance = new ADNetworkLogonDatesUpdateTask(); return instance.ScheduleTask(); } @@ -68,16 +67,18 @@ namespace Disco.Services.Interop.ActiveDirectory if (!string.IsNullOrEmpty(Device.DeviceDomainId) && Device.DeviceDomainId.Contains('\\')) { var context = ActiveDirectory.Context; - var deviceSamAccountName = UserExtensions.SplitUserId(Device.DeviceDomainId).Item2 + "$"; - var ldapFilter = string.Format(ldapFilterTemplate, ADHelpers.EscapeLdapQuery(deviceSamAccountName)); + string deviceSamAccountName; + ADDomain deviceDomain; - var domain = context.GetDomainFromId(Device.DeviceDomainId); + ActiveDirectory.ParseDomainAccountId(Device.DeviceDomainId + "$", out deviceSamAccountName, out deviceDomain); + + var ldapFilter = string.Format(ldapFilterTemplate, ADHelpers.EscapeLdapQuery(deviceSamAccountName)); IEnumerable domainControllers; if (context.SearchAllForestServers) - domainControllers = domain.GetAllReachableDomainControllers(); + domainControllers = deviceDomain.GetAllReachableDomainControllers(); else - domainControllers = domain.GetReachableSiteDomainControllers(); + domainControllers = deviceDomain.GetReachableSiteDomainControllers(); lastLogon = domainControllers.Select(dc => { diff --git a/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs b/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs index fa923143..8eb389ac 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs @@ -111,32 +111,41 @@ namespace Disco.Services.Interop.ActiveDirectory additionalProperties); } + [Obsolete("Use generic equivalents: GetPropertyValue(string PropertyName)")] public object GetPropertyValue(string PropertyName, int Index = 0) + { + return GetPropertyValues(PropertyName).Skip(Index).FirstOrDefault(); + } + public T GetPropertyValue(string PropertyName) + { + return GetPropertyValues(PropertyName).FirstOrDefault(); + } + public IEnumerable GetPropertyValues(string PropertyName) { switch (PropertyName.ToLower()) { case "name": - return this.Name; + return new string[] { this.Name }.OfType(); case "samaccountname": - return this.SamAccountName; + return new string[] { this.SamAccountName }.OfType(); case "distinguishedname": - return this.DistinguishedName; + return new string[] { this.DistinguishedName }.OfType(); case "objectsid": - return this.SecurityIdentifier.ToString(); + return new SecurityIdentifier[] { this.SecurityIdentifier }.OfType(); case "sn": - return this.Surname; + return new string[] { this.Surname }.OfType(); case "givenname": - return this.GivenName; + return new string[] { this.GivenName }.OfType(); case "mail": - return this.Email; + return new string[] { this.Email }.OfType(); case "telephonenumber": - return this.Phone; + return new string[] { this.Phone }.OfType(); default: object[] adProperty; - if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty) && Index <= adProperty.Length) - return adProperty[Index]; + if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty)) + return adProperty.OfType(); else - return null; + return Enumerable.Empty(); } } diff --git a/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs b/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs index df8accc4..f4e0f3c2 100644 --- a/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs +++ b/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs @@ -114,23 +114,23 @@ namespace Disco.Services.Interop.ActiveDirectory #region Groups - public static ADGroup RetrieveADGroup(string Id) + public static ADGroup RetrieveADGroup(string Id, params string[] AdditionalProperties) { var domain = Context.GetDomainFromId(Id); - return domain.GetAvailableDomainController().RetrieveADGroup(Id); + return domain.GetAvailableDomainController().RetrieveADGroup(Id, AdditionalProperties); } - public static ADGroup RetrieveADGroupByDistinguishedName(string DistinguishedName) + public static ADGroup RetrieveADGroupByDistinguishedName(string DistinguishedName, params string[] AdditionalProperties) { var domain = Context.GetDomainFromDistinguishedName(DistinguishedName); - return domain.GetAvailableDomainController().RetrieveADGroupByDistinguishedName(DistinguishedName); + return domain.GetAvailableDomainController().RetrieveADGroupByDistinguishedName(DistinguishedName, AdditionalProperties); } - public static ADGroup RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier SecurityIdentifier) + public static ADGroup RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier SecurityIdentifier, params string[] AdditionalProperties) { var domain = Context.GetDomainFromSecurityIdentifier(SecurityIdentifier); - return domain.GetAvailableDomainController().RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier); + return domain.GetAvailableDomainController().RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier, AdditionalProperties); } - public static IEnumerable SearchADGroups(string Term, int? ResultLimit = ActiveDirectory.DefaultSearchResultLimit) + public static IEnumerable SearchADGroups(string Term, int? ResultLimit = ActiveDirectory.DefaultSearchResultLimit, params string[] AdditionalProperties) { if (string.IsNullOrWhiteSpace(Term)) throw new ArgumentNullException("Term"); @@ -141,7 +141,7 @@ namespace Disco.Services.Interop.ActiveDirectory if (string.IsNullOrWhiteSpace(term)) return Enumerable.Empty(); - var ldapFilter= string.Format(ADGroup.LdapSearchFilterTemplate, ADHelpers.EscapeLdapQuery(term)); + var ldapFilter = string.Format(ADGroup.LdapSearchFilterTemplate, ADHelpers.EscapeLdapQuery(term)); IEnumerable searchResults; if (searchDomain != null) @@ -149,7 +149,7 @@ namespace Disco.Services.Interop.ActiveDirectory else searchResults = Context.SearchScope(ldapFilter, ADGroup.LoadProperties, ResultLimit); - return searchResults.Select(result => result.AsADGroup()); + return searchResults.Select(result => result.AsADGroup(AdditionalProperties)); } #endregion @@ -185,6 +185,124 @@ namespace Disco.Services.Interop.ActiveDirectory #region Helpers + public static string ParseDomainAccountId(string AccountId) + { + return ParseDomainAccountId(AccountId, null); + } + public static string ParseDomainAccountId(string AccountId, string AccountDomain) + { + string accountUsername; + ADDomain domain; + + return ParseDomainAccountId(AccountId, AccountDomain, out accountUsername, out domain); + } + public static string ParseDomainAccountId(string AccountId, out string AccountUsername) + { + return ParseDomainAccountId(AccountId, null, out AccountUsername); + } + public static string ParseDomainAccountId(string AccountId, string AccountDomain, out string AccountUsername) + { + ADDomain domain; + + return ParseDomainAccountId(AccountId, AccountDomain, out AccountUsername, out domain); + } + public static string ParseDomainAccountId(string AccountId, out ADDomain Domain) + { + return ParseDomainAccountId(AccountId, null, out Domain); + } + public static string ParseDomainAccountId(string AccountId, string AccountDomain, out ADDomain Domain) + { + string accountUsername; + + return ParseDomainAccountId(AccountId, AccountDomain, out accountUsername, out Domain); + } + public static string ParseDomainAccountId(string AccountId, out string AccountUsername, out ADDomain Domain) + { + return ParseDomainAccountId(AccountId, null, out AccountUsername, out Domain); + } + public static string ParseDomainAccountId(string AccountId, string AccountDomain, out string AccountUsername, out ADDomain Domain) + { + if (string.IsNullOrWhiteSpace(AccountId)) + throw new ArgumentNullException("AccountId"); + + var slashIndex = AccountId.IndexOf('\\'); + + if (slashIndex < 0 && !string.IsNullOrWhiteSpace(AccountDomain)) + { + AccountId = AccountDomain + @"\" + AccountId; + slashIndex = AccountDomain.Length; + } + + if (slashIndex < 0) + { + AccountUsername = AccountId; + Domain = Context.PrimaryDomain; + } + else + { + AccountUsername = AccountId.Substring(slashIndex + 1); + Domain = Context.GetDomainByNetBiosName(AccountId.Substring(0, slashIndex)); + } + + return string.Concat(Domain.NetBiosName, @"\", AccountUsername); + } + + public static bool IsValidDomainAccountId(string AccountId) + { + string accountUsername; + ADDomain domain; + + return IsValidDomainAccountId(AccountId, out accountUsername, out domain); + } + public static bool IsValidDomainAccountId(string AccountId, out string AccountUsername) + { + ADDomain domain; + + return IsValidDomainAccountId(AccountId, out AccountUsername, out domain); + } + public static bool IsValidDomainAccountId(string AccountId, out ADDomain Domain) + { + string accountUsername; + + return IsValidDomainAccountId(AccountId, out accountUsername, out Domain); + } + public static bool IsValidDomainAccountId(string AccountId, out string AccountUsername, out ADDomain Domain) + { + if (string.IsNullOrEmpty(AccountId)) + { + AccountUsername = null; + Domain = null; + return false; + } + + var slashIndex = AccountId.IndexOf('\\'); + if (slashIndex < 0) + { + AccountUsername = AccountId; + Domain = null; + return false; + } + else + { + AccountUsername = AccountId.Substring(slashIndex + 1); + return ActiveDirectory.Context.TryGetDomainByNetBiosName(AccountId.Substring(0, slashIndex), out Domain); + } + } + + /// + /// If the AccountId Domain matches the Primary Domain, returns the Account Username without the Domain specified + /// + /// + public static string FriendlyAccountId(string AccountId) + { + var slashIndex = AccountId.IndexOf('\\'); + + if (slashIndex > 0 && AccountId.Substring(0, slashIndex).Equals(ActiveDirectory.Context.PrimaryDomain.NetBiosName, StringComparison.OrdinalIgnoreCase)) + return AccountId.Substring(slashIndex + 1); + else + return AccountId; + } + private static string RelevantSearchTerm(string Term, out ADDomain Domain) { Domain = null; diff --git a/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryContext.cs b/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryContext.cs index 399dbe6b..f5de4d7d 100644 --- a/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryContext.cs +++ b/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryContext.cs @@ -15,6 +15,7 @@ namespace Disco.Services.Interop.ActiveDirectory public ADSite Site { get; private set; } public ADDomain PrimaryDomain { get; private set; } public List Domains { get; private set; } + public ActiveDirectoryManagedGroups ManagedGroups { get; private set; } public List ForestServers { @@ -39,7 +40,12 @@ namespace Disco.Services.Interop.ActiveDirectory #region Contructor/Initializing - internal ActiveDirectoryContext(DiscoDataContext Database) + private ActiveDirectoryContext() + { + ManagedGroups = new ActiveDirectoryManagedGroups(); + } + + internal ActiveDirectoryContext(DiscoDataContext Database) : this() { Initialize(Database); } @@ -138,24 +144,24 @@ namespace Disco.Services.Interop.ActiveDirectory if (string.IsNullOrWhiteSpace(Id)) throw new ArgumentNullException("Id"); - var idSplit = UserExtensions.SplitUserId(Id); + var slashIndex = Id.IndexOf('\\'); - if (string.IsNullOrWhiteSpace(idSplit.Item1)) + if (slashIndex < 0) throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id"); - return TryGetDomainByNetBiosName(idSplit.Item1, out Domain); + return TryGetDomainByNetBiosName(Id.Substring(0, slashIndex), out Domain); } public ADDomain GetDomainFromId(string Id) { if (string.IsNullOrWhiteSpace(Id)) throw new ArgumentNullException("Id"); - var idSplit = UserExtensions.SplitUserId(Id); + var slashIndex = Id.IndexOf('\\'); - if (string.IsNullOrWhiteSpace(idSplit.Item1)) + if (slashIndex < 0) throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id"); - return GetDomainByNetBiosName(idSplit.Item1); + return GetDomainByNetBiosName(Id.Substring(0, slashIndex)); } #endregion diff --git a/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryExtensions.cs b/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryExtensions.cs index 9e19096f..3c560b61 100644 --- a/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryExtensions.cs +++ b/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryExtensions.cs @@ -69,21 +69,21 @@ namespace Disco.Services.Interop.ActiveDirectory } // Groups - public static ADGroup AsADGroup(this ADSearchResult SearchResult) + public static ADGroup AsADGroup(this ADSearchResult SearchResult, string[] AdditionalProperties) { - return ADGroup.FromSearchResult(SearchResult); + return ADGroup.FromSearchResult(SearchResult, AdditionalProperties); } - public static IEnumerable AsADGroups(this IEnumerable SearchResults) + public static IEnumerable AsADGroups(this IEnumerable SearchResults, string[] AdditionalProperties) { - return SearchResults.Select(sr => ADGroup.FromSearchResult(sr)); + return SearchResults.Select(sr => ADGroup.FromSearchResult(sr, AdditionalProperties)); } - public static ADGroup AsADGroup(this ADDirectoryEntry DirectoryEntry) + public static ADGroup AsADGroup(this ADDirectoryEntry DirectoryEntry, string[] AdditionalProperties) { - return ADGroup.FromDirectoryEntry(DirectoryEntry); + return ADGroup.FromDirectoryEntry(DirectoryEntry, AdditionalProperties); } - public static IEnumerable AsADGroups(this IEnumerable DirectoryEntries) + public static IEnumerable AsADGroups(this IEnumerable DirectoryEntries, string[] AdditionalProperties) { - return DirectoryEntries.Select(de => ADGroup.FromDirectoryEntry(de)); + return DirectoryEntries.Select(de => ADGroup.FromDirectoryEntry(de, AdditionalProperties)); } // Organisational Units diff --git a/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryManagedGroups.cs b/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryManagedGroups.cs new file mode 100644 index 00000000..379f8cbb --- /dev/null +++ b/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryManagedGroups.cs @@ -0,0 +1,483 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Subjects; +using System.Text; +using System.Threading.Tasks; +using Disco.Data.Repository; + +namespace Disco.Services.Interop.ActiveDirectory +{ + using Disco.Services.Logging; + using Disco.Services.Tasks; + using ScheduledActionItemGrouping = List>>; + + public class ActiveDirectoryManagedGroups : IDisposable + { + private ConcurrentDictionary managedGroups; + private Subject actionBuffer; + private IDisposable actionBufferSubscription; + + internal ActiveDirectoryManagedGroups() + { + managedGroups = new ConcurrentDictionary(); + actionBuffer = new Subject(); + + // Subscribe, wait for no additional actions after 10 seconds + actionBufferSubscription = actionBuffer + .BufferWithInactivity(TimeSpan.FromSeconds(10)) + .Subscribe(ParseScheduledActions); + } + + #region Collection Methods + public void AddOrUpdate(ADManagedGroup ManagedGroup) + { + ManagedGroup.Context = this; + ManagedGroup.Initialize(); + + string key = ManagedGroup.Key; + + var existingGroup = managedGroups.Values + .Where(g => g.Key != ManagedGroup.Key) + .FirstOrDefault(g => g.Configuration.GroupId.Equals(ManagedGroup.Configuration.GroupId, StringComparison.OrdinalIgnoreCase)); + + if (existingGroup != null) + throw new ArgumentException(string.Format("[{0}] cannot manage this group [{1}] because is already managed by [{2}]", ManagedGroup.Key, ManagedGroup.Configuration.GroupId, existingGroup.Key), "ManagedGroup"); + + managedGroups.AddOrUpdate(key, ManagedGroup, (itemKey, item) => + { + item.Dispose(); + return ManagedGroup; + }); + } + public bool Remove(string Key) + { + ADManagedGroup item; + + if (managedGroups.TryRemove(Key, out item)) + { + item.Dispose(); + return true; + } + + return false; + } + public bool TryGetValue(string Key, out ADManagedGroup ManagedGroup) + { + return managedGroups.TryGetValue(Key, out ManagedGroup); + } + public List Values + { + get + { + return managedGroups.Values.ToList(); + } + } + #endregion + + public string ValidateGroupId(string GroupId, string IgnoreManagedGroupKey) + { + var group = ActiveDirectory.RetrieveADGroup(GroupId, "isCriticalSystemObject"); + if (group == null) + throw new ArgumentException(string.Format("The group [{0}] wasn't found", GroupId), "DevicesLinkedGroup"); + if (group.GetPropertyValue("isCriticalSystemObject")) + throw new ArgumentException(string.Format("The group [{0}] is a Critical System Active Directory Object and Disco refuses to modify it", group.DistinguishedName), "DevicesLinkedGroup"); + + GroupId = group.Id; + + var otherManagedGroup = ActiveDirectory.Context.ManagedGroups.Values + .Where(g => g.Key != IgnoreManagedGroupKey) + .FirstOrDefault(g => g.Configuration.GroupId.Equals(GroupId, StringComparison.OrdinalIgnoreCase)); + if (otherManagedGroup != null) + throw new ArgumentException(string.Format("Cannot manage this group [{0}] because is already managed by [{1}]", GroupId, otherManagedGroup.Key), "DevicesLinkedGroup"); + + return GroupId; + } + + internal void ScheduleAction(ADManagedGroupScheduledAction ScheduledAction) + { + actionBuffer.OnNext(ScheduledAction); + } + + private void ParseScheduledActions(IEnumerable Actions) + { + ScheduledActionItemGrouping groupedActionItems; + + using (DiscoDataContext Database = new DiscoDataContext()) + { + groupedActionItems = Actions + .GroupBy(a => a.ManagedGroup) + .Where(g => + { + ADManagedGroup item; + if (managedGroups.TryGetValue(g.Key.Key, out item)) + return item == g.Key; + else + return false; + }) + .Select(g => // Reduce actions to last instance of ActionSubjectId + Tuple.Create( + g.Key, + g.GroupBy(i => i.InvokingIdentifier, (id, idg) => idg.Last()) + ) + ).Select(g => // Resolve action group members (action subjects) + Tuple.Create(g.Item1, g.Item2.SelectMany(i => i.ResolveMembers(Database))) + ).Select(g => // Reduce actions to last instance of MemberId + Tuple.Create( + g.Item1, + g.Item2.GroupBy(i => i.MemberId, (id, idg) => idg.Last()).ToList() + ) + ).ToList(); + } + + ApplyScheduledActionItems(groupedActionItems); + } + + private void ApplyScheduledActionItems(ScheduledActionItemGrouping ActionGroups) + { + var actionsCount = ActionGroups.SelectMany(a => a.Item2).Count(); + if (actionsCount > 0) + { + var adSearchLoadProperties = new string[] { "distinguishedName", "sAMAccountName" }; + var accountDNCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (actionsCount > 40) + { + // Potentially over 40 accounts, cache all scoped + var scopeAccounts = ActiveDirectory.Context.SearchScope("(|(objectCategory=computer)(objectCategory=person))", adSearchLoadProperties); + foreach (var scopeAccount in scopeAccounts) + { + var id = string.Format(@"{0}\{1}", scopeAccount.Domain.NetBiosName, scopeAccount.Value("sAMAccountName")); + accountDNCache[id] = scopeAccount.Value("distinguishedName"); + } + } + + foreach (var actionGroup in ActionGroups) + { + // Resolve Member Ids to AD Distinguished Names + // Discard non-existent users + var actionItems = actionGroup.Item2.Select(a => + { + string distinguishedName; + if (!accountDNCache.TryGetValue(a.MemberId, out distinguishedName)) + { + string memberUsername; + ADDomain memberDomain; + if (!ActiveDirectory.IsValidDomainAccountId(a.MemberId, out memberUsername, out memberDomain)) + { + accountDNCache[a.MemberId] = null; // Add to cache (avoid retries) + return null; + } + + var ldapFilter = string.Format("(&(|(objectCategory=computer)(objectCategory=person))(sAMAccountName={0}))", memberUsername); + + var adSearchResult = memberDomain.SearchEntireDomain(ldapFilter, adSearchLoadProperties, ActiveDirectory.SingleSearchResult).FirstOrDefault(); + if (adSearchResult != null) + { + var adSearchResultDN = adSearchResult.Value("distinguishedName"); + accountDNCache[a.MemberId] = adSearchResultDN; // Add to cache + a.MemberDistinguishedName = adSearchResultDN; // Update ActionItem + return a; + } + else + { + accountDNCache[a.MemberId] = null; // Add to cache (avoid retries) + return null; + } + } + else if (distinguishedName == null) + return null; + else + { + a.MemberDistinguishedName = distinguishedName; // Update ActionItem + return a; + } + }).Where(a => a != null).ToList(); + + if (actionItems.Count > 0) + { + var adGroup = actionGroup.Item1.GetGroup(); + if (adGroup == null) + { + SystemLog.LogWarning("Active Directory Managed Group", actionGroup.Item1.Key, "Group Not Found", actionGroup.Item1.Configuration.GroupId); + break; + } + var adGroupMembers = adGroup.GetPropertyValues("member").ToList(); + actionItems = actionItems.Where(a => + { + switch (a.ActionType) + { + case ADManagedGroupScheduledActionType.AddGroupMember: + return !adGroupMembers.Contains(a.MemberDistinguishedName); + case ADManagedGroupScheduledActionType.RemoveGroupMember: + return adGroupMembers.Contains(a.MemberDistinguishedName); + default: + return false; + } + }).ToList(); + + if (actionItems.Count > 0) + { + using (var adGroupEntry = ActiveDirectory.Context.RetrieveDirectoryEntry(adGroup.DistinguishedName, new string[] { "member", "isCriticalSystemObject" })) + { + if (adGroupEntry.Entry.Properties.Value("isCriticalSystemObject")) + throw new InvalidOperationException(string.Format("This group [{0}] is a Critical System Active Directory Object and Disco refuses to modify it", adGroup.DistinguishedName)); + + var adGroupEntryMembers = adGroupEntry.Entry.Properties["member"]; + foreach (var item in actionItems) + { + switch (item.ActionType) + { + case ADManagedGroupScheduledActionType.AddGroupMember: + if (!adGroupEntryMembers.Contains(item.MemberDistinguishedName)) + { + // Add Member Entry + adGroupEntryMembers.Add(item.MemberDistinguishedName); + } + break; + case ADManagedGroupScheduledActionType.RemoveGroupMember: + if (adGroupEntryMembers.Contains(item.MemberDistinguishedName)) + { + // Add Member Entry + adGroupEntryMembers.Remove(item.MemberDistinguishedName); + } + break; + } + } + + // Commit Changes + adGroupEntry.Entry.CommitChanges(); + } + } + } + } + } + } + + public int SyncManagedGroups(IScheduledTaskStatus Status) + { + return SyncManagedGroups(managedGroups.Values, Status); + } + + public int SyncManagedGroups(ADManagedGroup ManagedGroup, IScheduledTaskStatus Status) + { + return SyncManagedGroups(new ADManagedGroup[] { ManagedGroup }, Status); + } + + public int SyncManagedGroups(IEnumerable ManagedGroups, IScheduledTaskStatus Status) + { + List managedGroups = ManagedGroups.ToList(); + ScheduledActionItemGrouping actionGroups; + int changeCount = 0; + + Status.UpdateStatus(0, "Determining Managed Group Members"); + + using (DiscoDataContext Database = new DiscoDataContext()) + { + actionGroups = managedGroups.Select((g, index) => + { + Status.UpdateStatus( + ((double)30 / managedGroups.Count) * index, // 0 -> 30 + string.Format("Determining Group Members: {0} [{1}]", g.GroupDescription, g.Configuration.GroupId)); + return Tuple.Create( + g, + g.DetermineMembers(Database).Select(m => + new ADManagedGroupScheduledActionItem( + g, + ADManagedGroupScheduledActionType.AddGroupMember, + m + )).ToList()); + }).ToList(); + } + + var actionsCount = actionGroups.SelectMany(a => a.Item2).Count(); + if (actionsCount > 0) + { + Status.UpdateStatus(30, "Resolving Group Members"); + + var adSearchLoadProperties = new string[] { "distinguishedName", "sAMAccountName", "displayName", "name" }; + var accountDNCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); + if (actionsCount > 40) + { + // Potentially over 40 accounts, cache all scoped + var scopeAccounts = ActiveDirectory.Context.SearchScope("(|(objectCategory=computer)(objectCategory=person))", adSearchLoadProperties); + foreach (var scopeAccount in scopeAccounts) + { + var id = string.Format(@"{0}\{1}", scopeAccount.Domain.NetBiosName, scopeAccount.Value("sAMAccountName")); + accountDNCache[id] = Tuple.Create(scopeAccount.Value("distinguishedName"), scopeAccount.Value("displayName") ?? scopeAccount.Value("name")); + } + } + + actionGroups = actionGroups.Select((g, index) => + { + Status.UpdateStatus( + 30 + (((double)30 / actionGroups.Count) * index), // 30 -> 60 + string.Format("Resolving {0} Group Members: {1} [{2}]", g.Item2.Count, g.Item1.GroupDescription, g.Item1.Configuration.GroupId)); + + // Resolve Member Ids to AD Distinguished Names + // Discard non-existent users + return Tuple.Create( + g.Item1, + g.Item2.Select(a => + { + Tuple definition; + if (!accountDNCache.TryGetValue(a.MemberId, out definition)) + { + string memberUsername; + ADDomain memberDomain; + if (!ActiveDirectory.IsValidDomainAccountId(a.MemberId, out memberUsername, out memberDomain)) + { + accountDNCache[a.MemberId] = null; // Add to cache (avoid retries) + return null; + } + + var ldapFilter = string.Format("(&(|(objectCategory=computer)(objectCategory=person))(sAMAccountName={0}))", memberUsername); + + var adSearchResult = memberDomain.SearchEntireDomain(ldapFilter, adSearchLoadProperties, ActiveDirectory.SingleSearchResult).FirstOrDefault(); + if (adSearchResult != null) + { + definition = Tuple.Create(adSearchResult.Value("distinguishedName"), adSearchResult.Value("displayName") ?? adSearchResult.Value("name")); + accountDNCache[a.MemberId] = definition; // Add to cache + } + else + { + accountDNCache[a.MemberId] = null; // Add to cache (avoid retries) + return null; + } + } + else if (definition == null) + return null; + + a.MemberDistinguishedName = definition.Item1; // Update ActionItem + a.MemberDisplayName = definition.Item2; + return a; + }).Where(a => a != null).ToList()); + }).ToList(); + } + + foreach (var actionGroup in actionGroups) + { + var adGroup = actionGroup.Item1.GetGroup(); + if (adGroup == null) + { + SystemLog.LogWarning("Active Directory Managed Group", actionGroup.Item1.Key, "Group Not Found", actionGroup.Item1.Configuration.GroupId); + break; + } + + Status.UpdateStatus( + 60 + (((double)40 / actionGroups.Count) * actionGroups.IndexOf(actionGroup)), // 60 -> 100 + string.Format("Synchronizing {0} Group Members: {1} [{2}]", actionGroup.Item2.Count, actionGroup.Item1.GroupDescription, actionGroup.Item1.Configuration.GroupId)); + + using (var adGroupEntry = ActiveDirectory.Context.RetrieveDirectoryEntry(adGroup.DistinguishedName, new string[] { "isCriticalSystemObject", "description", "member" })) + { + if (adGroupEntry.Entry.Properties.Value("isCriticalSystemObject")) + throw new InvalidOperationException(string.Format("This group [{0}] is a Critical System Active Directory Object and Disco refuses to modify it", adGroup.DistinguishedName)); + + // Update Description + var groupDescription = string.Format("Disco ICT: {0}", actionGroup.Item1.GroupDescription); + if (adGroupEntry.Entry.Properties.Value("description") != groupDescription) + { + var adGroupEntryDescription = adGroupEntry.Entry.Properties["description"]; + if (adGroupEntryDescription.Count > 0) + adGroupEntryDescription.Clear(); + adGroupEntryDescription.Add(groupDescription); + } + + // Sync Members + var adGroupEntryMembers = adGroupEntry.Entry.Properties["member"]; + + // Remove Items + var removeItems = adGroupEntryMembers + .Cast() + .Except(actionGroup.Item2.Select(i => i.MemberDistinguishedName)) + .ToList(); + removeItems.ForEach(i => adGroupEntryMembers.Remove(i)); + + // Add Items + var addItems = actionGroup. + Item2.Select(i => i.MemberDistinguishedName) + .Except(adGroupEntryMembers.Cast()) + .ToList(); + addItems.ForEach(i => adGroupEntryMembers.Add(i)); + + // Commit Changes + adGroupEntry.Entry.CommitChanges(); + + changeCount += removeItems.Count; + changeCount += addItems.Count; + } + } + + Status.UpdateStatus(100, "Managed Group Synchronization Finished"); + + return changeCount; + } + + public void Dispose() + { + if (actionBufferSubscription != null) + actionBufferSubscription.Dispose(); + + if (actionBuffer != null) + actionBuffer.Dispose(); + } + } + + internal class ADManagedGroupScheduledAction + { + private Func> memberResolver; + + public ADManagedGroup ManagedGroup { get; private set; } + public ADManagedGroupScheduledActionType ActionType { get; private set; } + public string InvokingIdentifier { get; set; } + + public ADManagedGroupScheduledAction(ADManagedGroup ManagedGroup, ADManagedGroupScheduledActionType ActionType, string InvokingIdentifier, Func> MemberResolver) + { + this.ManagedGroup = ManagedGroup; + this.ActionType = ActionType; + this.InvokingIdentifier = InvokingIdentifier; + this.memberResolver = MemberResolver; + } + + public IEnumerable ResolveMembers(DiscoDataContext Database) + { + if (memberResolver != null) + { + var members = memberResolver(Database); + if (members == null) + return Enumerable.Empty(); + else + return members.Select(m => + new ADManagedGroupScheduledActionItem(this.ManagedGroup, this.ActionType, m) + ); + } + else + { + return new ADManagedGroupScheduledActionItem[] + { + new ADManagedGroupScheduledActionItem(this.ManagedGroup, this.ActionType, this.InvokingIdentifier) + }; + } + } + } + internal class ADManagedGroupScheduledActionItem + { + public ADManagedGroup ManagedGroup { get; private set; } + public ADManagedGroupScheduledActionType ActionType { get; private set; } + public string MemberId { get; set; } + public string MemberDistinguishedName { get; set; } + public string MemberDisplayName { get; set; } + + public ADManagedGroupScheduledActionItem(ADManagedGroup ManagedGroup, ADManagedGroupScheduledActionType ActionType, string MemberId) + { + this.ManagedGroup = ManagedGroup; + this.ActionType = ActionType; + this.MemberId = MemberId; + } + } + internal enum ADManagedGroupScheduledActionType + { + AddGroupMember, + RemoveGroupMember + } +} diff --git a/Disco.Services/Interop/ActiveDirectory/Description.cs b/Disco.Services/Interop/ActiveDirectory/Description.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/Disco.Services/Interop/ActiveDirectory/Description.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Disco.Services/Jobs/JobExtensions.cs b/Disco.Services/Jobs/JobExtensions.cs index 9455e5f5..f915dad6 100644 --- a/Disco.Services/Jobs/JobExtensions.cs +++ b/Disco.Services/Jobs/JobExtensions.cs @@ -63,12 +63,12 @@ namespace Disco.Services i.UserId = j.UserId; i.UserDisplayName = j.User.DisplayName; - i.UserFriendlyId = UserExtensions.FriendlyUserId(j.UserId); + i.UserFriendlyId = ActiveDirectory.FriendlyAccountId(j.UserId); } if (j.OpenedTechUser != null) { i.OpenedTechUserId = j.OpenedTechUserId; - i.OpenedTechUserFriendlyId = UserExtensions.FriendlyUserId(j.OpenedTechUserId); + i.OpenedTechUserFriendlyId = ActiveDirectory.FriendlyAccountId(j.OpenedTechUserId); i.OpenedTechUserDisplayName = j.OpenedTechUser.DisplayName; } diff --git a/Disco.Services/Jobs/JobLists/JobTableExtensions.cs b/Disco.Services/Jobs/JobLists/JobTableExtensions.cs index b2939a47..e90a64c7 100644 --- a/Disco.Services/Jobs/JobLists/JobTableExtensions.cs +++ b/Disco.Services/Jobs/JobLists/JobTableExtensions.cs @@ -2,6 +2,7 @@ using Disco.Models.Repository; using Disco.Models.Services.Jobs.JobLists; using Disco.Services.Authorization; +using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Users; using System; using System.Collections.Generic; @@ -233,8 +234,8 @@ namespace Disco.Services foreach (var j in items) { - j.UserFriendlyId =j.UserId == null ? null : UserExtensions.FriendlyUserId(j.UserId); - j.OpenedTechUserFriendlyId = UserExtensions.FriendlyUserId(j.OpenedTechUserId); + j.UserFriendlyId =j.UserId == null ? null : ActiveDirectory.FriendlyAccountId(j.UserId); + j.OpenedTechUserFriendlyId = ActiveDirectory.FriendlyAccountId(j.OpenedTechUserId); if (j.DeviceAddressId.HasValue) j.DeviceAddress = Database.DiscoConfiguration.OrganisationAddresses.GetAddress(j.DeviceAddressId.Value).Name; diff --git a/Disco.Services/Jobs/Noticeboards/HeldDeviceItem.cs b/Disco.Services/Jobs/Noticeboards/HeldDeviceItem.cs index c463cf7b..2c52f30a 100644 --- a/Disco.Services/Jobs/Noticeboards/HeldDeviceItem.cs +++ b/Disco.Services/Jobs/Noticeboards/HeldDeviceItem.cs @@ -1,6 +1,7 @@ using Disco.Data.Configuration.Modules; using Disco.Models.Repository; using Disco.Models.Services.Jobs.Noticeboards; +using Disco.Services.Interop.ActiveDirectory; using System; using System.Collections.Generic; using System.Linq; @@ -17,7 +18,7 @@ namespace Disco.Services.Jobs.Noticeboards { get { - return DeviceComputerName == null ? null : UserExtensions.FriendlyUserId(DeviceComputerName); + return DeviceComputerName == null ? null : ActiveDirectory.FriendlyAccountId(DeviceComputerName); } set { } // for XML Serialization } @@ -67,7 +68,7 @@ namespace Disco.Services.Jobs.Noticeboards { get { - return UserId == null ? null : UserExtensions.FriendlyUserId(UserId); + return UserId == null ? null : ActiveDirectory.FriendlyAccountId(UserId); } set { } // for XML Serialization } diff --git a/Disco.Services/Jobs/Noticeboards/HeldDevicesForUsers.cs b/Disco.Services/Jobs/Noticeboards/HeldDevicesForUsers.cs index 301b63d0..3326020e 100644 --- a/Disco.Services/Jobs/Noticeboards/HeldDevicesForUsers.cs +++ b/Disco.Services/Jobs/Noticeboards/HeldDevicesForUsers.cs @@ -82,9 +82,7 @@ namespace Disco.Services.Jobs.Noticeboards } public static IHeldDeviceItem GetHeldDeviceForUsers(DiscoDataContext Database, string UserId) { - var split = UserExtensions.SplitUserId(UserId); - if (split.Item1 == null) - UserId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, UserId); + UserId = ActiveDirectory.ParseDomainAccountId(UserId); return GetHeldDevicesForUsers(Database.Devices.Where(d => d.AssignedUserId == UserId).SelectMany(d => d.Jobs)).FirstOrDefault(); } diff --git a/Disco.Services/Searching/Search.cs b/Disco.Services/Searching/Search.cs index 1843892d..afc72af5 100644 --- a/Disco.Services/Searching/Search.cs +++ b/Disco.Services/Searching/Search.cs @@ -235,7 +235,7 @@ namespace Disco.Services.Searching UserFlagAssignments = u.UserFlagAssignments }).ToList(); - results.ForEach(u => u.FriendlyId = UserExtensions.FriendlyUserId(u.Id)); + results.ForEach(u => u.FriendlyId = ActiveDirectory.FriendlyAccountId(u.Id)); return results; } diff --git a/Disco.Services/Users/UserExtensions.cs b/Disco.Services/Users/UserExtensions.cs index 7539e8a2..5e84590f 100644 --- a/Disco.Services/Users/UserExtensions.cs +++ b/Disco.Services/Users/UserExtensions.cs @@ -18,26 +18,7 @@ namespace Disco.Services public static string FriendlyId(this User u) { - return FriendlyUserId(u.UserId); - } - - public static string FriendlyUserId(string UserId) - { - var splitUserId = SplitUserId(UserId); - - if (splitUserId.Item1 != null && splitUserId.Item1.Equals(ActiveDirectory.Context.PrimaryDomain.NetBiosName, StringComparison.OrdinalIgnoreCase)) - return splitUserId.Item2; - else - return UserId; - } - - public static Tuple SplitUserId(string UserId) - { - var slashIndex = UserId.IndexOf('\\'); - if (slashIndex < 0) - return Tuple.Create(null, UserId); - else - return Tuple.Create(UserId.Substring(0, slashIndex), UserId.Substring(slashIndex + 1)); + return ActiveDirectory.FriendlyAccountId(u.UserId); } } } diff --git a/Disco.Services/Users/UserFlags/Cache.cs b/Disco.Services/Users/UserFlags/Cache.cs index 9cedf708..4fd3957e 100644 --- a/Disco.Services/Users/UserFlags/Cache.cs +++ b/Disco.Services/Users/UserFlags/Cache.cs @@ -42,28 +42,9 @@ namespace Disco.Services.Users.UserFlags return _Cache.Values.ToList(); } - public UserFlag Update(UserFlag UserFlag) + public void AddOrUpdate(UserFlag UserFlag) { - UserFlag existingItem; - - if (_Cache.TryGetValue(UserFlag.Id, out existingItem)) - { - if (_Cache.TryUpdate(UserFlag.Id, UserFlag, existingItem)) - { - return UserFlag; - } - else - return null; - } - else - { - if (_Cache.TryAdd(UserFlag.Id, UserFlag)) - { - return UserFlag; - } - else - return null; - } + _Cache.AddOrUpdate(UserFlag.Id, UserFlag, (key, existingItem) => UserFlag); } public UserFlag Remove(int UserFlagId) diff --git a/Disco.Services/Users/UserFlags/UserFlagService.cs b/Disco.Services/Users/UserFlags/UserFlagService.cs index 7ad720ab..a1e96931 100644 --- a/Disco.Services/Users/UserFlags/UserFlagService.cs +++ b/Disco.Services/Users/UserFlags/UserFlagService.cs @@ -1,4 +1,5 @@ using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; using Disco.Models.Repository; using Disco.Services.Extensions; using Disco.Services.Tasks; @@ -9,16 +10,39 @@ using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Reactive.Linq; namespace Disco.Services.Users.UserFlags { public static class UserFlagService { private static Cache _cache; + internal static Lazy> UserFlagAssignmentRepositoryEvents; + + static UserFlagService() + { + // Statically defined (lazy) Assignment Repository Definition + // Used by UserFlagAssignedUsersManagedGroup & UserFlagAssignedUserDevicesManagedGroup + UserFlagAssignmentRepositoryEvents = + new Lazy>(() => + RepositoryMonitor.StreamAfterCommit.Where(e => + e.EntityType == typeof(UserFlagAssignment) && + (e.EventType != RepositoryMonitorEventType.Modified || + e.ModifiedProperties.Contains("RemovedDate")) + ) + ); + } public static void Initialize(DiscoDataContext Database) { _cache = new Cache(Database); + + // Initialize Managed Groups (if configured) + _cache.GetUserFlags().ForEach(uf => + { + UserFlagUsersManagedGroup.Initialize(uf); + UserFlagUserDevicesManagedGroup.Initialize(uf); + }); } public static List GetUserFlags() { return _cache.GetUserFlags(); } @@ -41,13 +65,17 @@ namespace Disco.Services.Users.UserFlags Name = UserFlag.Name, Description = UserFlag.Description, Icon = UserFlag.Icon, - IconColour = UserFlag.IconColour + IconColour = UserFlag.IconColour, + UsersLinkedGroup = UserFlag.UsersLinkedGroup, + UserDevicesLinkedGroup = UserFlag.UserDevicesLinkedGroup }; Database.UserFlags.Add(flag); Database.SaveChanges(); - return _cache.Update(flag); + _cache.AddOrUpdate(flag); + + return flag; } public static UserFlag Update(DiscoDataContext Database, UserFlag UserFlag) { @@ -61,12 +89,20 @@ namespace Disco.Services.Users.UserFlags Database.SaveChanges(); - return _cache.Update(UserFlag); + _cache.AddOrUpdate(UserFlag); + UserFlagUsersManagedGroup.Initialize(UserFlag); + UserFlagUserDevicesManagedGroup.Initialize(UserFlag); + + return UserFlag; } public static void DeleteUserFlag(DiscoDataContext Database, int UserFlagId, IScheduledTaskStatus Status) { UserFlag flag = Database.UserFlags.Find(UserFlagId); + // Dispose of AD Managed Groups + Interop.ActiveDirectory.ActiveDirectory.Context.ManagedGroups.Remove(UserFlagUserDevicesManagedGroup.GetKey(flag)); + Interop.ActiveDirectory.ActiveDirectory.Context.ManagedGroups.Remove(UserFlagUsersManagedGroup.GetKey(flag)); + // Delete Assignments Status.UpdateStatus(0, string.Format("Removing '{0}' [{1}] User Flag", flag.Name, flag.Id), "Starting"); List flagAssignments = Database.UserFlagAssignments.Where(fa => fa.UserFlagId == flag.Id).ToList(); @@ -94,34 +130,43 @@ namespace Disco.Services.Users.UserFlags { if (Users.Count > 0) { - double progressInterval; + const int databaseChunkSize = 100; string comments = string.IsNullOrWhiteSpace(Comments) ? null : Comments.Trim(); var addUsers = Users.Where(u => !u.UserFlagAssignments.Any(a => a.UserFlagId == UserFlag.Id && !a.RemovedDate.HasValue)).ToList(); progressInterval = (double)100 / addUsers.Count; - var addedUserAssignments = addUsers.Select((user, index) => + var addedUserAssignments = addUsers.Chunk(databaseChunkSize).SelectMany((chunk, chunkIndex) => { - Status.UpdateStatus(index * progressInterval, string.Format("Assigning Flag: {0}", user.ToString())); + var chunkIndexOffset = databaseChunkSize * chunkIndex; - var fa = new UserFlagAssignment() + var chunkResults = chunk.Select((user, index) => { - UserFlagId = UserFlag.Id, - UserId = user.UserId, - AddedDate = DateTime.Now, - AddedUserId = Technician.UserId, - Comments = comments - }; + Status.UpdateStatus((chunkIndexOffset + index) * progressInterval, string.Format("Assigning Flag: {0}", user.ToString())); - Database.UserFlagAssignments.Add(fa); + var fa = new UserFlagAssignment() + { + UserFlagId = UserFlag.Id, + UserId = user.UserId, + AddedDate = DateTime.Now, + AddedUserId = Technician.UserId, + Comments = comments + }; + + Database.UserFlagAssignments.Add(fa); + return fa; + }).ToList(); + + // Save Chunk Items to Database Database.SaveChanges(); - return fa; + + return chunkResults; }).Where(fa => fa != null).ToList(); Status.SetFinishedMessage(string.Format("{0} Users/s Added; {1} User/s Skipped", addUsers.Count, (Users.Count - addUsers.Count))); - + return addedUserAssignments; } else @@ -134,6 +179,7 @@ namespace Disco.Services.Users.UserFlags public static IEnumerable BulkAssignOverrideUsers(DiscoDataContext Database, UserFlag UserFlag, User Technician, string Comments, List Users, IScheduledTaskStatus Status) { double progressInterval; + const int databaseChunkSize = 100; string comments = string.IsNullOrWhiteSpace(Comments) ? null : Comments.Trim(); Status.UpdateStatus(0, "Calculating assignment changes"); @@ -147,31 +193,53 @@ namespace Disco.Services.Users.UserFlags progressInterval = (double)100 / (removeAssignments.Count + addUsers.Count); var removedDateTime = DateTime.Now; - removeAssignments.Select((flagAssignment, index) => + // Remove Assignments + removeAssignments.Chunk(databaseChunkSize).SelectMany((chunk, chunkIndex) => { - Status.UpdateStatus(index * progressInterval, string.Format("Removing Flag: {0}", flagAssignment.User.ToString())); - flagAssignment.RemovedDate = removedDateTime; - flagAssignment.RemovedUserId = Technician.UserId; + var chunkIndexOffset = (chunkIndex * databaseChunkSize) + removeAssignments.Count; + + var chunkResults = chunk.Select((flagAssignment, index) => + { + Status.UpdateStatus((chunkIndexOffset + index) * progressInterval, string.Format("Removing Flag: {0}", flagAssignment.User.ToString())); + + flagAssignment.RemovedDate = removedDateTime; + flagAssignment.RemovedUserId = Technician.UserId; + + return flagAssignment; + }).ToList(); + + // Save Chunk Items to Database Database.SaveChanges(); - return flagAssignment; + + return chunkResults; }).ToList(); - var addedUserAssignments = addUsers.Select((user, index) => + // Add Assignments + var addedUserAssignments = addUsers.Chunk(databaseChunkSize).SelectMany((chunk, chunkIndex) => { - Status.UpdateStatus((removeAssignments.Count + index) * progressInterval, string.Format("Assigning Flag: {0}", user.ToString())); + var chunkIndexOffset = (chunkIndex * databaseChunkSize) + removeAssignments.Count; - var fa = new UserFlagAssignment() + var chunkResults = chunk.Select((user, index) => { - UserFlagId = UserFlag.Id, - UserId = user.UserId, - AddedDate = DateTime.Now, - AddedUserId = Technician.UserId, - Comments = comments - }; + Status.UpdateStatus((chunkIndexOffset + index) * progressInterval, string.Format("Assigning Flag: {0}", user.ToString())); - Database.UserFlagAssignments.Add(fa); + var fa = new UserFlagAssignment() + { + UserFlagId = UserFlag.Id, + UserId = user.UserId, + AddedDate = DateTime.Now, + AddedUserId = Technician.UserId, + Comments = comments + }; + + Database.UserFlagAssignments.Add(fa); + return fa; + }).ToList(); + + // Save Chunk Items to Database Database.SaveChanges(); - return fa; + + return chunkResults; }).ToList(); Status.SetFinishedMessage(string.Format("{0} Users/s Added; {1} User/s Removed; {2} User/s Skipped", addUsers.Count, removeAssignments.Count, (Users.Count - addUsers.Count))); diff --git a/Disco.Services/Users/UserFlags/UserFlagUserDevicesManagedGroup.cs b/Disco.Services/Users/UserFlags/UserFlagUserDevicesManagedGroup.cs new file mode 100644 index 00000000..58416052 --- /dev/null +++ b/Disco.Services/Users/UserFlags/UserFlagUserDevicesManagedGroup.cs @@ -0,0 +1,167 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.Services.Users.UserFlags +{ + public class UserFlagUserDevicesManagedGroup : ADManagedGroup + { + private const string KeyFormat = "UserFlag_{0}_UserDevices"; + private const string DescriptionFormat = "User associated with the {0} Flag will have their assigned Devices added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Assigned User Devices Linked Group"; + private const string GroupDescriptionFormat = "{0} [User Flag User Devices]"; + + private IDisposable repositorySubscription; + private int UserFlagId; + private string UserFlagName; + + public override string Description { get { return string.Format(DescriptionFormat, UserFlagName); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, UserFlagName); } } + public override bool IncludeFilterBeginDate { get { return false; } } + + private UserFlagUserDevicesManagedGroup(string Key, ADManagedGroupConfiguration Configuration, UserFlag UserFlag) + : base(Key, Configuration) + { + this.UserFlagId = UserFlag.Id; + this.UserFlagName = UserFlag.Name; + } + + public override void Initialize() + { + // Subscribe to changes + repositorySubscription = UserFlagService.UserFlagAssignmentRepositoryEvents.Value + .Where(e => + (((UserFlagAssignment)e.Entity).UserFlagId == UserFlagId)) + .Subscribe(ProcessRepositoryEvent); + } + + public static string GetKey(UserFlag UserFlag) + { + return string.Format(KeyFormat, UserFlag.Id); + } + public static string GetDescription(UserFlag UserFlag) + { + return string.Format(DescriptionFormat, UserFlag.Name); + } + public static string GetCategoryDescription(UserFlag UserFlag) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(UserFlag UserFlag, out UserFlagUserDevicesManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(UserFlag); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (UserFlagUserDevicesManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static UserFlagUserDevicesManagedGroup Initialize(UserFlag UserFlag) + { + if (UserFlag.Id > 0) + { + var key = GetKey(UserFlag); + + if (!string.IsNullOrEmpty(UserFlag.UserDevicesLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(UserFlag.UserDevicesLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new UserFlagUserDevicesManagedGroup( + key, + config, + UserFlag); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + } + + return null; + } + + private IEnumerable DetermineDeviceMembers(DiscoDataContext Database, string UserId) + { + return DetermineDeviceMembers(Database.Users.Where(u => u.UserId == UserId)); + } + + private IEnumerable DetermineDeviceMembers(IQueryable Users) + { + return Users + .SelectMany(u => u.DeviceUserAssignments) + .Where(da => !da.UnassignedDate.HasValue && da.Device.DeviceDomainId != null) + .Select(da => da.Device.DeviceDomainId) + .ToList() + .Where(ActiveDirectory.IsValidDomainAccountId) + .Select(id => id + "$"); + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + var assignments = Database.UserFlagAssignments + .Where(a => a.UserFlagId == UserFlagId && !a.RemovedDate.HasValue) + .Select(a => a.User); + + return DetermineDeviceMembers(assignments); + } + + private void ProcessRepositoryEvent(RepositoryMonitorEvent Event) + { + var userFlagAssignemnt = (UserFlagAssignment)Event.Entity; + string userId = userFlagAssignemnt.UserId; + + switch (Event.EventType) + { + case RepositoryMonitorEventType.Added: + if (!userFlagAssignemnt.RemovedDate.HasValue) + AddMember(userFlagAssignemnt.UserId, (database) => DetermineDeviceMembers(database, userId)); + break; + case RepositoryMonitorEventType.Modified: + if (userFlagAssignemnt.RemovedDate.HasValue) + RemoveMember(userFlagAssignemnt.UserId, (database) => DetermineDeviceMembers(database, userId)); + else + AddMember(userFlagAssignemnt.UserId, (database) => DetermineDeviceMembers(database, userId)); + break; + case RepositoryMonitorEventType.Deleted: + // Remove the user's devices if no other (non-removed) assignments exist. + RemoveMember(userId, (database) => + { + if (database.UserFlagAssignments.Any(a => a.UserFlagId == UserFlagId && a.UserId == userId && !a.RemovedDate.HasValue)) + return null; + else + return DetermineDeviceMembers(database, userId); + }); + break; + } + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + } + } +} diff --git a/Disco.Services/Users/UserFlags/UserFlagUsersManagedGroup.cs b/Disco.Services/Users/UserFlags/UserFlagUsersManagedGroup.cs new file mode 100644 index 00000000..742a006c --- /dev/null +++ b/Disco.Services/Users/UserFlags/UserFlagUsersManagedGroup.cs @@ -0,0 +1,149 @@ +using Disco.Data.Repository; +using Disco.Data.Repository.Monitor; +using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; + +namespace Disco.Services.Users.UserFlags +{ + public class UserFlagUsersManagedGroup : ADManagedGroup + { + private const string KeyFormat = "UserFlag_{0}_Users"; + private const string DescriptionFormat = "User associated with the {0} Flag will be added to this Active Directory group."; + private const string CategoryDescriptionFormat = "Assigned Users Linked Group"; + private const string GroupDescriptionFormat = "{0} [User Flag Users]"; + + private IDisposable repositorySubscription; + private int UserFlagId; + private string UserFlagName; + + public override string Description { get { return string.Format(DescriptionFormat, UserFlagName); } } + public override string CategoryDescription { get { return CategoryDescriptionFormat; } } + public override string GroupDescription { get { return string.Format(GroupDescriptionFormat, UserFlagName); } } + public override bool IncludeFilterBeginDate { get { return false; } } + + private UserFlagUsersManagedGroup(string Key, ADManagedGroupConfiguration Configuration, UserFlag UserFlag) + : base(Key, Configuration) + { + this.UserFlagId = UserFlag.Id; + this.UserFlagName = UserFlag.Name; + } + + public override void Initialize() + { + // Subscribe to changes + repositorySubscription = UserFlagService.UserFlagAssignmentRepositoryEvents.Value + .Where(e => + (((UserFlagAssignment)e.Entity).UserFlagId == UserFlagId)) + .Subscribe(ProcessRepositoryEvent); + } + + public static string GetKey(UserFlag UserFlag) + { + return string.Format(KeyFormat, UserFlag.Id); + } + public static string GetDescription(UserFlag UserFlag) + { + return string.Format(DescriptionFormat, UserFlag.Name); + } + public static string GetCategoryDescription(UserFlag UserFlag) + { + return CategoryDescriptionFormat; + } + + public static bool TryGetManagedGroup(UserFlag UserFlag, out UserFlagUsersManagedGroup ManagedGroup) + { + ADManagedGroup managedGroup; + string key = GetKey(UserFlag); + + if (ActiveDirectory.Context.ManagedGroups.TryGetValue(key, out managedGroup)) + { + ManagedGroup = (UserFlagUsersManagedGroup)managedGroup; + return true; + } + else + { + ManagedGroup = null; + return false; + } + } + + public static UserFlagUsersManagedGroup Initialize(UserFlag UserFlag) + { + if (UserFlag.Id > 0) + { + var key = GetKey(UserFlag); + + if (!string.IsNullOrEmpty(UserFlag.UsersLinkedGroup)) + { + var config = ADManagedGroup.ConfigurationFromJson(UserFlag.UsersLinkedGroup); + + if (config != null && !string.IsNullOrWhiteSpace(config.GroupId)) + { + var group = new UserFlagUsersManagedGroup( + key, + config, + UserFlag); + + // Add to AD Context + ActiveDirectory.Context.ManagedGroups.AddOrUpdate(group); + + return group; + } + } + + // Remove from AD Context + ActiveDirectory.Context.ManagedGroups.Remove(key); + } + + return null; + } + + public override IEnumerable DetermineMembers(DiscoDataContext Database) + { + return Database.UserFlagAssignments + .Where(a => a.UserFlagId == UserFlagId && !a.RemovedDate.HasValue) + .Select(a => a.UserId); + } + + private void ProcessRepositoryEvent(RepositoryMonitorEvent Event) + { + var userFlagAssignemnt = (UserFlagAssignment)Event.Entity; + + switch (Event.EventType) + { + case RepositoryMonitorEventType.Added: + if (!userFlagAssignemnt.RemovedDate.HasValue) + AddMember(userFlagAssignemnt.UserId); + break; + case RepositoryMonitorEventType.Modified: + if (userFlagAssignemnt.RemovedDate.HasValue) + RemoveMember(userFlagAssignemnt.UserId); + else + AddMember(userFlagAssignemnt.UserId); + break; + case RepositoryMonitorEventType.Deleted: + string userId = userFlagAssignemnt.UserId; + // Remove the user if no other (non-removed) assignments exist. + RemoveMember(userId, (database) => + { + if (database.UserFlagAssignments.Any(a => a.UserFlagId == UserFlagId && a.UserId == userId && !a.RemovedDate.HasValue)) + return null; + else + return new string[] { userId }; + }); + break; + } + } + + public override void Dispose() + { + if (repositorySubscription != null) + repositorySubscription.Dispose(); + } + } +} diff --git a/Disco.Services/Users/UserFlags/UserFlagsBulkAssignTask.cs b/Disco.Services/Users/UserFlags/UserFlagsBulkAssignTask.cs index 8c39534f..42a73735 100644 --- a/Disco.Services/Users/UserFlags/UserFlagsBulkAssignTask.cs +++ b/Disco.Services/Users/UserFlags/UserFlagsBulkAssignTask.cs @@ -41,7 +41,7 @@ namespace Disco.Services.Users.UserFlags // Parse Users var userIds = UserIds - .Select(u => u.Contains('\\') ? u : string.Concat(ActiveDirectory.Context.PrimaryDomain.NetBiosName, @"\", u)) + .Select(u => ActiveDirectory.ParseDomainAccountId(u)) .Distinct(StringComparer.OrdinalIgnoreCase).ToList(); Status.UpdateStatus(10, "Loading users from the database"); diff --git a/Disco.Web/App_Code/CommonHelpers.cshtml b/Disco.Web/App_Code/CommonHelpers.cshtml index 0fcbe659..50573b85 100644 --- a/Disco.Web/App_Code/CommonHelpers.cshtml +++ b/Disco.Web/App_Code/CommonHelpers.cshtml @@ -55,7 +55,7 @@ { if (UserId != null) { - @prepend @Disco.Services.UserExtensions.FriendlyUserId(UserId) + @prepend @Disco.Services.Interop.ActiveDirectory.ActiveDirectory.FriendlyAccountId(UserId) } else { diff --git a/Disco.Web/App_Code/CommonHelpers.generated.cs b/Disco.Web/App_Code/CommonHelpers.generated.cs index b237223c..97db9500 100644 --- a/Disco.Web/App_Code/CommonHelpers.generated.cs +++ b/Disco.Web/App_Code/CommonHelpers.generated.cs @@ -674,7 +674,7 @@ WriteLiteralTo(@__razor_helper_writer, " "); #line 58 "..\..\App_Code\CommonHelpers.cshtml" -WriteTo(@__razor_helper_writer, Disco.Services.UserExtensions.FriendlyUserId(UserId)); +WriteTo(@__razor_helper_writer, Disco.Services.Interop.ActiveDirectory.ActiveDirectory.FriendlyAccountId(UserId)); #line default #line hidden diff --git a/Disco.Web/App_Start/AppConfig.cs b/Disco.Web/App_Start/AppConfig.cs index 7689a1a3..126c274a 100644 --- a/Disco.Web/App_Start/AppConfig.cs +++ b/Disco.Web/App_Start/AppConfig.cs @@ -59,6 +59,10 @@ namespace Disco.Web // Initialize User Flags Disco.Services.Users.UserFlags.UserFlagService.Initialize(Database); + // Initialize Satellite Managed Groups (which don't belong to any other component) + Disco.Services.Devices.ManagedGroups.DeviceManagedGroups.Initialize(Database); + Disco.BI.DocumentTemplateBI.ManagedGroups.DocumentTemplateManagedGroups.Initialize(Database); + // Initialize Plugins Disco.Services.Plugins.Plugins.InitalizePlugins(Database); diff --git a/Disco.Web/Areas/API/Controllers/DeviceBatchController.cs b/Disco.Web/Areas/API/Controllers/DeviceBatchController.cs index 54739c63..cc051b60 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceBatchController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceBatchController.cs @@ -1,6 +1,9 @@ using Disco.BI.Extensions; using Disco.Models.Repository; using Disco.Services.Authorization; +using Disco.Services.Devices.ManagedGroups; +using Disco.Services.Interop.ActiveDirectory; +using Disco.Services.Tasks; using Disco.Services.Web; using Disco.Web.Extensions; using System; @@ -27,6 +30,8 @@ namespace Disco.Web.Areas.API.Controllers const string pInsuredUntil = "insureduntil"; const string pInsuranceDetails = "insurancedetails"; const string pComments = "comments"; + const string pDevicesLinkedGroup = "deviceslinkedgroup"; + const string pAssignedUsersLinkedGroup = "assigneduserslinkedgroup"; [DiscoAuthorize(Claims.Config.DeviceBatch.Configure)] public virtual ActionResult Update(int id, string key, string value = null, bool redirect = false) @@ -86,6 +91,12 @@ namespace Disco.Web.Areas.API.Controllers case pComments: UpdateComments(deviceBatch, value); break; + case pDevicesLinkedGroup: + UpdateDevicesLinkedGroup(deviceBatch, value); + break; + case pAssignedUsersLinkedGroup: + UpdateAssignedUsersLinkedGroup(deviceBatch, value); + break; default: throw new Exception("Invalid Update Key"); } @@ -193,6 +204,71 @@ namespace Disco.Web.Areas.API.Controllers { return Update(id, pComments, Comments, redirect); } + + [DiscoAuthorize(Claims.Config.DeviceBatch.Configure)] + public virtual ActionResult UpdateDevicesLinkedGroup(int id, string GroupId = null, bool redirect = false) + { + try + { + if (id < 0) + throw new ArgumentOutOfRangeException("id"); + + var deviceBatch = Database.DeviceBatches.Find(id); + if (deviceBatch == null) + throw new ArgumentException("Invalid Device Batch Id", "id"); + + var syncTaskStatus = UpdateDevicesLinkedGroup(deviceBatch, GroupId); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DeviceBatch.Index(deviceBatch.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } + [DiscoAuthorize(Claims.Config.DeviceBatch.Configure)] + public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string GroupId = null, bool redirect = false) + { + try + { + if (id < 0) + throw new ArgumentOutOfRangeException("id"); + + var deviceBatch = Database.DeviceBatches.Find(id); + if (deviceBatch == null) + throw new ArgumentException("Invalid Device Batch Id", "id"); + + var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceBatch, GroupId); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DeviceBatch.Index(deviceBatch.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } #endregion #region Update Properties @@ -397,6 +473,40 @@ namespace Disco.Web.Areas.API.Controllers deviceBatch.Comments = Comments; Database.SaveChanges(); } + + private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceBatch DeviceBatch, string DevicesLinkedGroup) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceBatchDevicesManagedGroup.GetKey(DeviceBatch), DevicesLinkedGroup, null); + + if (DeviceBatch.DevicesLinkedGroup != configJson) + { + DeviceBatch.DevicesLinkedGroup = configJson; + Database.SaveChanges(); + + var managedGroup = DeviceBatchDevicesManagedGroup.Initialize(DeviceBatch); + if (managedGroup != null) // Sync Group + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + + return null; + } + + private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(DeviceBatch DeviceBatch, string AssignedUsersLinkedGroup) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceBatchAssignedUsersManagedGroup.GetKey(DeviceBatch), AssignedUsersLinkedGroup, null); + + if (DeviceBatch.AssignedUsersLinkedGroup != configJson) + { + DeviceBatch.AssignedUsersLinkedGroup = configJson; + Database.SaveChanges(); + + var managedGroup = DeviceBatchDevicesManagedGroup.Initialize(DeviceBatch); + if (managedGroup != null) // Sync Group + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + + return null; + } #endregion #region Actions diff --git a/Disco.Web/Areas/API/Controllers/DeviceController.cs b/Disco.Web/Areas/API/Controllers/DeviceController.cs index 5e8d6703..6f760042 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceController.cs @@ -131,15 +131,10 @@ namespace Disco.Web.Areas.API.Controllers } [DiscoAuthorize(Claims.Device.Actions.AssignUser)] - public virtual ActionResult UpdateAssignedUserId(string id, string AssignedUserId = null, string AssignedUserDomain = null, bool redirect = false) + public virtual ActionResult UpdateAssignedUserId(string id, string AssignedUserId = null, bool redirect = false) { - if (AssignedUserId != null && !AssignedUserId.Contains('\\')) - { - if (string.IsNullOrWhiteSpace(AssignedUserDomain)) - AssignedUserId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, AssignedUserId); - else - AssignedUserId = string.Format(@"{0}\{1}", AssignedUserDomain, AssignedUserId); - } + if (!string.IsNullOrWhiteSpace(AssignedUserId)) + AssignedUserId = ActiveDirectory.ParseDomainAccountId(AssignedUserId); return Update(id, pAssignedUserId, AssignedUserId, redirect); } diff --git a/Disco.Web/Areas/API/Controllers/DeviceProfileController.cs b/Disco.Web/Areas/API/Controllers/DeviceProfileController.cs index 8d944466..406167a3 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceProfileController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceProfileController.cs @@ -1,17 +1,17 @@ using Disco.BI.Extensions; using Disco.Models.Repository; using Disco.Services.Authorization; +using Disco.Services.Devices.ManagedGroups; using Disco.Services.Interop.ActiveDirectory; +using Disco.Services.Tasks; using Disco.Services.Web; using System; -using System.Linq; using System.Web.Mvc; namespace Disco.Web.Areas.API.Controllers { public partial class DeviceProfileController : AuthorizedDatabaseController { - const string pDescription = "description"; const string pName = "name"; const string pShortName = "shortname"; @@ -25,6 +25,8 @@ namespace Disco.Web.Areas.API.Controllers const string pProvisionADAccount = "provisionadaccount"; const string pAssignedUserLocalAdmin = "assigneduserlocaladmin"; const string pAllowUntrustedReimageJobEnrolment = "allowuntrustedreimagejobrnrolment"; + const string pDevicesLinkedGroup = "deviceslinkedgroup"; + const string pAssignedUsersLinkedGroup = "assigneduserslinkedgroup"; [DiscoAuthorize(Claims.Config.DeviceProfile.Configure)] public virtual ActionResult Update(int id, string key, string value = null, Nullable redirect = null) @@ -82,6 +84,12 @@ namespace Disco.Web.Areas.API.Controllers case pAllowUntrustedReimageJobEnrolment: UpdateAllowUntrustedReimageJobEnrolment(deviceProfile, value); break; + case pDevicesLinkedGroup: + UpdateDevicesLinkedGroup(deviceProfile, value); + break; + case pAssignedUsersLinkedGroup: + UpdateAssignedUsersLinkedGroup(deviceProfile, value); + break; default: throw new Exception("Invalid Update Key"); } @@ -183,6 +191,71 @@ namespace Disco.Web.Areas.API.Controllers { return Update(id, pAllowUntrustedReimageJobEnrolment, AllowUntrustedReimageJobEnrolment, redirect); } + + [DiscoAuthorize(Claims.Config.DeviceProfile.Configure)] + public virtual ActionResult UpdateDevicesLinkedGroup(int id, string GroupId = null, bool redirect = false) + { + try + { + if (id < 0) + throw new ArgumentOutOfRangeException("id"); + + var deviceProfile = Database.DeviceProfiles.Find(id); + if (deviceProfile == null) + throw new ArgumentException("Invalid Device Profile Id", "id"); + + var syncTaskStatus = UpdateDevicesLinkedGroup(deviceProfile, GroupId); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DeviceProfile.Index(deviceProfile.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } + [DiscoAuthorize(Claims.Config.DeviceProfile.Configure)] + public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string GroupId = null, bool redirect = false) + { + try + { + if (id < 0) + throw new ArgumentOutOfRangeException("id"); + + var deviceProfile = Database.DeviceProfiles.Find(id); + if (deviceProfile == null) + throw new ArgumentException("Invalid Device Profile Id", "id"); + + var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceProfile, GroupId); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DeviceProfile.Index(deviceProfile.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } #endregion #region Update Properties @@ -365,6 +438,40 @@ namespace Disco.Web.Areas.API.Controllers } throw new Exception("Invalid Boolean Value"); } + + private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceProfile DeviceProfile, string DevicesLinkedGroup) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceProfileDevicesManagedGroup.GetKey(DeviceProfile), DevicesLinkedGroup, null); + + if (DeviceProfile.DevicesLinkedGroup != configJson) + { + DeviceProfile.DevicesLinkedGroup = configJson; + Database.SaveChanges(); + + var managedGroup = DeviceProfileDevicesManagedGroup.Initialize(DeviceProfile); + if (managedGroup != null) // Sync Group + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + + return null; + } + + private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(DeviceProfile DeviceProfile, string AssignedUsersLinkedGroup) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceProfileAssignedUsersManagedGroup.GetKey(DeviceProfile), AssignedUsersLinkedGroup, null); + + if (DeviceProfile.AssignedUsersLinkedGroup != configJson) + { + DeviceProfile.AssignedUsersLinkedGroup = configJson; + Database.SaveChanges(); + + var managedGroup = DeviceProfileAssignedUsersManagedGroup.Initialize(DeviceProfile); + if (managedGroup != null) // Sync Group + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + + return null; + } #endregion #region Actions diff --git a/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs b/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs index e7a0a430..b92e8d5c 100644 --- a/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs +++ b/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs @@ -1,8 +1,10 @@ using Disco.BI; +using Disco.BI.DocumentTemplateBI.ManagedGroups; using Disco.BI.Extensions; using Disco.Models.Repository; using Disco.Services.Authorization; using Disco.Services.Interop.ActiveDirectory; +using Disco.Services.Tasks; using Disco.Services.Users; using Disco.Services.Web; using System; @@ -30,7 +32,10 @@ namespace Disco.Web.Areas.API.Controllers throw new ArgumentNullException("id"); if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); + + ScheduledTaskStatus resultTask = null; var documentTemplate = Database.DocumentTemplates.Find(id); + if (documentTemplate != null) { switch (key.ToLower()) @@ -39,7 +44,7 @@ namespace Disco.Web.Areas.API.Controllers UpdateDescription(documentTemplate, value); break; case pScope: - UpdateScope(documentTemplate, value); + resultTask = UpdateScope(documentTemplate, value); break; case pFilterExpression: Authorization.Require(Claims.Config.DocumentTemplate.ConfigureFilterExpression); @@ -57,7 +62,15 @@ namespace Disco.Web.Areas.API.Controllers throw new Exception("Invalid Document Template Id"); } if (redirect) - return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); + if (resultTask == null) + { + return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); + } + else + { + resultTask.SetFinishedUrl(Url.Action(MVC.Config.DocumentTemplate.Index(documentTemplate.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(resultTask.SessionId)); + } else return Json("OK", JsonRequestBehavior.AllowGet); } @@ -163,6 +176,72 @@ namespace Disco.Web.Areas.API.Controllers } } + + [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] + public virtual ActionResult UpdateDevicesLinkedGroup(string id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false) + { + try + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException("id"); + + var documentTemplate = Database.DocumentTemplates.Find(id); + if (documentTemplate == null) + throw new ArgumentException("Invalid Document Template Id", "id"); + + var syncTaskStatus = UpdateDevicesLinkedGroup(documentTemplate, GroupId, FilterBeginDate); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DocumentTemplate.Index(documentTemplate.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } + + [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] + public virtual ActionResult UpdateUsersLinkedGroup(string id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false) + { + try + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException("id"); + + var documentTemplate = Database.DocumentTemplates.Find(id); + if (documentTemplate == null) + throw new ArgumentException("Invalid Document Template Id", "id"); + + var syncTaskStatus = UpdateUsersLinkedGroup(documentTemplate, GroupId, FilterBeginDate); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DocumentTemplate.Index(documentTemplate.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } #endregion #region Update Properties @@ -176,28 +255,38 @@ namespace Disco.Web.Areas.API.Controllers } throw new Exception("Invalid Description"); } - private void UpdateScope(Disco.Models.Repository.DocumentTemplate documentTemplate, string Scope) + private ScheduledTaskStatus UpdateScope(Disco.Models.Repository.DocumentTemplate documentTemplate, string Scope) { - if (!string.IsNullOrWhiteSpace(Scope)) + if (string.IsNullOrWhiteSpace(Scope) || !Disco.Models.Repository.DocumentTemplate.DocumentTemplateScopes.ToList().Contains(Scope)) + throw new ArgumentException("Invalid Scope", "Scope"); + + Database.Configuration.LazyLoadingEnabled = true; + + if (documentTemplate.Scope != Scope) { - if (Disco.Models.Repository.DocumentTemplate.DocumentTemplateScopes.ToList().Contains(Scope)) + + documentTemplate.Scope = Scope; + + if (documentTemplate.Scope != Disco.Models.Repository.DocumentTemplate.DocumentTemplateScopes.Job && + documentTemplate.JobSubTypes != null) { - Database.Configuration.LazyLoadingEnabled = true; - - documentTemplate.Scope = Scope; - - if (documentTemplate.Scope != Disco.Models.Repository.DocumentTemplate.DocumentTemplateScopes.Job && - documentTemplate.JobSubTypes != null) - { - foreach (var st in documentTemplate.JobSubTypes.ToArray()) - documentTemplate.JobSubTypes.Remove(st); - } - - Database.SaveChanges(); - return; + foreach (var st in documentTemplate.JobSubTypes.ToArray()) + documentTemplate.JobSubTypes.Remove(st); } + + Database.SaveChanges(); + + // Trigger Managed Group Sync + var managedGroups = new ADManagedGroup[] { + DocumentTemplateDevicesManagedGroup.Initialize(documentTemplate), + DocumentTemplateUsersManagedGroup.Initialize(documentTemplate) + }; + + if (managedGroups.Any(mg => mg != null)) // Sync Group + return ADManagedGroupsSyncTask.ScheduleSync(managedGroups.Where(mg => mg != null)); } - throw new Exception("Invalid Scope"); + + return null; } private void UpdateFilterExpression(Disco.Models.Repository.DocumentTemplate documentTemplate, string FilterExpression) { @@ -257,6 +346,40 @@ namespace Disco.Web.Areas.API.Controllers } Database.SaveChanges(); } + + private ScheduledTaskStatus UpdateDevicesLinkedGroup(DocumentTemplate DocumentTemplate, string DevicesLinkedGroup, DateTime? FilterBeginDate) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateDevicesManagedGroup.GetKey(DocumentTemplate), DevicesLinkedGroup, FilterBeginDate); + + if (DocumentTemplate.DevicesLinkedGroup != configJson) + { + DocumentTemplate.DevicesLinkedGroup = configJson; + Database.SaveChanges(); + + var managedGroup = DocumentTemplateDevicesManagedGroup.Initialize(DocumentTemplate); + if (managedGroup != null) // Sync Group + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + + return null; + } + + private ScheduledTaskStatus UpdateUsersLinkedGroup(DocumentTemplate DocumentTemplate, string UsersLinkedGroup, DateTime? FilterBeginDate) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateUsersManagedGroup.GetKey(DocumentTemplate), UsersLinkedGroup, FilterBeginDate); + + if (DocumentTemplate.UsersLinkedGroup != configJson) + { + DocumentTemplate.UsersLinkedGroup = configJson; + Database.SaveChanges(); + + var managedGroup = DocumentTemplateUsersManagedGroup.Initialize(DocumentTemplate); + if (managedGroup != null) // Sync Group + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + + return null; + } #endregion #region Actions @@ -340,7 +463,7 @@ namespace Disco.Web.Areas.API.Controllers if (results != null) return Json(results, JsonRequestBehavior.AllowGet); } - + } return Json(null, JsonRequestBehavior.AllowGet); } diff --git a/Disco.Web/Areas/API/Controllers/SystemController.cs b/Disco.Web/Areas/API/Controllers/SystemController.cs index 26216cbc..8e9eecbe 100644 --- a/Disco.Web/Areas/API/Controllers/SystemController.cs +++ b/Disco.Web/Areas/API/Controllers/SystemController.cs @@ -19,7 +19,7 @@ namespace Disco.Web.Areas.API.Controllers [DiscoAuthorize(Claims.Config.System.Show)] public virtual ActionResult UpdateLastNetworkLogonDates() { - var taskStatus = Disco.Services.Interop.ActiveDirectory.ADTaskUpdateNetworkLogonDates.ScheduleImmediately(); + var taskStatus = Disco.Services.Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.ScheduleImmediately(); return RedirectToAction(MVC.Config.Logging.TaskStatus(taskStatus.SessionId)); } @@ -294,6 +294,17 @@ namespace Disco.Web.Areas.API.Controllers return Json(results, JsonRequestBehavior.AllowGet); } + [DiscoAuthorizeAny(Claims.Config.UserFlag.Configure)] + public virtual ActionResult SearchGroupSubjects(string term) + { + var groupResults = ActiveDirectory.SearchADGroups(term).Cast(); + + var results = groupResults.OrderBy(r => r.SamAccountName) + .Select(r => Models.Shared.SubjectDescriptorModel.FromActiveDirectoryObject(r)).ToList(); + + return Json(results, JsonRequestBehavior.AllowGet); + } + [DiscoAuthorizeAny(Claims.DiscoAdminAccount, Claims.Config.JobQueue.Configure)] public virtual ActionResult Subject(string Id) { @@ -305,6 +316,22 @@ namespace Disco.Web.Areas.API.Controllers return Json(Models.Shared.SubjectDescriptorModel.FromActiveDirectoryObject(subject), JsonRequestBehavior.AllowGet); } + [DiscoAuthorizeAny(Claims.Config.UserFlag.Configure)] + public virtual ActionResult SyncActiveDirectoryManagedGroup(string id, string redirectUrl = null) + { + ADManagedGroup managedGroup; + + if (!ActiveDirectory.Context.ManagedGroups.TryGetValue(id, out managedGroup)) + throw new ArgumentException("Unknown Managed Group Key"); + + var taskStatus = ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + + if (redirectUrl != null) + taskStatus.SetFinishedUrl(redirectUrl); + + return RedirectToAction(MVC.Config.Logging.TaskStatus(taskStatus.SessionId)); + } + #endregion #region Proxy Settings diff --git a/Disco.Web/Areas/API/Controllers/UserController.cs b/Disco.Web/Areas/API/Controllers/UserController.cs index da260c3b..c06d0115 100644 --- a/Disco.Web/Areas/API/Controllers/UserController.cs +++ b/Disco.Web/Areas/API/Controllers/UserController.cs @@ -58,10 +58,7 @@ namespace Disco.Web.Areas.API.Controllers [DiscoAuthorize(Claims.User.Actions.AddAttachments)] public virtual ActionResult AttachmentUpload(string id, string Domain, string Comments) { - if (string.IsNullOrEmpty(Domain)) - id = ActiveDirectory.Context.PrimaryDomain.NetBiosName + @"\" + id; - else - id = Domain + @"\" + id; + id = ActiveDirectory.ParseDomainAccountId(id, Domain); var u = Database.Users.Find(id); if (u != null) @@ -120,10 +117,7 @@ namespace Disco.Web.Areas.API.Controllers [DiscoAuthorize(Claims.User.ShowAttachments)] public virtual ActionResult Attachments(string id, string Domain) { - if (string.IsNullOrEmpty(Domain)) - id = ActiveDirectory.Context.PrimaryDomain.NetBiosName + @"\" + id; - else - id = Domain + @"\" + id; + id = ActiveDirectory.ParseDomainAccountId(id, Domain); var u = Database.Users.Include("UserAttachments.DocumentTemplate").Include("UserAttachments.TechUser").Where(m => m.UserId == id).FirstOrDefault(); if (u != null) @@ -167,10 +161,7 @@ namespace Disco.Web.Areas.API.Controllers if (string.IsNullOrEmpty(DocumentTemplateId)) throw new ArgumentNullException("AttachmentTypeId"); - if (string.IsNullOrEmpty(Domain)) - id = ActiveDirectory.Context.PrimaryDomain.NetBiosName + @"\" + id; - else - id = Domain + @"\" + id; + id = ActiveDirectory.ParseDomainAccountId(id, Domain); var user = Database.Users.Find(id); if (user != null) diff --git a/Disco.Web/Areas/API/Controllers/UserFlagController.cs b/Disco.Web/Areas/API/Controllers/UserFlagController.cs index 7ad00b63..5807d86f 100644 --- a/Disco.Web/Areas/API/Controllers/UserFlagController.cs +++ b/Disco.Web/Areas/API/Controllers/UserFlagController.cs @@ -1,5 +1,7 @@ using Disco.Models.Repository; +using Disco.Models.Services.Interop.ActiveDirectory; using Disco.Services.Authorization; +using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Tasks; using Disco.Services.Users.UserFlags; using Disco.Services.Web; @@ -15,6 +17,8 @@ namespace Disco.Web.Areas.API.Controllers const string pDescription = "description"; const string pIcon = "icon"; const string pIconColour = "iconcolour"; + const string pAssignedUsersLinkedGroup = "assigneduserslinkedgroup"; + const string pAssignedUserDevicesLinkedGroup = "assigneduserdeviceslinkedgroup"; [DiscoAuthorize(Claims.Config.UserFlag.Configure)] public virtual ActionResult Update(int id, string key, string value = null, Nullable redirect = null) @@ -44,6 +48,12 @@ namespace Disco.Web.Areas.API.Controllers case pIconColour: UpdateIconColour(flag, value); break; + case pAssignedUsersLinkedGroup: + UpdateAssignedUsersLinkedGroup(flag, value); + break; + case pAssignedUserDevicesLinkedGroup: + UpdateAssignedUserDevicesLinkedGroup(flag, value); + break; default: throw new Exception("Invalid Update Key"); } @@ -106,7 +116,7 @@ namespace Disco.Web.Areas.API.Controllers } else { - return Json("Invalid User Flag Id", JsonRequestBehavior.AllowGet); + throw new ArgumentException("Invalid User Flag Id", "id"); } if (redirect) return RedirectToAction(MVC.Config.UserFlag.Index(UserFlag.Id)); @@ -121,6 +131,72 @@ namespace Disco.Web.Areas.API.Controllers return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); } } + [DiscoAuthorize(Claims.Config.UserFlag.Configure)] + public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string GroupId = null, bool redirect = false) + { + try + { + if (id < 0) + throw new ArgumentOutOfRangeException("id"); + + var UserFlag = Database.UserFlags.Find(id); + if (UserFlag == null) + throw new ArgumentException("Invalid User Flag Id", "id"); + + + var syncTaskStatus = UpdateAssignedUsersLinkedGroup(UserFlag, GroupId); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.UserFlag.Index(UserFlag.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.UserFlag.Index(UserFlag.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } + [DiscoAuthorize(Claims.Config.UserFlag.Configure)] + public virtual ActionResult UpdateAssignedUserDevicesLinkedGroup(int id, string GroupId = null, bool redirect = false) + { + try + { + if (id < 0) + throw new ArgumentOutOfRangeException("id"); + + var UserFlag = Database.UserFlags.Find(id); + if (UserFlag == null) + throw new ArgumentException("Invalid User Flag Id", "id"); + + + var syncTaskStatus = UpdateAssignedUserDevicesLinkedGroup(UserFlag, GroupId); + if (redirect) + if (syncTaskStatus == null) + return RedirectToAction(MVC.Config.UserFlag.Index(UserFlag.Id)); + else + { + syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.UserFlag.Index(UserFlag.Id))); + return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); + } + else + return Json("OK", JsonRequestBehavior.AllowGet); + } + catch (Exception ex) + { + if (redirect) + throw; + else + return Json(string.Format("Error: {0}", ex.Message), JsonRequestBehavior.AllowGet); + } + } #endregion #region Update Properties @@ -131,37 +207,98 @@ namespace Disco.Web.Areas.API.Controllers if (string.IsNullOrWhiteSpace(IconColour)) throw new ArgumentNullException("IconColour"); - UserFlag.Icon = Icon; - UserFlag.IconColour = IconColour; - UserFlagService.Update(Database, UserFlag); + if (UserFlag.Icon != Icon || + UserFlag.IconColour != IconColour) + { + UserFlag.Icon = Icon; + UserFlag.IconColour = IconColour; + UserFlagService.Update(Database, UserFlag); + } } private void UpdateIcon(UserFlag UserFlag, string Icon) { if (string.IsNullOrWhiteSpace(Icon)) throw new ArgumentNullException("Icon"); - UserFlag.Icon = Icon; - UserFlagService.Update(Database, UserFlag); + if (UserFlag.Icon != Icon) + { + UserFlag.Icon = Icon; + UserFlagService.Update(Database, UserFlag); + } } private void UpdateIconColour(UserFlag UserFlag, string IconColour) { if (string.IsNullOrWhiteSpace(IconColour)) throw new ArgumentNullException("IconColour"); - UserFlag.IconColour = IconColour; - UserFlagService.Update(Database, UserFlag); + if (UserFlag.IconColour != IconColour) + { + UserFlag.IconColour = IconColour; + UserFlagService.Update(Database, UserFlag); + } } private void UpdateName(UserFlag UserFlag, string Name) { - UserFlag.Name = Name; - UserFlagService.Update(Database, UserFlag); + if (UserFlag.Name != Name) + { + UserFlag.Name = Name; + UserFlagService.Update(Database, UserFlag); + } } private void UpdateDescription(UserFlag UserFlag, string Description) { - UserFlag.Description = Description; - UserFlagService.Update(Database, UserFlag); + if (UserFlag.Description != Description) + { + UserFlag.Description = Description; + UserFlagService.Update(Database, UserFlag); + } + } + + private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(UserFlag UserFlag, string AssignedUsersLinkedGroup) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(UserFlagUsersManagedGroup.GetKey(UserFlag), AssignedUsersLinkedGroup, null); + + if (UserFlag.UsersLinkedGroup != configJson) + { + UserFlag.UsersLinkedGroup = configJson; + UserFlagService.Update(Database, UserFlag); + + if (UserFlag.UsersLinkedGroup != null) + { + // Sync Group + UserFlagUsersManagedGroup managedGroup; + if (UserFlagUsersManagedGroup.TryGetManagedGroup(UserFlag, out managedGroup)) + { + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + } + } + + return null; + } + private ScheduledTaskStatus UpdateAssignedUserDevicesLinkedGroup(UserFlag UserFlag, string AssignedUserDevicesLinkedGroup) + { + var configJson = ADManagedGroup.ValidConfigurationToJson(UserFlagUserDevicesManagedGroup.GetKey(UserFlag), AssignedUserDevicesLinkedGroup, null); + + if (UserFlag.UserDevicesLinkedGroup != configJson) + { + UserFlag.UserDevicesLinkedGroup = configJson; + UserFlagService.Update(Database, UserFlag); + + if (UserFlag.UserDevicesLinkedGroup != null) + { + // Sync Group + UserFlagUserDevicesManagedGroup managedGroup; + if (UserFlagUserDevicesManagedGroup.TryGetManagedGroup(UserFlag, out managedGroup)) + { + return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); + } + } + } + + return null; } #endregion @@ -218,7 +355,7 @@ namespace Disco.Web.Areas.API.Controllers throw new ArgumentException("Invalid User Flag Id", "id"); var assignedUsers = Database.UserFlagAssignments.Where(a => a.UserFlagId == userFlag.Id && !a.RemovedDate.HasValue).OrderBy(a => a.UserId).Select(a => a.UserId).ToList(); - + return Json(assignedUsers, JsonRequestBehavior.AllowGet); } #endregion diff --git a/Disco.Web/Areas/Config/Controllers/DeviceBatchController.cs b/Disco.Web/Areas/Config/Controllers/DeviceBatchController.cs index be765620..71f28658 100644 --- a/Disco.Web/Areas/Config/Controllers/DeviceBatchController.cs +++ b/Disco.Web/Areas/Config/Controllers/DeviceBatchController.cs @@ -1,6 +1,7 @@ using Disco.BI.Extensions; using Disco.Models.UI.Config.DeviceBatch; using Disco.Services.Authorization; +using Disco.Services.Devices.ManagedGroups; using Disco.Services.Plugins.Features.UIExtension; using Disco.Services.Web; using System; @@ -36,6 +37,13 @@ namespace Disco.Web.Areas.Config.Controllers DeviceDecommissionedCount = dG.Count(d => d.DecommissionedDate.HasValue) }).ToArray().Cast().ToList(); + DeviceBatchAssignedUsersManagedGroup assignedUsersManagedGroup; + if (DeviceBatchAssignedUsersManagedGroup.TryGetManagedGroup(m.DeviceBatch, out assignedUsersManagedGroup)) + m.AssignedUsersLinkedGroup = assignedUsersManagedGroup; + DeviceBatchDevicesManagedGroup devicesManagedGroup; + if (DeviceBatchDevicesManagedGroup.TryGetManagedGroup(m.DeviceBatch, out devicesManagedGroup)) + m.DevicesLinkedGroup = devicesManagedGroup; + if (Authorization.Has(Claims.Config.DeviceBatch.Delete)) m.CanDelete = m.DeviceBatch.CanDelete(Database); diff --git a/Disco.Web/Areas/Config/Controllers/DeviceProfileController.cs b/Disco.Web/Areas/Config/Controllers/DeviceProfileController.cs index 6eec7f7d..e908f28f 100644 --- a/Disco.Web/Areas/Config/Controllers/DeviceProfileController.cs +++ b/Disco.Web/Areas/Config/Controllers/DeviceProfileController.cs @@ -2,6 +2,7 @@ using Disco.Models.Repository; using Disco.Models.UI.Config.DeviceProfile; using Disco.Services.Authorization; +using Disco.Services.Devices.ManagedGroups; using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Plugins; using Disco.Services.Plugins.Features.CertificateProvider; @@ -36,6 +37,13 @@ namespace Disco.Web.Areas.Config.Controllers if (m.DeviceProfile.DefaultOrganisationAddress.HasValue) m.DefaultOrganisationAddress = Database.DiscoConfiguration.OrganisationAddresses.GetAddress(m.DeviceProfile.DefaultOrganisationAddress.Value); + DeviceProfileAssignedUsersManagedGroup assignedUsersManagedGroup; + if (DeviceProfileAssignedUsersManagedGroup.TryGetManagedGroup(m.DeviceProfile, out assignedUsersManagedGroup)) + m.AssignedUsersLinkedGroup = assignedUsersManagedGroup; + DeviceProfileDevicesManagedGroup devicesManagedGroup; + if (DeviceProfileDevicesManagedGroup.TryGetManagedGroup(m.DeviceProfile, out devicesManagedGroup)) + m.DevicesLinkedGroup = devicesManagedGroup; + m.CertificateProviders = Plugins.GetPluginFeatures(typeof(CertificateProviderFeature)); var DistributionValues = Enum.GetValues(typeof(Disco.Models.Repository.DeviceProfile.DistributionTypes)); diff --git a/Disco.Web/Areas/Config/Controllers/DocumentTemplateController.cs b/Disco.Web/Areas/Config/Controllers/DocumentTemplateController.cs index 9e360e97..5853b0c8 100644 --- a/Disco.Web/Areas/Config/Controllers/DocumentTemplateController.cs +++ b/Disco.Web/Areas/Config/Controllers/DocumentTemplateController.cs @@ -1,4 +1,5 @@ -using Disco.BI.Extensions; +using Disco.BI.DocumentTemplateBI.ManagedGroups; +using Disco.BI.Extensions; using Disco.Models.UI.Config.DocumentTemplate; using Disco.Services.Authorization; using Disco.Services.Plugins.Features.UIExtension; @@ -33,6 +34,13 @@ namespace Disco.Web.Areas.Config.Controllers m.TemplateExpressions = m.DocumentTemplate.ExtractPdfExpressions(Database); m.UpdateModel(Database); + DocumentTemplateDevicesManagedGroup devicesManagedGroup; + if (DocumentTemplateDevicesManagedGroup.TryGetManagedGroup(m.DocumentTemplate, out devicesManagedGroup)) + m.DevicesLinkedGroup = devicesManagedGroup; + DocumentTemplateUsersManagedGroup usersManagedGroup; + if (DocumentTemplateUsersManagedGroup.TryGetManagedGroup(m.DocumentTemplate, out usersManagedGroup)) + m.UsersLinkedGroup = usersManagedGroup; + // UI Extensions UIExtensions.ExecuteExtensions(this.ControllerContext, m); diff --git a/Disco.Web/Areas/Config/Controllers/UserFlagController.cs b/Disco.Web/Areas/Config/Controllers/UserFlagController.cs index 6e3bbc34..9101299c 100644 --- a/Disco.Web/Areas/Config/Controllers/UserFlagController.cs +++ b/Disco.Web/Areas/Config/Controllers/UserFlagController.cs @@ -30,6 +30,13 @@ namespace Disco.Web.Areas.Config.Controllers if (m == null) throw new ArgumentException("Invalid User Flag Id"); + UserFlagUsersManagedGroup assignedUsersManagedGroup; + if (UserFlagUsersManagedGroup.TryGetManagedGroup(m.UserFlag, out assignedUsersManagedGroup)) + m.UsersLinkedGroup = assignedUsersManagedGroup; + UserFlagUserDevicesManagedGroup assignedUserDevicesManagedGroup; + if (UserFlagUserDevicesManagedGroup.TryGetManagedGroup(m.UserFlag, out assignedUserDevicesManagedGroup)) + m.UserDevicesLinkedGroup = assignedUserDevicesManagedGroup; + if (Authorization.Has(Claims.Config.UserFlag.Configure)) { m.Icons = UIHelpers.Icons; diff --git a/Disco.Web/Areas/Config/Models/DeviceBatch/ShowModel.cs b/Disco.Web/Areas/Config/Models/DeviceBatch/ShowModel.cs index 13db03f2..c9d9eb98 100644 --- a/Disco.Web/Areas/Config/Models/DeviceBatch/ShowModel.cs +++ b/Disco.Web/Areas/Config/Models/DeviceBatch/ShowModel.cs @@ -1,18 +1,21 @@ using Disco.Models.UI.Config.DeviceBatch; -using System; +using Disco.Services.Devices.ManagedGroups; using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; namespace Disco.Web.Areas.Config.Models.DeviceBatch { public class ShowModel : ConfigDeviceBatchShowModel { public Disco.Models.Repository.DeviceBatch DeviceBatch { get; set; } + public Disco.Models.Repository.DeviceModel DefaultDeviceModel { get; set; } + public List DeviceModels { get; set; } public List DeviceModelMembers { get; set; } + + public DeviceBatchAssignedUsersManagedGroup AssignedUsersLinkedGroup { get; set; } + public DeviceBatchDevicesManagedGroup DevicesLinkedGroup { get; set; } + public int DeviceCount { get; set; } public int DeviceDecommissionedCount { get; set; } public bool CanDelete { get; set; } diff --git a/Disco.Web/Areas/Config/Models/DeviceProfile/ShowModel.cs b/Disco.Web/Areas/Config/Models/DeviceProfile/ShowModel.cs index 06a77bfd..b4a48ff3 100644 --- a/Disco.Web/Areas/Config/Models/DeviceProfile/ShowModel.cs +++ b/Disco.Web/Areas/Config/Models/DeviceProfile/ShowModel.cs @@ -1,11 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using Disco.Services.Plugins; -using Disco.Models.UI.Config.DeviceProfile; +using Disco.Models.UI.Config.DeviceProfile; +using Disco.Services.Devices.ManagedGroups; using Disco.Services.Interop.ActiveDirectory; +using Disco.Services.Plugins; +using System.Collections.Generic; +using System.Web.Mvc; namespace Disco.Web.Areas.Config.Models.DeviceProfile { @@ -16,6 +14,9 @@ namespace Disco.Web.Areas.Config.Models.DeviceProfile public Disco.Models.BI.Config.OrganisationAddress DefaultOrganisationAddress { get; set; } public List OrganisationAddresses { get; set; } + public DeviceProfileAssignedUsersManagedGroup AssignedUsersLinkedGroup { get; set; } + public DeviceProfileDevicesManagedGroup DevicesLinkedGroup { get; set; } + public string FriendlyOrganisationalUnitName { get diff --git a/Disco.Web/Areas/Config/Models/DocumentTemplate/ShowModel.cs b/Disco.Web/Areas/Config/Models/DocumentTemplate/ShowModel.cs index f37f5eb1..49aa31b2 100644 --- a/Disco.Web/Areas/Config/Models/DocumentTemplate/ShowModel.cs +++ b/Disco.Web/Areas/Config/Models/DocumentTemplate/ShowModel.cs @@ -5,6 +5,7 @@ using System.Web; using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.UI.Config.DocumentTemplate; +using Disco.BI.DocumentTemplateBI.ManagedGroups; namespace Disco.Web.Areas.Config.Models.DocumentTemplate { @@ -26,6 +27,9 @@ namespace Disco.Web.Areas.Config.Models.DocumentTemplate } } + public DocumentTemplateDevicesManagedGroup DevicesLinkedGroup { get; set; } + public DocumentTemplateUsersManagedGroup UsersLinkedGroup { get; set; } + public void UpdateModel(DiscoDataContext Database) { diff --git a/Disco.Web/Areas/Config/Models/Shared/LinkedGroupModel.cs b/Disco.Web/Areas/Config/Models/Shared/LinkedGroupModel.cs new file mode 100644 index 00000000..d5b04d06 --- /dev/null +++ b/Disco.Web/Areas/Config/Models/Shared/LinkedGroupModel.cs @@ -0,0 +1,16 @@ +using Disco.Services.Interop.ActiveDirectory; + +namespace Disco.Web.Areas.Config.Models.Shared +{ + public class LinkedGroupModel + { + public bool CanConfigure { get; set; } + + public string Description { get; set; } + public string CategoryDescription { get; set; } + + public string UpdateUrl { get; set; } + + public ADManagedGroup ManagedGroup { get; set; } + } +} \ No newline at end of file diff --git a/Disco.Web/Areas/Config/Models/UserFlag/ShowModel.cs b/Disco.Web/Areas/Config/Models/UserFlag/ShowModel.cs index ddf1400b..d2733540 100644 --- a/Disco.Web/Areas/Config/Models/UserFlag/ShowModel.cs +++ b/Disco.Web/Areas/Config/Models/UserFlag/ShowModel.cs @@ -1,4 +1,5 @@ using Disco.Models.UI.Config.UserFlag; +using Disco.Services.Users.UserFlags; using System.Collections.Generic; namespace Disco.Web.Areas.Config.Models.UserFlag @@ -10,6 +11,9 @@ namespace Disco.Web.Areas.Config.Models.UserFlag public int CurrentAssignmentCount { get; set; } public int TotalAssignmentCount { get; set; } + public UserFlagUsersManagedGroup UsersLinkedGroup { get; set; } + public UserFlagUserDevicesManagedGroup UserDevicesLinkedGroup { get; set; } + public IEnumerable> Icons { get; set; } public IEnumerable> ThemeColours { get; set; } } diff --git a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml index 066a5c28..47e5e6e2 100644 --- a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml +++ b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml @@ -1,4 +1,6 @@ @model Disco.Web.Areas.Config.Models.DeviceBatch.ShowModel +@using Disco.Services.Devices.ManagedGroups; +@using Disco.Web.Areas.Config.Models.Shared; @{ Authorization.Require(Claims.Config.DeviceBatch.Show); @@ -7,6 +9,10 @@ var canConfig = Authorization.Has(Claims.Config.DeviceBatch.Configure); var canDeviceModelShow = Authorization.Has(Claims.Config.DeviceModel.Show); + var hideAdvanced = + Model.DeviceBatch.AssignedUsersLinkedGroup == null && + Model.DeviceBatch.DevicesLinkedGroup == null; + if (canConfig) { Html.BundleDeferred("~/ClientScripts/Modules/Disco-PropertyChangeHelpers"); @@ -14,7 +20,7 @@ Html.BundleDeferred("~/ClientScripts/Modules/tinymce"); } } -
+
+ @if (hideAdvanced) + { + + + + } + + + +
Id: @@ -652,6 +658,54 @@ }
+ + +
Linked Groups: + +
+ @Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceBatchDevicesManagedGroup.GetCategoryDescription(Model.DeviceBatch), + Description = DeviceBatchDevicesManagedGroup.GetDescription(Model.DeviceBatch), + ManagedGroup = Model.DevicesLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceBatch.UpdateDevicesLinkedGroup(Model.DeviceBatch.Id, redirect: true)) + }) + @Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceBatchAssignedUsersManagedGroup.GetCategoryDescription(Model.DeviceBatch), + Description = DeviceBatchAssignedUsersManagedGroup.GetDescription(Model.DeviceBatch), + ManagedGroup = Model.AssignedUsersLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceBatch.UpdateAssignedUsersLinkedGroup(Model.DeviceBatch.Id, redirect: true)) + }) + @if (canConfig) + { + @Html.Partial(MVC.Config.Shared.Views.LinkedGroupShared) + } +
+
+  Linked Active Directory Groups are automatically synchronized to include members currently associated with this Device Batch. +
+
@@ -670,4 +724,4 @@ @Html.ActionLinkButton(string.Format("View {0} Device{1}", Model.DeviceCount, (Model.DeviceCount != 1 ? "s" : null)), MVC.Search.Query(Model.DeviceBatch.Id.ToString(), "DeviceBatch")) } } -
\ No newline at end of file +
diff --git a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs index 051db861..b8a6d31e 100644 --- a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs +++ b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs @@ -31,8 +31,20 @@ namespace Disco.Web.Areas.Config.Views.DeviceBatch using Disco.Models.Repository; using Disco.Services; using Disco.Services.Authorization; + + #line 2 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + using Disco.Services.Devices.ManagedGroups; + + #line default + #line hidden using Disco.Services.Web; using Disco.Web; + + #line 3 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + using Disco.Web.Areas.Config.Models.Shared; + + #line default + #line hidden using Disco.Web.Extensions; [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] @@ -45,7 +57,7 @@ namespace Disco.Web.Areas.Config.Views.DeviceBatch public override void Execute() { - #line 2 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 4 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Authorization.Require(Claims.Config.DeviceBatch.Show); @@ -54,6 +66,10 @@ namespace Disco.Web.Areas.Config.Views.DeviceBatch var canConfig = Authorization.Has(Claims.Config.DeviceBatch.Configure); var canDeviceModelShow = Authorization.Has(Claims.Config.DeviceModel.Show); + var hideAdvanced = + Model.DeviceBatch.AssignedUsersLinkedGroup == null && + Model.DeviceBatch.DevicesLinkedGroup == null; + if (canConfig) { Html.BundleDeferred("~/ClientScripts/Modules/Disco-PropertyChangeHelpers"); @@ -66,7 +82,17 @@ namespace Disco.Web.Areas.Config.Views.DeviceBatch #line hidden WriteLiteral("\r\n(hideAdvanced ? " Config_HideAdvanced" : null + + #line default + #line hidden +, 988), false) +); WriteLiteral(" style=\"width: 730px\""); @@ -79,7 +105,7 @@ WriteLiteral(">Id:\r\n \r\n \r\n"); WriteLiteral(" "); - #line 23 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 29 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.DisplayFor(model => model.DeviceBatch.Id)); @@ -89,13 +115,13 @@ WriteLiteral("\r\n \r\n \r\n \r\n " \r\n \r\n"); - #line 30 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 36 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 30 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 36 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -103,42 +129,42 @@ WriteLiteral("\r\n \r\n \r\n \r\n #line default #line hidden - #line 32 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 38 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.Name)); #line default #line hidden - #line 32 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 38 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 33 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 39 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 33 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 39 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 34 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 40 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 34 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 40 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -156,7 +182,7 @@ WriteLiteral(@"> '"); - #line 40 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 46 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Url.Action(MVC.API.DeviceBatch.UpdateName(Model.DeviceBatch.Id))); @@ -166,7 +192,7 @@ WriteLiteral("\',\r\n \'BatchName\'\r\n " });\r\n \r\n"); - #line 45 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 51 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -175,14 +201,14 @@ WriteLiteral("\',\r\n \'BatchName\'\r\n #line default #line hidden - #line 48 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 54 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceBatch.Name); #line default #line hidden - #line 48 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 54 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } @@ -193,7 +219,7 @@ WriteLiteral(" \r\n \r\n \r\n "del:\r\n \r\n "); - #line 55 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 61 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -201,42 +227,42 @@ WriteLiteral(" \r\n \r\n \r\n #line default #line hidden - #line 57 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 63 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.DropDownListFor(model => model.DeviceBatch.DefaultDeviceModelId, Model.DeviceModels.ToSelectListItems(null, true))); #line default #line hidden - #line 57 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 63 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 58 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 64 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 58 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 64 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 59 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 65 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 59 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 65 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -252,7 +278,7 @@ WriteLiteral(">\r\n $(function () {\r\n " \'"); - #line 65 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 71 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Url.Action(MVC.API.DeviceBatch.UpdateDefaultDeviceModelId(Model.DeviceBatch.Id))); @@ -262,7 +288,7 @@ WriteLiteral("\',\r\n \'DefaultDeviceModelId\'\r\n ";\r\n });\r\n \r\n"); - #line 70 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 76 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -278,7 +304,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>
"); - #line 74 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 80 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -286,14 +312,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 76 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 82 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DefaultDeviceModel.ToString()); #line default #line hidden - #line 76 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 82 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" ; } } @@ -311,13 +337,13 @@ WriteLiteral(">Devices added offline will default to this Device Model.\r\n " \r\n"); - #line 87 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 93 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 87 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 93 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (Model.DeviceModelMembers.Count > 0) { @@ -340,13 +366,13 @@ WriteLiteral(@"> "); - #line 98 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 104 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 98 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 104 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" foreach (var membership in Model.DeviceModelMembers.OrderByDescending(dmm => dmm.DeviceCount)) { @@ -356,13 +382,13 @@ WriteLiteral(@"> WriteLiteral(" \r\n \r\n"); - #line 102 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 108 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 102 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 108 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canDeviceModelShow) { @@ -370,14 +396,14 @@ WriteLiteral(" \r\n #line default #line hidden - #line 104 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 110 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.ActionLink(membership.DeviceModel.ToString(), MVC.Config.DeviceModel.Index(membership.DeviceModel.Id))); #line default #line hidden - #line 104 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 110 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else @@ -387,14 +413,14 @@ WriteLiteral(" \r\n #line default #line hidden - #line 108 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 114 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(membership.DeviceModel.ToString()); #line default #line hidden - #line 108 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 114 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } @@ -407,7 +433,7 @@ WriteLiteral(" \r\n WriteLiteral(" "); - #line 112 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 118 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(membership.DeviceCount.ToString("n0")); @@ -419,7 +445,7 @@ WriteLiteral("\r\n \r\n WriteLiteral(" "); - #line 115 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 121 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(membership.DeviceDecommissionedCount.ToString("n0")); @@ -429,7 +455,7 @@ WriteLiteral("\r\n \r\n ">\r\n"); - #line 118 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 124 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } @@ -439,7 +465,7 @@ WriteLiteral(" \r\n \r\n Total Models: "); - #line 122 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 128 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceModelMembers.Count.ToString("n0")); @@ -448,7 +474,7 @@ WriteLiteral(" \r\n \r\n "); - #line 123 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 129 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceCount.ToString("n0")); @@ -457,7 +483,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n "); - #line 124 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 130 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceDecommissionedCount.ToString("n0")); @@ -467,7 +493,7 @@ WriteLiteral("\r\n \r\n " \r\n"); - #line 128 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 134 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -482,7 +508,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral(">No device models are referenced in this batch.\r\n"); - #line 132 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 138 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } @@ -491,7 +517,7 @@ WriteLiteral(">No device models are referenced in this batch.\r\n"); WriteLiteral(" "); - #line 133 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 139 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (Model.DeviceBatch.UnitQuantity.HasValue && Model.DeviceBatch.UnitQuantity.Value > Model.DeviceCount) { var missingCount = Model.DeviceBatch.UnitQuantity.Value - Model.DeviceCount; @@ -514,7 +540,7 @@ WriteLiteral(">\r\n"); WriteLiteral(" "); - #line 138 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 144 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceCount.ToString("n0")); @@ -523,7 +549,7 @@ WriteLiteral(" "); WriteLiteral(" of "); - #line 138 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 144 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceBatch.UnitQuantity.Value.ToString("n0")); @@ -532,7 +558,7 @@ WriteLiteral(" of "); WriteLiteral(" purchased devices are managed by Disco. "); - #line 138 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 144 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(missingCount.ToString("n0")); @@ -541,7 +567,7 @@ WriteLiteral(" purchased devices are managed by Disco. "); WriteLiteral(" "); - #line 138 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 144 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(missingCount == 1 ? "is" : "are"); @@ -550,7 +576,7 @@ WriteLiteral(" "); WriteLiteral(" not managed.\r\n \r\n"); - #line 140 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 146 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } @@ -574,13 +600,13 @@ WriteLiteral(" style=\"width: 100px\""); WriteLiteral(">Purchase Date:\r\n \r\n \r\n"); - #line 152 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 158 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 152 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 158 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -588,28 +614,28 @@ WriteLiteral(">Purchase Date:\r\n \r\n #line default #line hidden - #line 154 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 160 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.PurchaseDate)); #line default #line hidden - #line 154 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 160 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 155 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 161 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 155 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 161 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -624,7 +650,7 @@ WriteLiteral(@" \r\n"); - #line 194 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 200 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -756,7 +782,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 198 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 204 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -764,14 +790,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 200 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 206 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceBatch.Supplier); #line default #line hidden - #line 200 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 206 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } @@ -783,7 +809,7 @@ WriteLiteral(" \r\n \r\n " "); - #line 207 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 213 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -791,42 +817,42 @@ WriteLiteral(" \r\n \r\n #line default #line hidden - #line 209 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 215 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.UnitCost)); #line default #line hidden - #line 209 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 215 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 210 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 216 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 210 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 216 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 211 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 217 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 211 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 217 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -844,7 +870,7 @@ WriteLiteral(@"> '"); - #line 217 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 223 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Url.Action(MVC.API.DeviceBatch.UpdateUnitCost(Model.DeviceBatch.Id))); @@ -855,7 +881,7 @@ WriteLiteral("\',\r\n \'UnitCost\'\r\n "\r\n"); - #line 222 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 228 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -871,7 +897,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 226 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 232 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -879,14 +905,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 228 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 234 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceBatch.UnitCost.Value.ToString("C")); #line default #line hidden - #line 228 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 234 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } @@ -898,7 +924,7 @@ WriteLiteral(" \r\n \r\n " "); - #line 235 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 241 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -906,42 +932,42 @@ WriteLiteral(" \r\n \r\n #line default #line hidden - #line 237 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 243 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.UnitQuantity)); #line default #line hidden - #line 237 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 243 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 238 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 244 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 238 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 244 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 239 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 245 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 239 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 245 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -959,7 +985,7 @@ WriteLiteral(@"> '"); - #line 245 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 251 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Url.Action(MVC.API.DeviceBatch.UpdateUnitQuantity(Model.DeviceBatch.Id))); @@ -970,7 +996,7 @@ WriteLiteral("\',\r\n \'UnitQuantity\'\r\ " \r\n"); - #line 250 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 256 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -986,7 +1012,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 254 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 260 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -994,14 +1020,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 256 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 262 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceBatch.UnitQuantity.Value.ToString("n0")); #line default #line hidden - #line 256 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 262 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } @@ -1016,7 +1042,7 @@ WriteLiteral(" id=\"DeviceBatch_PurchaseDetails_Container\""); WriteLiteral(">\r\n
\r\n Details "); - #line 263 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 269 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader("ajaxPurchaseDetails")); @@ -1025,13 +1051,13 @@ WriteLiteral(">\r\n
\r\n Details WriteLiteral("\r\n
\r\n"); - #line 265 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 271 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 265 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 271 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -1039,14 +1065,14 @@ WriteLiteral("\r\n
\r\n"); #line default #line hidden - #line 267 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 273 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.PurchaseDetails)); #line default #line hidden - #line 267 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 273 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -1075,7 +1101,7 @@ WriteLiteral(@"> url: '"); - #line 284 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 290 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Url.Action(MVC.API.DeviceBatch.UpdatePurchaseDetails(Model.DeviceBatch.Id))); @@ -1109,7 +1135,7 @@ WriteLiteral("\',\r\n dataType: \'jso " });\r\n \r\n"); - #line 319 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 325 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1125,7 +1151,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 323 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 329 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1133,14 +1159,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 325 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 331 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(new HtmlString(Model.DeviceBatch.PurchaseDetails)); #line default #line hidden - #line 325 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 331 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } @@ -1165,13 +1191,13 @@ WriteLiteral(" style=\"width: 100px\""); WriteLiteral(">Valid Until:\r\n \r\n \r\n"); - #line 339 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 345 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 339 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 345 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -1179,28 +1205,28 @@ WriteLiteral(">Valid Until:\r\n \r\n #line default #line hidden - #line 341 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 347 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.WarrantyValidUntil)); #line default #line hidden - #line 341 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 347 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 342 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 348 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 342 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 348 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -1215,7 +1241,7 @@ WriteLiteral(@" \r\n"); - #line 422 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 428 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1371,7 +1397,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 426 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 432 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1379,14 +1405,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 428 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 434 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(new HtmlString(Model.DeviceBatch.WarrantyDetails)); #line default #line hidden - #line 428 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 434 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } @@ -1411,7 +1437,7 @@ WriteLiteral(" style=\"width: 100px\""); WriteLiteral(">Supplier:\r\n \r\n "); - #line 441 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 447 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -1419,42 +1445,42 @@ WriteLiteral(">Supplier:\r\n \r\n #line default #line hidden - #line 443 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 449 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.InsuranceSupplier)); #line default #line hidden - #line 443 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 449 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 444 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 450 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 444 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 450 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 445 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 451 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 445 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 451 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -1472,7 +1498,7 @@ WriteLiteral(@"> '"); - #line 451 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 457 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Url.Action(MVC.API.DeviceBatch.UpdateInsuranceSupplier(Model.DeviceBatch.Id))); @@ -1483,7 +1509,7 @@ WriteLiteral("\',\r\n \'InsuranceSupplier " \r\n"); - #line 456 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 462 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1499,7 +1525,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 460 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 466 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1507,14 +1533,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 462 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 468 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Model.DeviceBatch.InsuranceSupplier); #line default #line hidden - #line 462 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 468 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" ; } } @@ -1530,7 +1556,7 @@ WriteLiteral(" class=\"name\""); WriteLiteral(">Insured Date:\r\n \r\n "); - #line 470 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 476 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -1538,28 +1564,28 @@ WriteLiteral(">Insured Date:\r\n \r\n #line default #line hidden - #line 472 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 478 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.InsuredDate)); #line default #line hidden - #line 472 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 478 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 473 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 479 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 473 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 479 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -1574,7 +1600,7 @@ WriteLiteral(@" \r\n"); - #line 578 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 584 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1819,7 +1845,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 582 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 588 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1827,14 +1853,14 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 584 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 590 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(new HtmlString(Model.DeviceBatch.InsuranceDetails)); #line default #line hidden - #line 584 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 590 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } @@ -1847,7 +1873,7 @@ WriteLiteral(" \r\n \r\n \r\n WriteLiteral(" "); - #line 591 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 597 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(AjaxHelpers.AjaxLoader("ajaxComments")); @@ -1856,7 +1882,7 @@ WriteLiteral(" "); WriteLiteral("\r\n \r\n "); - #line 593 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 599 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (canConfig) { @@ -1864,14 +1890,14 @@ WriteLiteral("\r\n \r\n "); #line default #line hidden - #line 595 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 601 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.EditorFor(model => model.DeviceBatch.Comments)); #line default #line hidden - #line 595 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 601 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" @@ -1898,7 +1924,7 @@ WriteLiteral(@"> url: '"); - #line 610 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 616 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Url.Action(MVC.API.DeviceBatch.UpdateComments(Model.DeviceBatch.Id))); @@ -1929,7 +1955,7 @@ WriteLiteral("\',\r\n dataType: \'json\',\r\n "\n"); - #line 645 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 651 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1945,7 +1971,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>"); - #line 649 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 655 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } else { @@ -1953,34 +1979,167 @@ WriteLiteral("><None Specified>"); #line default #line hidden - #line 651 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 657 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(new HtmlString(Model.DeviceBatch.Comments)); #line default #line hidden - #line 651 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 657 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } #line default #line hidden -WriteLiteral(" \r\n \r\n \r\n\r\n\r\n \r\n"); + + + #line 661 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + + + #line default + #line hidden + + #line 661 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + if (hideAdvanced) + { + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n Show Advanced Options + + + +"); + + + #line 677 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n Linked Groups:\r\n \r\n \r\n " + +"
\r\n"); + +WriteLiteral(" "); + + + #line 683 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceBatchDevicesManagedGroup.GetCategoryDescription(Model.DeviceBatch), + Description = DeviceBatchDevicesManagedGroup.GetDescription(Model.DeviceBatch), + ManagedGroup = Model.DevicesLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceBatch.UpdateDevicesLinkedGroup(Model.DeviceBatch.Id, redirect: true)) + })); + + + #line default + #line hidden +WriteLiteral("\r\n"); + +WriteLiteral(" "); + + + #line 691 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceBatchAssignedUsersManagedGroup.GetCategoryDescription(Model.DeviceBatch), + Description = DeviceBatchAssignedUsersManagedGroup.GetDescription(Model.DeviceBatch), + ManagedGroup = Model.AssignedUsersLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceBatch.UpdateAssignedUsersLinkedGroup(Model.DeviceBatch.Id, redirect: true)) + })); + + + #line default + #line hidden +WriteLiteral("\r\n"); + + + #line 699 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + + + #line default + #line hidden + + #line 699 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + if (canConfig) + { + + + #line default + #line hidden + + #line 701 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupShared)); + + + #line default + #line hidden + + #line 701 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + + } + + + #line default + #line hidden +WriteLiteral("
\r\n \r\n  Linked Active Directory Groups are automatically synchronized to inclu" + +"de members currently associated with this Device Batch.\r\n \r" + +"\n \r\n \r\n \r\n\r\n\r\n"); - #line 658 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 712 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" #line default #line hidden - #line 658 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 712 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (Model.CanDelete) { @@ -1988,14 +2147,14 @@ WriteLiteral(">\r\n"); #line default #line hidden - #line 660 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 714 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.ActionLinkButton("Delete", MVC.API.DeviceBatch.Delete(Model.DeviceBatch.Id, true), "buttonDelete")); #line default #line hidden - #line 660 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 714 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } @@ -2005,7 +2164,7 @@ WriteLiteral(">\r\n"); WriteLiteral(" "); - #line 662 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 716 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" if (Model.DeviceCount > 0) { if (Authorization.Has(Claims.Device.Actions.Export)) @@ -2015,14 +2174,14 @@ WriteLiteral(" "); #line default #line hidden - #line 666 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 720 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.Exporting.DeviceExportTypes.Batch, Model.DeviceBatch.Id))); #line default #line hidden - #line 666 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 720 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } if (Authorization.Has(Claims.Device.Search) && Model.DeviceCount > 0) @@ -2032,14 +2191,14 @@ WriteLiteral(" "); #line default #line hidden - #line 670 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 724 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" Write(Html.ActionLinkButton(string.Format("View {0} Device{1}", Model.DeviceCount, (Model.DeviceCount != 1 ? "s" : null)), MVC.Search.Query(Model.DeviceBatch.Id.ToString(), "DeviceBatch"))); #line default #line hidden - #line 670 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" + #line 724 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" } } @@ -2047,7 +2206,7 @@ WriteLiteral(" "); #line default #line hidden -WriteLiteral(""); +WriteLiteral("\r\n"); } } diff --git a/Disco.Web/Areas/Config/Views/DeviceProfile/Show.cshtml b/Disco.Web/Areas/Config/Views/DeviceProfile/Show.cshtml index 97402d64..ef8d287d 100644 --- a/Disco.Web/Areas/Config/Views/DeviceProfile/Show.cshtml +++ b/Disco.Web/Areas/Config/Views/DeviceProfile/Show.cshtml @@ -1,4 +1,6 @@ @model Disco.Web.Areas.Config.Models.DeviceProfile.ShowModel +@using Disco.Services.Devices.ManagedGroups; +@using Disco.Web.Areas.Config.Models.Shared; @{ Authorization.Require(Claims.Config.DeviceProfile.Show); @@ -8,13 +10,17 @@ var canConfigExpression = Authorization.Has(Claims.Config.DeviceProfile.ConfigureComputerNameTemplate); var canDelete = (Authorization.Has(Claims.Config.DeviceProfile.Delete) && Model.CanDelete); + var hideAdvanced = + Model.DeviceProfile.AssignedUsersLinkedGroup == null && + Model.DeviceProfile.DevicesLinkedGroup == null; + if (canConfig) { Html.BundleDeferred("~/Style/Fancytree"); Html.BundleDeferred("~/ClientScripts/Modules/jQuery-Fancytree"); } } -
+
+ @if (hideAdvanced) + { + + + + } + + + +
Id: @@ -689,6 +695,54 @@
+ + +
Linked Groups: + +
+ @Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceProfileDevicesManagedGroup.GetCategoryDescription(Model.DeviceProfile), + Description = DeviceProfileDevicesManagedGroup.GetDescription(Model.DeviceProfile), + ManagedGroup = Model.DevicesLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceProfile.UpdateDevicesLinkedGroup(Model.DeviceProfile.Id, redirect: true)) + }) + @Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceProfileAssignedUsersManagedGroup.GetCategoryDescription(Model.DeviceProfile), + Description = DeviceProfileAssignedUsersManagedGroup.GetDescription(Model.DeviceProfile), + ManagedGroup = Model.AssignedUsersLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceProfile.UpdateAssignedUsersLinkedGroup(Model.DeviceProfile.Id, redirect: true)) + }) + @if (canConfig) + { + @Html.Partial(MVC.Config.Shared.Views.LinkedGroupShared) + } +
+
+  Linked Active Directory Groups are automatically synchronized to include members currently associated with this Device Profile. +
+
@if (canDelete) diff --git a/Disco.Web/Areas/Config/Views/DeviceProfile/Show.generated.cs b/Disco.Web/Areas/Config/Views/DeviceProfile/Show.generated.cs index b27c1345..d88c0458 100644 --- a/Disco.Web/Areas/Config/Views/DeviceProfile/Show.generated.cs +++ b/Disco.Web/Areas/Config/Views/DeviceProfile/Show.generated.cs @@ -31,8 +31,20 @@ namespace Disco.Web.Areas.Config.Views.DeviceProfile using Disco.Models.Repository; using Disco.Services; using Disco.Services.Authorization; + + #line 2 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + using Disco.Services.Devices.ManagedGroups; + + #line default + #line hidden using Disco.Services.Web; using Disco.Web; + + #line 3 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + using Disco.Web.Areas.Config.Models.Shared; + + #line default + #line hidden using Disco.Web.Extensions; [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] @@ -45,7 +57,7 @@ namespace Disco.Web.Areas.Config.Views.DeviceProfile public override void Execute() { - #line 2 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 4 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Authorization.Require(Claims.Config.DeviceProfile.Show); @@ -55,6 +67,10 @@ namespace Disco.Web.Areas.Config.Views.DeviceProfile var canConfigExpression = Authorization.Has(Claims.Config.DeviceProfile.ConfigureComputerNameTemplate); var canDelete = (Authorization.Has(Claims.Config.DeviceProfile.Delete) && Model.CanDelete); + var hideAdvanced = + Model.DeviceProfile.AssignedUsersLinkedGroup == null && + Model.DeviceProfile.DevicesLinkedGroup == null; + if (canConfig) { Html.BundleDeferred("~/Style/Fancytree"); @@ -68,7 +84,16 @@ WriteLiteral("\r\n(hideAdvanced ? " Config_HideAdvanced" : null + + #line default + #line hidden +, 1046), false) +); WriteLiteral(" style=\"width: 640px\""); @@ -81,7 +106,7 @@ WriteLiteral(">Id:\r\n \r\n \r\n"); WriteLiteral(" "); - #line 23 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 29 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.DisplayFor(model => model.DeviceProfile.Id)); @@ -91,7 +116,7 @@ WriteLiteral("\r\n \r\n \r\n \r\n " \r\n "); - #line 29 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 35 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -99,42 +124,42 @@ WriteLiteral("\r\n \r\n \r\n \r\n #line default #line hidden - #line 31 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 37 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.TextBoxFor(model => model.DeviceProfile.Name)); #line default #line hidden - #line 31 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 37 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 32 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 38 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 32 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 38 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 33 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 39 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 33 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 39 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -167,7 +192,7 @@ WriteLiteral(@"> url: '"); - #line 54 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 60 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateName(Model.DeviceProfile.Id))); @@ -195,7 +220,7 @@ WriteLiteral(@"', "); - #line 73 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 79 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -204,14 +229,14 @@ WriteLiteral(@"', #line default #line hidden - #line 76 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 82 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.Name); #line default #line hidden - #line 76 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 82 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -222,7 +247,7 @@ WriteLiteral(" \r\n \r\n \r\n " \r\n "); - #line 83 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 89 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -230,42 +255,42 @@ WriteLiteral(" \r\n \r\n \r\n #line default #line hidden - #line 85 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 91 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.TextBoxFor(model => model.DeviceProfile.ShortName)); #line default #line hidden - #line 85 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 91 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 86 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 92 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 86 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 92 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 87 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 93 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 87 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 93 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -298,7 +323,7 @@ WriteLiteral(@"> url: '"); - #line 108 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 114 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateShortName(Model.DeviceProfile.Id))); @@ -326,7 +351,7 @@ WriteLiteral(@"', "); - #line 127 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 133 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -335,14 +360,14 @@ WriteLiteral(@"', #line default #line hidden - #line 130 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 136 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.ShortName); #line default #line hidden - #line 130 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 136 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -353,7 +378,7 @@ WriteLiteral(" \r\n \r\n \r\n " \r\n "); - #line 137 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 143 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -361,42 +386,42 @@ WriteLiteral(" \r\n \r\n \r\n #line default #line hidden - #line 139 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 145 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.TextBoxFor(model => model.DeviceProfile.Description)); #line default #line hidden - #line 139 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 145 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 140 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 146 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 140 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 146 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 141 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 147 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 141 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 147 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -429,7 +454,7 @@ WriteLiteral(@"> url: '"); - #line 162 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 168 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateDescription(Model.DeviceProfile.Id))); @@ -457,7 +482,7 @@ WriteLiteral(@"', "); - #line 181 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 187 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -466,14 +491,14 @@ WriteLiteral(@"', #line default #line hidden - #line 184 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 190 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.Description); #line default #line hidden - #line 184 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 190 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -484,7 +509,7 @@ WriteLiteral(" \r\n \r\n \r\n " \r\n
"); - #line 191 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 197 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceCount.ToString("n0")); @@ -493,7 +518,7 @@ WriteLiteral(" \r\n \r\n \r\n WriteLiteral(" "); - #line 191 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 197 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceCount == 1 ? "devices is a member" : "devices are members"); @@ -502,13 +527,13 @@ WriteLiteral(" "); WriteLiteral(" of this profile.
\r\n"); - #line 192 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 198 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 192 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 198 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (Model.DeviceDecommissionedCount > 0) { @@ -522,7 +547,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral(">"); - #line 194 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 200 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceDecommissionedCount.ToString("n0")); @@ -531,7 +556,7 @@ WriteLiteral(">"); WriteLiteral(" "); - #line 194 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 200 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceDecommissionedCount == 1 ? "device is" : "devices are"); @@ -540,7 +565,7 @@ WriteLiteral(" "); WriteLiteral(" decommissioned.
\r\n"); - #line 195 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 201 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -550,7 +575,7 @@ WriteLiteral(" \r\n \r\n \r\n ":\r\n \r\n "); - #line 201 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 207 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -558,28 +583,28 @@ WriteLiteral(" \r\n \r\n \r\n #line default #line hidden - #line 203 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 209 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.DropDownList("DeviceProfile_DistributionType", Model.DeviceProfileDistributionTypes)); #line default #line hidden - #line 203 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 209 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 204 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 210 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 204 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 210 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -598,7 +623,7 @@ WriteLiteral(@"> $.getJSON('"); - #line 211 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 217 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateDistributionType(Model.DeviceProfile.Id))); @@ -618,7 +643,7 @@ WriteLiteral(@"', data, function (response, result) { "); - #line 222 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 228 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -627,14 +652,14 @@ WriteLiteral(@"', data, function (response, result) { #line default #line hidden - #line 225 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 231 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.DistributionType.ToString()); #line default #line hidden - #line 225 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 231 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -645,7 +670,7 @@ WriteLiteral(" \r\n \r\n \r\n " \r\n "); - #line 232 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 238 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -653,28 +678,28 @@ WriteLiteral(" \r\n \r\n \r\n #line default #line hidden - #line 234 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 240 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.DropDownListFor(m => m.DeviceProfile.DefaultOrganisationAddress, Model.OrganisationAddresses.ToSelectListItems(Model.DeviceProfile.DefaultOrganisationAddress, true, "None"))); #line default #line hidden - #line 234 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 240 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 235 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 241 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 235 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 241 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -693,7 +718,7 @@ WriteLiteral(@"> $.getJSON('"); - #line 242 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 248 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateDefaultOrganisationAddress(Model.DeviceProfile.Id))); @@ -713,7 +738,7 @@ WriteLiteral(@"', data, function (response, result) { "); - #line 253 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 259 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -730,7 +755,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>\r\n"); - #line 259 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 265 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -739,14 +764,14 @@ WriteLiteral("><None Specified>\r\n"); #line default #line hidden - #line 262 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 268 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DefaultOrganisationAddress.ToString()); #line default #line hidden - #line 262 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 268 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } } @@ -758,7 +783,7 @@ WriteLiteral(" \r\n \r\n \r\n "ates:\r\n \r\n "); - #line 270 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 276 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -766,28 +791,28 @@ WriteLiteral(" \r\n \r\n \r\n #line default #line hidden - #line 272 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 278 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.DropDownListFor(model => model.DeviceProfile.CertificateProviderId, Model.CertificateProviders.ToSelectListItems(null, true, "Not Allocated"))); #line default #line hidden - #line 272 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 278 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 273 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 279 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 273 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 279 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -809,7 +834,7 @@ WriteLiteral(@"> url: '"); - #line 283 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 289 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateCertificateProviderId(Model.DeviceProfile.Id))); @@ -837,7 +862,7 @@ WriteLiteral(@"', "); - #line 302 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 308 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -854,7 +879,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Allocated>\r\n"); - #line 308 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 314 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -872,7 +897,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Allocated>\r\n"); - #line 315 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 321 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -881,14 +906,14 @@ WriteLiteral("><None Allocated>\r\n"); #line default #line hidden - #line 318 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 324 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(cp.Name); #line default #line hidden - #line 318 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 324 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } } @@ -901,7 +926,7 @@ WriteLiteral(" \r\n \r\n \r\n "/>\r\n Template Expression:\r\n \r\n "); - #line 328 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 334 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig && canConfigExpression) { @@ -909,42 +934,42 @@ WriteLiteral(" \r\n \r\n \r\n #line default #line hidden - #line 330 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 336 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.TextBoxFor(model => model.DeviceProfile.ComputerNameTemplate)); #line default #line hidden - #line 330 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 336 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 331 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 337 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 331 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 337 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 332 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 338 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 332 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 338 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -954,14 +979,14 @@ WriteLiteral(" (Url.Action(MVC.Config.DocumentTemplate.ExpressionBrowser()) + #line 339 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" +, Tuple.Create(Tuple.Create("", 16564), Tuple.Create(Url.Action(MVC.Config.DocumentTemplate.ExpressionBrowser()) #line default #line hidden -, 16277), false) +, 16564), false) ); WriteLiteral("> \r\n"); @@ -992,7 +1017,7 @@ WriteLiteral(@"> url: '"); - #line 353 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 359 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateComputerNameTemplate(Model.DeviceProfile.Id))); @@ -1020,7 +1045,7 @@ WriteLiteral(@"', "); - #line 372 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 378 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1037,13 +1062,13 @@ WriteLiteral(" class=\"code\""); WriteLiteral(">\r\n"); - #line 376 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 382 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 376 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 382 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (string.IsNullOrWhiteSpace(Model.DeviceProfile.ComputerNameTemplate)) { @@ -1057,7 +1082,7 @@ WriteLiteral(" class=\"smallMessage\""); WriteLiteral("><None Specified>\r\n"); - #line 379 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 385 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1066,14 +1091,14 @@ WriteLiteral("><None Specified>\r\n"); #line default #line hidden - #line 382 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 388 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.ComputerNameTemplate); #line default #line hidden - #line 382 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 388 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1083,7 +1108,7 @@ WriteLiteral("><None Specified>\r\n"); WriteLiteral(" \r\n"); - #line 385 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 391 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1096,13 +1121,13 @@ WriteLiteral(" style=\"margin-top: 8px;\""); WriteLiteral(">\r\n"); - #line 387 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 393 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 387 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 393 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -1118,7 +1143,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 389 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 395 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.EnforceComputerNameConvention ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1139,7 +1164,7 @@ WriteLiteral(@"> $.getJSON('"); - #line 396 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 402 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateEnforceComputerNameConvention(Model.DeviceProfile.Id))); @@ -1159,7 +1184,7 @@ WriteLiteral(@"', data, function (response, result) { "); - #line 407 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 413 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1176,7 +1201,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 410 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 416 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.EnforceComputerNameConvention ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1185,7 +1210,7 @@ WriteLiteral(" "); WriteLiteral(" disabled=\"disabled\" />\r\n"); - #line 411 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 417 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1201,7 +1226,7 @@ WriteLiteral(">\r\n Enforce Naming Convention\r\n WriteLiteral(" "); - #line 415 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 421 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); @@ -1219,13 +1244,13 @@ WriteLiteral(">\r\n Note: Computer names are only changed whe "
\r\n"); - #line 426 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 432 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 426 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 432 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -1241,7 +1266,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 428 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 434 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.ProvisionADAccount ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1262,7 +1287,7 @@ WriteLiteral(@"> $.getJSON('"); - #line 435 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 441 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateProvisionADAccount(Model.DeviceProfile.Id))); @@ -1282,7 +1307,7 @@ WriteLiteral(@"', data, function (response, result) { "); - #line 446 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 452 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1299,7 +1324,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 449 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 455 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.ProvisionADAccount ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1308,7 +1333,7 @@ WriteLiteral(" "); WriteLiteral(" disabled=\"disabled\" />\r\n"); - #line 450 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 456 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1324,7 +1349,7 @@ WriteLiteral(">\r\n Provision Active Directory Account\r\ WriteLiteral(" "); - #line 454 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 460 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); @@ -1337,13 +1362,13 @@ WriteLiteral(" style=\"margin-top: 8px;\""); WriteLiteral(">\r\n"); - #line 457 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 463 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 457 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 463 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -1359,7 +1384,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 459 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 465 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.AssignedUserLocalAdmin ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1380,7 +1405,7 @@ WriteLiteral(@"> $.getJSON('"); - #line 466 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 472 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateAssignedUserLocalAdmin(Model.DeviceProfile.Id))); @@ -1400,7 +1425,7 @@ WriteLiteral(@"', data, function (response, result) { "); - #line 477 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 483 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1417,7 +1442,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 480 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 486 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.AssignedUserLocalAdmin ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1426,7 +1451,7 @@ WriteLiteral(" "); WriteLiteral(" disabled=\"disabled\" />\r\n"); - #line 481 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 487 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1442,7 +1467,7 @@ WriteLiteral(">\r\n Assigned User is Local Administrator\ WriteLiteral(" "); - #line 485 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 491 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); @@ -1455,13 +1480,13 @@ WriteLiteral(" style=\"margin-top: 8px;\""); WriteLiteral(">\r\n"); - #line 488 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 494 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 488 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 494 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -1477,7 +1502,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 490 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 496 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.AllowUntrustedReimageJobEnrolment ? new MvcHtmlString("checked=\"checked\" ") : null); @@ -1498,7 +1523,7 @@ WriteLiteral(@"> $.getJSON('"); - #line 497 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 503 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateAllowUntrustedReimageJobEnrolment(Model.DeviceProfile.Id))); @@ -1518,7 +1543,7 @@ WriteLiteral(@"', data, function (response, result) { "); - #line 508 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 514 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1535,7 +1560,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 511 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 517 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.AllowUntrustedReimageJobEnrolment ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1544,7 +1569,7 @@ WriteLiteral(" "); WriteLiteral(" disabled=\"disabled\" />\r\n"); - #line 512 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 518 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1563,7 +1588,7 @@ WriteLiteral(">\'Software - Reimage\' Job is Open\r\n WriteLiteral(" "); - #line 516 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 522 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); @@ -1573,7 +1598,7 @@ WriteLiteral("\r\n
\r\n \r\n \r " Organisational Unit:\r\n \r\n "); - #line 523 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 529 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -1589,7 +1614,7 @@ WriteLiteral(" class=\"code\""); WriteLiteral(" data-value=\""); - #line 525 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 531 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.OrganisationalUnit); @@ -1602,7 +1627,7 @@ WriteLiteral(">\r\n \r\n"); WriteLiteral(" "); - #line 527 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 533 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.FriendlyOrganisationalUnitName); @@ -1621,20 +1646,20 @@ WriteLiteral(" class=\"button small\""); WriteLiteral(">Change"); - #line 530 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 536 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 530 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 536 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 530 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 536 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" @@ -1657,7 +1682,7 @@ WriteLiteral(">\r\n"); WriteLiteral(" "); - #line 533 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 539 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); @@ -1679,7 +1704,7 @@ WriteLiteral(" type=\"text/javascript\""); WriteLiteral(">\r\n $(function () {\r\n var ouSetUrl = \'"); - #line 540 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 546 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateOrganisationalUnit(Model.DeviceProfile.Id, null, true))); @@ -1721,7 +1746,7 @@ WriteLiteral("\';\r\n var ouValue = $(\'#DeviceProfile_Or " $ouTree.css(\'height\', \'100%\');\r\n\r\n $.getJSON(\'"); - #line 590 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 596 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.System.DomainOrganisationalUnits())); @@ -1768,7 +1793,7 @@ WriteLiteral("\', null, function (data) {\r\n " \r\n"); - #line 650 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 656 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1787,7 +1812,7 @@ WriteLiteral(">\r\n \r\n"); WriteLiteral(" "); - #line 655 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 661 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.FriendlyOrganisationalUnitName); @@ -1796,7 +1821,7 @@ WriteLiteral(" "); WriteLiteral("\r\n \r\n \r\n"); - #line 658 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 664 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1809,13 +1834,13 @@ WriteLiteral(" style=\"margin-top: 8px;\""); WriteLiteral(">\r\n"); - #line 660 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 666 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 660 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 666 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canConfig) { @@ -1831,7 +1856,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 662 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 668 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.EnforceOrganisationalUnit ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1852,7 +1877,7 @@ WriteLiteral(@"> $.getJSON('"); - #line 669 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 675 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Url.Action(MVC.API.DeviceProfile.UpdateEnforceOrganisationalUnit(Model.DeviceProfile.Id))); @@ -1872,7 +1897,7 @@ WriteLiteral(@"', data, function (response, result) { "); - #line 680 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 686 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } else { @@ -1889,7 +1914,7 @@ WriteLiteral(" type=\"checkbox\""); WriteLiteral(" "); - #line 683 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 689 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Model.DeviceProfile.EnforceOrganisationalUnit ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)); @@ -1898,7 +1923,7 @@ WriteLiteral(" "); WriteLiteral(" disabled=\"disabled\" />\r\n"); - #line 684 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 690 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1914,17 +1939,149 @@ WriteLiteral(">\r\n Enforce Organisational Unit\r\n WriteLiteral(" "); - #line 688 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 694 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(AjaxHelpers.AjaxLoader()); #line default #line hidden -WriteLiteral("\r\n \r\n \r\n \r\n \r\n\r" + -"\n"); +WriteLiteral("\r\n \r\n \r\n \r\n"); - #line 694 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 698 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + + + #line default + #line hidden + + #line 698 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + if (hideAdvanced) + { + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n Show Advanced Options + + + +"); + + + #line 714 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n Linked Groups:\r\n \r\n \r\n " + +"
\r\n"); + +WriteLiteral(" "); + + + #line 720 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceProfileDevicesManagedGroup.GetCategoryDescription(Model.DeviceProfile), + Description = DeviceProfileDevicesManagedGroup.GetDescription(Model.DeviceProfile), + ManagedGroup = Model.DevicesLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceProfile.UpdateDevicesLinkedGroup(Model.DeviceProfile.Id, redirect: true)) + })); + + + #line default + #line hidden +WriteLiteral("\r\n"); + +WriteLiteral(" "); + + + #line 728 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DeviceProfileAssignedUsersManagedGroup.GetCategoryDescription(Model.DeviceProfile), + Description = DeviceProfileAssignedUsersManagedGroup.GetDescription(Model.DeviceProfile), + ManagedGroup = Model.AssignedUsersLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DeviceProfile.UpdateAssignedUsersLinkedGroup(Model.DeviceProfile.Id, redirect: true)) + })); + + + #line default + #line hidden +WriteLiteral("\r\n"); + + + #line 736 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + + + #line default + #line hidden + + #line 736 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + if (canConfig) + { + + + #line default + #line hidden + + #line 738 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupShared)); + + + #line default + #line hidden + + #line 738 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + + } + + + #line default + #line hidden +WriteLiteral("
\r\n \r\n  Linked Active Directory Groups are automatically synchronized to inclu" + +"de members currently associated with this Device Profile.\r\n \r\n \r\n \r\n \r\n\r\n"); + + + #line 748 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canDelete) { @@ -1979,7 +2136,7 @@ WriteLiteral(@"> "); - #line 730 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 784 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -1992,13 +2149,13 @@ WriteLiteral(" class=\"actionBar\""); WriteLiteral(">\r\n"); - #line 732 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 786 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line default #line hidden - #line 732 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 786 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (canDelete) { @@ -2006,14 +2163,14 @@ WriteLiteral(">\r\n"); #line default #line hidden - #line 734 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 788 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.ActionLinkButton("Delete", MVC.API.DeviceProfile.Delete(Model.DeviceProfile.Id, true), "buttonDelete")); #line default #line hidden - #line 734 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 788 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -2023,7 +2180,7 @@ WriteLiteral(">\r\n"); WriteLiteral(" "); - #line 736 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 790 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (Authorization.Has(Claims.Device.Actions.Export)) { @@ -2031,14 +2188,14 @@ WriteLiteral(" "); #line default #line hidden - #line 738 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 792 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.Exporting.DeviceExportTypes.Profile, Model.DeviceProfile.Id))); #line default #line hidden - #line 738 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 792 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } @@ -2048,7 +2205,7 @@ WriteLiteral(" "); WriteLiteral(" "); - #line 740 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 794 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" if (Authorization.Has(Claims.Device.Search) && Model.DeviceCount > 0) { @@ -2056,14 +2213,14 @@ WriteLiteral(" "); #line default #line hidden - #line 742 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 796 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" Write(Html.ActionLinkButton(string.Format("View {0} Device{1}", Model.DeviceCount, (Model.DeviceCount != 1 ? "s" : null)), MVC.Search.Query(Model.DeviceProfile.Id.ToString(), "DeviceProfile"))); #line default #line hidden - #line 742 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" + #line 796 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" } diff --git a/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser.cshtml b/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser.cshtml index d6a60935..c7dc0f04 100644 --- a/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser.cshtml +++ b/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser.cshtml @@ -10,24 +10,24 @@ Expressions within Disco are based on the Spring.NET Framework. Please refer to the Expression Evaluation documentation. -

+

Device Scope

-

+

Job Scope

-

+

User Scope

-

+

Variables

-

+

Extension Libraries

diff --git a/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser2.generated.cs b/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser2.generated.cs index fadd74d2..731704cc 100644 --- a/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser2.generated.cs +++ b/Disco.Web/Areas/Config/Views/DocumentTemplate/ExpressionBrowser2.generated.cs @@ -73,32 +73,51 @@ WriteLiteral(" href=\"http://www.springframework.net/doc-latest/reference/html/e WriteLiteral("\r\n target=\"_blank\""); -WriteLiteral(">Expression Evaluation documentation.\r\n

\r\n Device Scope

\r\n " + -" Expression Evaluation documentation.\r\n \r\n Device Scope\r\n \r\n \r\n

\r\n Job Scope

\r\n \r\n \r\n \r\n Job Scope\r\n \r\n \r\n

\r\n User Scope

\r\n \r\n \r\n \r\n User Scope\r\n \r\n \r\n

\r\n Variables\r\n

\r\n \r\n \r\n \r\n Variables\r\n \r\n \r\n \r\n

\r\n Extension Libraries

\r\n \r\n \r\n \r\n Extension Libraries\r\n e.All(p => !p.ParseError)); + #region Can Bulk Generate var canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate); if (canBulkGenerate) @@ -28,243 +36,179 @@ ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "Document Templates", MVC.Config.DocumentTemplate.Index(null), Model.DocumentTemplate.Description); } -
- - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + @if (hideAdvanced) { - if (string.IsNullOrWhiteSpace(Model.DocumentTemplate.FilterExpression)) - { - <None Specified> - } - else - { -
- @Model.DocumentTemplate.FilterExpression -
- } + + + } - - -
Id: - @Html.DisplayFor(model => model.DocumentTemplate.Id) -
Stored Instances: - @Html.DisplayFor(model => model.StoredInstanceCount) -
Description: - @if (canConfig) - { - @Html.TextBoxFor(model => model.DocumentTemplate.Description) - @AjaxHelpers.AjaxSave() - @AjaxHelpers.AjaxLoader() - - } - else - { - if (string.IsNullOrEmpty(Model.DocumentTemplate.Description)) - { - <None Specified> - } - else - { - @Model.DocumentTemplate.Description - } - } -
Always Flatten Form: - @if (canConfig) - { - - @AjaxHelpers.AjaxLoader() - - } - else - { - - } -
Scope: - @if (canConfig) - { - @Html.DropDownListFor(model => model.DocumentTemplate.Scope, Model.Scopes.ToSelectListItems(null)) - @AjaxHelpers.AjaxLoader() - - } - else - { -
@Model.DocumentTemplate.Scope
- } - @if (canConfig || (Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.Job)) - { -
-

Filter:

-
- @if (Model.DocumentTemplate.JobSubTypes.Count > 0) - { -
    - @foreach (var jobType in Model.DocumentTemplate.JobSubTypes.GroupBy(jst => jst.JobType).OrderBy(jtg => jtg.Key.Description)) - { -
  • - @jobType.Key.Description -
      - @if (jobType.Count() == Model.JobTypes.FirstOrDefault(jt => jt.Id == jobType.Key.Id).JobSubTypes.Count) - { -
    • [All Sub Types]
    • - } - else - { - foreach (var jobSubType in jobType) - { -
    • @jobSubType.Description
    • + }).blur(function () { + $DescriptionAjaxSave.hide(); + }) + .change(function () { + $DescriptionAjaxSave.hide(); + var $ajaxLoading = $DescriptionAjaxSave.next('.ajaxLoading').show(); + var data = { Description: $Description.val() }; + $.ajax({ + url: '@Url.Action(MVC.API.DocumentTemplate.UpdateDescription(Model.DocumentTemplate.Id))', + dataType: 'json', + data: data, + success: function (d) { + if (d == 'OK') { + $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); + } else { + $ajaxLoading.hide(); + alert('Unable to update description: ' + d); } + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('Unable to update description: ' + textStatus); + $ajaxLoading.hide(); } -
    -
  • - } -
+ }); + }); + }); + + } + else + { + if (string.IsNullOrEmpty(Model.DocumentTemplate.Description)) + { + <None Specified> } else { - <No Filter> + @Model.DocumentTemplate.Description } + } +
Always Flatten Form: + @if (canConfig) + { + + @AjaxHelpers.AjaxLoader() + + } + else + { + + } +
Scope: + +

@Model.DocumentTemplate.Scope Scope

+
+

+ This template is generated from @(Model.DocumentTemplate.Scope)s. Any expressions within the Template PDF will be evaluated within the @(Model.DocumentTemplate.Scope) Scope. +

+
+
+
@if (canConfig) { - Update -
- @using (Html.BeginForm(MVC.API.DocumentTemplate.UpdateJobSubTypes(Model.DocumentTemplate.Id, null, true))) +
+ @using (Html.BeginForm(MVC.API.DocumentTemplate.UpdateScope(Model.DocumentTemplate.Id, redirect: true))) { - var selectedTypes = Model.DocumentTemplate.JobSubTypes.Select(jst => jst.JobType).Distinct().ToList(); - foreach (var jt in Model.JobTypes) - { -
-

-

-
- @CommonHelpers.CheckboxBulkSelect(string.Format("CheckboxBulkSelect_{0}", jt.Id), "div") - @CommonHelpers.CheckBoxList("JobSubTypes", jt.JobSubTypes.OrderBy(jst => jst.Description).ToSelectListItems(Model.DocumentTemplate.JobSubTypes), 2) -
-
- } +
+ + +
+ } +
+

+ Expressions within the Template PDF may need to be updated to reflect any changes to the Document Template Scope. +

+
+ @if (Model.DocumentTemplate.UsersLinkedGroup != null || Model.DocumentTemplate.DevicesLinkedGroup != null) + { +
+

+ Warning: This Document Template contains Linked Groups, these will be automatically updated to reflect the new Document Template Scope which may result in undesired behaviour. +

+
}
- } -
- } -
Template PDF - - @Html.ActionLink("Download Template", MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id)) - @if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.Upload)) - { -
- using (Html.BeginForm(MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id, true, null), FormMethod.Post, new { enctype = "multipart/form-data" })) - { - - - } - - } -
Filter Expression: - @if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression)) - { - @Html.TextBoxFor(model => model.DocumentTemplate.FilterExpression) - @AjaxHelpers.AjaxRemove() - @AjaxHelpers.AjaxLoader() - + } + + } +
PDF Template + + @Html.ActionLinkSmallButton("Download Template", MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id)) + @if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.Upload)) + { + +
+

Select a PDF Template to upload:

+
+ @using (Html.BeginForm(MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id, true, null), FormMethod.Post, new { enctype = "multipart/form-data" })) + { + + } +
+
+ - } - else + + } +
+ + +
+ + +
+
+

Advanced Options

+ + + + + + + + + + + +
Filter Expression: + @if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression)) + { + @Html.TextBoxFor(model => model.DocumentTemplate.FilterExpression) + @AjaxHelpers.AjaxRemove() + @AjaxHelpers.AjaxLoader() + + } + else + { + if (string.IsNullOrWhiteSpace(Model.DocumentTemplate.FilterExpression)) + { + <None Specified> + } + else + { +
+ @Model.DocumentTemplate.FilterExpression +
+ } + } +
Linked Groups: + +
+ @Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DocumentTemplateUsersManagedGroup.GetCategoryDescription(Model.DocumentTemplate), + Description = DocumentTemplateUsersManagedGroup.GetDescription(Model.DocumentTemplate), + ManagedGroup = Model.UsersLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DocumentTemplate.UpdateUsersLinkedGroup(Model.DocumentTemplate.Id, redirect: true)) + }) + @Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DocumentTemplateDevicesManagedGroup.GetCategoryDescription(Model.DocumentTemplate), + Description = DocumentTemplateDevicesManagedGroup.GetDescription(Model.DocumentTemplate), + ManagedGroup = Model.DevicesLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DocumentTemplate.UpdateDevicesLinkedGroup(Model.DocumentTemplate.Id, redirect: true)) + }) + @if (canConfig) + { + @Html.Partial(MVC.Config.Shared.Views.LinkedGroupShared) + } +
+
+
+
+

Template Expressions

+ @Html.Partial(MVC.Config.DocumentTemplate.Views._ExpressionsTable, Model.TemplateExpressions) +
-

Template Expressions

-@Html.Partial(MVC.Config.DocumentTemplate.Views._ExpressionsTable, Model.TemplateExpressions)

This item will be permanently deleted and cannot be recovered.
diff --git a/Disco.Web/Areas/Config/Views/DocumentTemplate/Show.generated.cs b/Disco.Web/Areas/Config/Views/DocumentTemplate/Show.generated.cs index b50214af..720fd934 100644 --- a/Disco.Web/Areas/Config/Views/DocumentTemplate/Show.generated.cs +++ b/Disco.Web/Areas/Config/Views/DocumentTemplate/Show.generated.cs @@ -27,18 +27,30 @@ namespace Disco.Web.Areas.Config.Views.DocumentTemplate using System.Web.UI; using System.Web.WebPages; using Disco; + + #line 2 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + using Disco.BI.DocumentTemplateBI.ManagedGroups; + + #line default + #line hidden using Disco.BI.Extensions; using Disco.Models.Repository; using Disco.Services; using Disco.Services.Authorization; - #line 2 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 3 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" using Disco.Services.Interop.ActiveDirectory; #line default #line hidden using Disco.Services.Web; using Disco.Web; + + #line 4 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + using Disco.Web.Areas.Config.Models.Shared; + + #line default + #line hidden using Disco.Web.Extensions; [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] @@ -51,12 +63,18 @@ namespace Disco.Web.Areas.Config.Views.DocumentTemplate public override void Execute() { - #line 3 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 5 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" Authorization.Require(Claims.Config.DocumentTemplate.Show); var canConfig = Authorization.Has(Claims.Config.DocumentTemplate.Configure); + var hideAdvanced = + Model.DocumentTemplate.UsersLinkedGroup == null && + Model.DocumentTemplate.DevicesLinkedGroup == null && + Model.DocumentTemplate.FilterExpression == null && + Model.TemplateExpressions.All(e => e.All(p => !p.ParseError)); + #region Can Bulk Generate var canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate); if (canBulkGenerate) @@ -85,191 +103,217 @@ namespace Disco.Web.Areas.Config.Views.DocumentTemplate #line hidden WriteLiteral("\r\n(hideAdvanced ? "Config_HideAdvanced" : null + + #line default + #line hidden +, 1778), false) +); + +WriteLiteral(">\r\n \r\n \r\n \r\n \r\n " + -" \r\n \r\n \r\n " + +" \r\n \r\n
\r\n"); + +WriteLiteral(" "); - #line 408 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" -Write(Html.Partial(MVC.Config.DocumentTemplate.Views._ExpressionsTable, Model.TemplateExpressions)); + #line 503 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel() + { + CanConfigure = canConfig, + CategoryDescription = DocumentTemplateUsersManagedGroup.GetCategoryDescription(Model.DocumentTemplate), + Description = DocumentTemplateUsersManagedGroup.GetDescription(Model.DocumentTemplate), + ManagedGroup = Model.UsersLinkedGroup, + UpdateUrl = Url.Action(MVC.API.DocumentTemplate.UpdateUsersLinkedGroup(Model.DocumentTemplate.Id, redirect: true)) + })); #line default #line hidden -WriteLiteral("\r\n
\r" + +"\n \r\n
Id:\r\n "); +WriteLiteral(">\r\n \r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + +" \r\n \r\n "); - #line 42 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Html.DisplayFor(model => model.StoredInstanceCount)); + #line 53 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Html.DisplayFor(model => model.StoredInstanceCount)); #line default #line hidden -WriteLiteral("\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + +" \r\n "); + + + #line 59 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (canConfig) + { + #line default #line hidden - #line 50 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Html.TextBoxFor(model => model.DocumentTemplate.Description)); + #line 61 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Html.TextBoxFor(model => model.DocumentTemplate.Description)); #line default #line hidden - #line 50 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - - + #line 61 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + #line default #line hidden - #line 51 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(AjaxHelpers.AjaxSave()); + #line 62 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(AjaxHelpers.AjaxSave()); #line default #line hidden - #line 51 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - - + #line 62 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + #line default #line hidden - #line 52 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(AjaxHelpers.AjaxLoader()); + #line 63 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 52 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - + #line 63 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line default #line hidden -WriteLiteral(" - $(function () { - var $Description = $('#DocumentTemplate_Description'); - var $DescriptionAjaxSave = $Description.next('.ajaxSave'); - $Description - .watermark('Description') - .focus(function () { $Description.select() }) - .keydown(function (e) { - $DescriptionAjaxSave.show(); - if (e.which == 13) { - $(this).blur(); - } - }).blur(function () { - $DescriptionAjaxSave.hide(); - }) - .change(function () { - $DescriptionAjaxSave.hide(); - var $ajaxLoading = $DescriptionAjaxSave.next('.ajaxLoading').show(); - var data = { Description: $Description.val() }; - $.ajax({ - url: '"); + $(function () { + var $Description = $('#DocumentTemplate_Description'); + var $DescriptionAjaxSave = $Description.next('.ajaxSave'); + $Description + .watermark('Description') + .focus(function () { $Description.select() }) + .keydown(function (e) { + $DescriptionAjaxSave.show(); + if (e.which == 13) { + $(this).blur(); + } + }).blur(function () { + $DescriptionAjaxSave.hide(); + }) + .change(function () { + $DescriptionAjaxSave.hide(); + var $ajaxLoading = $DescriptionAjaxSave.next('.ajaxLoading').show(); + var data = { Description: $Description.val() }; + $.ajax({ + url: '"); - #line 73 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Url.Action(MVC.API.DocumentTemplate.UpdateDescription(Model.DocumentTemplate.Id))); + #line 84 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Url.Action(MVC.API.DocumentTemplate.UpdateDescription(Model.DocumentTemplate.Id))); #line default #line hidden WriteLiteral(@"', - dataType: 'json', - data: data, - success: function (d) { - if (d == 'OK') { - $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); - } else { - $ajaxLoading.hide(); - alert('Unable to update description: ' + d); - } - }, - error: function (jqXHR, textStatus, errorThrown) { - alert('Unable to update description: ' + textStatus); - $ajaxLoading.hide(); - } - }); - }); - }); - + dataType: 'json', + data: data, + success: function (d) { + if (d == 'OK') { + $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); + } else { + $ajaxLoading.hide(); + alert('Unable to update description: ' + d); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('Unable to update description: ' + textStatus); + $ajaxLoading.hide(); + } + }); + }); + }); + "); - #line 92 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - else - { - if (string.IsNullOrEmpty(Model.DocumentTemplate.Description)) - { + #line 103 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + else + { + if (string.IsNullOrEmpty(Model.DocumentTemplate.Description)) + { #line default #line hidden -WriteLiteral(" <None Specified>\r\n"); - #line 98 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - else - { - - - #line default - #line hidden - - #line 101 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Model.DocumentTemplate.Description); - - - #line default - #line hidden - - #line 101 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - - } - } - - - #line default - #line hidden -WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n " + +" \r\n " + +" \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n \r\n \r\n \r\n"); + + + #line 403 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + + #line default + #line hidden + + #line 403 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (hideAdvanced) + { + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n Show Advanced Options + + + "); - #line 331 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 419 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" } #line default #line hidden -WriteLiteral(" \r\n \r\n \r\n \r\n \r\n
Id:\r\n "); - #line 36 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Html.DisplayFor(model => model.DocumentTemplate.Id)); + #line 46 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Html.DisplayFor(model => model.DocumentTemplate.Id)); #line default #line hidden -WriteLiteral("\r\n
Stored Instance" + -"s:\r\n "); +WriteLiteral("\r\n
Statistics:\r\n
Description:\r\n " + -" "); +WriteLiteral(" Stored Instance"); - #line 48 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - if (canConfig) - { - + #line 53 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Model.StoredInstanceCount == 1 ? null : "s"); + + + #line default + #line hidden +WriteLiteral("\r\n
Description:\r\n
Always Flatten Fo" + -"rm:\r\n "); - - #line 109 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - if (canConfig) - { + } + else + { + + + #line default + #line hidden + + #line 112 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Model.DocumentTemplate.Description); #line default #line hidden -WriteLiteral("
Always Flatten Form:\r\n "); + + + #line 120 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (canConfig) + { + + + #line default + #line hidden +WriteLiteral(" \r\n"); - #line 112 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - + #line 123 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line default #line hidden - #line 112 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(AjaxHelpers.AjaxLoader()); + #line 123 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 112 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - + #line 123 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line default #line hidden -WriteLiteral(" - $(function () { - $('#DocumentTemplate_FlattenForm').click(function () { - var $this = $(this); - var $ajaxLoading = $this.next('.ajaxLoading').show(); - var data = { FlattenForm: $this.is(':checked') }; - $.getJSON('"); + $(function () { + $('#DocumentTemplate_FlattenForm').click(function () { + var $this = $(this); + var $ajaxLoading = $this.next('.ajaxLoading').show(); + var data = { FlattenForm: $this.is(':checked') }; + $.getJSON('"); - #line 119 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Url.Action(MVC.API.DocumentTemplate.UpdateFlattenForm(Model.DocumentTemplate.Id))); + #line 130 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Url.Action(MVC.API.DocumentTemplate.UpdateFlattenForm(Model.DocumentTemplate.Id))); #line default #line hidden WriteLiteral(@"', data, function (response, result) { - if (result != 'success' || response != 'OK') { - alert('Unable to change Flatten Form:\n' + response); - $ajaxLoading.hide(); - } else { - $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); - } + if (result != 'success' || response != 'OK') { + alert('Unable to change Flatten Form:\n' + response); + $ajaxLoading.hide(); + } else { + $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); + } + }); + }); }); - }); - }); - + "); - #line 130 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - else - { + #line 141 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + else + { #line default #line hidden -WriteLiteral(" \r\n"); - #line 134 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - - - #line default - #line hidden -WriteLiteral("
Scope:\r\n " + -" "); - - - #line 140 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - if (canConfig) - { - - - #line default - #line hidden - - #line 142 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Html.DropDownListFor(model => model.DocumentTemplate.Scope, Model.Scopes.ToSelectListItems(null))); - - - #line default - #line hidden - - #line 142 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - - - - #line default - #line hidden - - #line 143 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(AjaxHelpers.AjaxLoader()); - - - #line default - #line hidden - - #line 143 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - - - - #line default - #line hidden -WriteLiteral(" - $(function () { - var $scope = $('#DocumentTemplate_Scope'); - $scope.change(function () { - var $ajaxLoading = $scope.next('.ajaxLoading').show(); - var data = { Scope: $scope.val() }; - $.ajax({ - url: '"); - - - #line 151 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Url.Action(MVC.API.DocumentTemplate.UpdateScope(Model.DocumentTemplate.Id))); - - - #line default - #line hidden -WriteLiteral(@"', - dataType: 'json', - data: data, - success: function (d) { - if (d == 'OK') { - $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); - scopeChange(); - } else { - $ajaxLoading.hide(); - alert('Unable to update scope: ' + d); - } - }, - error: function (jqXHR, textStatus, errorThrown) { - alert('Unable to update scope: ' + textStatus); - $ajaxLoading.hide(); - } - }); - }); - - var $JobSubTypes = $('#Config_DocumentTemplates_JobSubTypes'); - - function scopeChange() { - if ($scope.val() == 'Job') { - $JobSubTypes.slideDown('fast'); - } else { - $JobSubTypes.slideUp('fast'); - } + #line 145 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" } - }); - -"); + + + #line default + #line hidden +WriteLiteral("
Scope:\r\n \r\n " + +"

"); + + + #line 152 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Model.DocumentTemplate.Scope); + + + #line default + #line hidden +WriteLiteral(" Scope

\r\n \r\n \r\n This template is generated from "); + + + #line 155 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Model.DocumentTemplate.Scope); + + + #line default + #line hidden +WriteLiteral("s. Any expressions within the Template PDF will be evaluated within the (Url.Action(MVC.Config.DocumentTemplate.ExpressionBrowser()) + + #line default + #line hidden +, 8607), false) +, Tuple.Create(Tuple.Create("", 8669), Tuple.Create("#", 8669), true) + + #line 155 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + , Tuple.Create(Tuple.Create("", 8670), Tuple.Create(Model.DocumentTemplate.Scope + + #line default + #line hidden +, 8670), false) +, Tuple.Create(Tuple.Create("", 8701), Tuple.Create("Scope", 8701), true) +); + +WriteLiteral(">"); + + + #line 155 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Model.DocumentTemplate.Scope); + + + #line default + #line hidden +WriteLiteral(" Scope.\r\n

\r\n \r\n " + +"
\r\n Change Scope\r\n
\r\n"); + + + #line 161 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + + #line default + #line hidden + + #line 161 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (canConfig) + { + + + #line default + #line hidden +WriteLiteral(" \r\n"); + + + #line 164 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + + #line default + #line hidden + + #line 164 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + using (Html.BeginForm(MVC.API.DocumentTemplate.UpdateScope(Model.DocumentTemplate.Id, redirect: true))) + { + + + #line default + #line hidden +WriteLiteral(" \r\n Scope: \r\n \r\n"); + + + #line 169 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + + #line default + #line hidden + + #line 169 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + foreach (var scope in Model.Scopes) + { + + + #line default + #line hidden +WriteLiteral(" (scope + + #line default + #line hidden +, 9838), false) +); + +WriteAttribute("selected", Tuple.Create(" selected=\"", 9845), Tuple.Create("\"", 9916) + + #line 171 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + , Tuple.Create(Tuple.Create("", 9856), Tuple.Create(scope == Model.DocumentTemplate.Scope ? "selected" : null + + #line default + #line hidden +, 9856), false) +); + +WriteLiteral(">"); + + + #line 171 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(scope); + + + #line default + #line hidden +WriteLiteral(" \r\n"); + + + #line 172 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n " + +" \r\n"); + + + #line 175 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n Expressions within the Template PDF may need to be updated to reflect any ch" + +"anges to the Document Template Scope.\r\n

\r" + +"\n \r\n"); #line 181 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - else - { + + + #line default + #line hidden + + #line 181 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (Model.DocumentTemplate.UsersLinkedGroup != null || Model.DocumentTemplate.DevicesLinkedGroup != null) + { #line default #line hidden -WriteLiteral("
"); +WriteLiteral(" \r\n \r\n Warning: This Document Template contains Linked Groups, these will be automatically updated to reflect the new Document Template Scope which may result in undesired behaviour. +

+
+"); - #line 184 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Model.DocumentTemplate.Scope); + #line 188 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } #line default #line hidden -WriteLiteral("\r\n"); +WriteLiteral(" \r\n"); + +WriteLiteral(" \r\n $(function () {\r\n " + +" var dialog;\r\n\r\n function showDialog() " + +"{\r\n if (dialog == null) {\r\n " + +" dialog = $(\'#Config_DocumentTemplates_Scope_Dialog" + +"\').dialog({\r\n width: 400,\r\n " + +" resizable: false,\r\n " + +" modal: true,\r\n " + +" autoOpen: false,\r\n but" + +"tons: {\r\n \'Save Changes\': fun" + +"ction () {\r\n dialog.dialo" + +"g(\'option\', \'buttons\', null);\r\n " + +" dialog.dialog(\'disable\');\r\n " + +" $(\'#Config_DocumentTemplates_Scope_Scope\').closest(\'form\').submit();\r\n" + +" },\r\n " + +" \'Cancel\': function () {\r\n " + +" dialog.dialog(\'close\');\r\n " + +" }\r\n }\r\n" + +" });\r\n " + +" }\r\n\r\n dialog.dialog(\'open\');\r\n\r\n" + +" return false;\r\n " + +" }\r\n\r\n $(\'#Config_DocumentTemplates" + +"_Scope_Button\').click(showDialog);\r\n });\r\n " + +" \r\n"); - #line 185 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } + #line 222 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } #line default #line hidden -WriteLiteral(" "); +WriteLiteral(" "); - #line 186 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - if (canConfig || (Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.Job)) - { + #line 223 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.Job) + { #line default #line hidden -WriteLiteral(" \r\n"); + +WriteLiteral("

Job Type Filters:

\r\n"); + +WriteLiteral("
PDF Template\r\n " + +"\r\n"); + +WriteLiteral(" "); - #line 304 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - - - #line default - #line hidden -WriteLiteral("
Template PDF\r\n " + -" \r\n"); - -WriteLiteral(" "); - - - #line 311 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Html.ActionLink("Download Template", MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id))); + #line 349 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Html.ActionLinkSmallButton("Download Template", MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id))); #line default @@ -881,252 +1097,416 @@ WriteLiteral(" "); WriteLiteral("\r\n"); - #line 312 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - + #line 350 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line default #line hidden - #line 312 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.Upload)) - { + #line 350 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.Upload)) + { #line default #line hidden -WriteLiteral("
\r\n"); +WriteLiteral(" Replace Template\r\n"); + +WriteLiteral(" \r\n

Select a PDF Template to upload:

\r\n " + +"
\r\n"); - #line 315 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - using (Html.BeginForm(MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id, true, null), FormMethod.Post, new { enctype = "multipart/form-data" })) - { + #line 356 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + + #line default + #line hidden + + #line 356 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + using (Html.BeginForm(MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id, true, null), FormMethod.Post, new { enctype = "multipart/form-data" })) + { #line default #line hidden -WriteLiteral(" \r\n"); - -WriteLiteral(" \r\n"); - #line 319 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } + #line 359 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } #line default #line hidden -WriteLiteral(" \r\n
\r\n"); + +WriteLiteral(" - $(function () { - var $template = $('#Template'); - $template.closest('form').submit(function () { - if ($template.val() == '') { - alert('A template file is required to upload.'); - return false; - } - }); - }); - +WriteLiteral(">\r\n $(function () {\r\n " + +" var dialog, template;\r\n\r\n function sho" + +"wDialog() {\r\n if (dialog == null) {\r\n " + +" template = $(\'#Config_DocumentTemplates_" + +"TemplatePdf_Template\');\r\n\r\n dialog = " + +"$(\'#Config_DocumentTemplates_TemplatePdf_Dialog\').dialog({\r\n " + +" width: 350,\r\n " + +" resizable: false,\r\n moda" + +"l: true,\r\n autoOpen: false,\r\n " + +" buttons: {\r\n " + +" \'Upload\': function () {\r\n " + +" if (template.val() == \'\') {\r\n " + +" alert(\'A template file is required to uplo" + +"ad.\');\r\n } else {\r\n " + +" dialog.dialog(\'option\', \'b" + +"uttons\', null);\r\n dia" + +"log.dialog(\'disable\');\r\n " + +" template.closest(\'form\').submit();\r\n " + +" }\r\n },\r\n " + +" \'Cancel\': function () {\r\n " + +" dialog.dialog(\'close\');\r\n " + +" }\r\n " + +" }\r\n });\r\n " + +" }\r\n\r\n dialog." + +"dialog(\'open\');\r\n\r\n return false;\r\n " + +" }\r\n\r\n $(\'#Confi" + +"g_DocumentTemplates_TemplatePdf_Button\').click(showDialog);\r\n " + +" });\r\n \r\n"); + + + #line 400 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + + + #line default + #line hidden +WriteLiteral("
Filter Expression" + -":\r\n "); +WriteLiteral("
\r\n \r\n \r\n

Advanced Options

\r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n
Filter Expression:\r\n " + +" "); - #line 337 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression)) - { - + #line 430 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression)) + { + #line default #line hidden - #line 339 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Html.TextBoxFor(model => model.DocumentTemplate.FilterExpression)); + #line 432 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Html.TextBoxFor(model => model.DocumentTemplate.FilterExpression)); #line default #line hidden - #line 339 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - - + #line 432 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + #line default #line hidden - #line 340 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(AjaxHelpers.AjaxRemove()); + #line 433 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(AjaxHelpers.AjaxRemove()); #line default #line hidden - #line 340 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - - + #line 433 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + + #line default #line hidden - #line 341 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(AjaxHelpers.AjaxLoader()); + #line 434 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(AjaxHelpers.AjaxLoader()); #line default #line hidden - #line 341 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - + #line 434 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line default #line hidden -WriteLiteral(" \r\n $(function () {\r\n var $FilterExpres" + -"sion = $(\'#DocumentTemplate_FilterExpression\');\r\n var $aj" + -"axLoading = $FilterExpression.nextAll(\'.ajaxLoading\').first();\r\n " + -" var $ajaxRemove = $FilterExpression.nextAll(\'.ajaxRemove\').first();\r\n " + -" $FilterExpression\r\n .waterma" + -"rk(\'Filter Expression\')\r\n .focus(function () { $F" + -"ilterExpression.select() })\r\n .keydown(function (" + -"e) {\r\n if (e.which == 13) {\r\n " + -" $(this).blur();\r\n }\r\n" + -" }).change(function () {\r\n " + -" updateFilterExpression($FilterExpression.val());\r\n " + -" });\r\n if ($FilterExpression.val() != \'\')\r\n" + -" $ajaxRemove.show();\r\n $ajaxRe" + -"move.click(function () {\r\n updateFilterExpression(\'\')" + -";\r\n $FilterExpression.val(\'\');\r\n " + -" });\r\n var updateFilterExpression = function (filterExp" + -"ression) {\r\n $ajaxLoading.show();\r\n " + -" $ajaxRemove.hide();\r\n var data = { FilterEx" + -"pression: filterExpression };\r\n $.ajax({\r\n " + -" url: \'"); +WriteLiteral(">\r\n $(function () {\r\n v" + +"ar $FilterExpression = $(\'#DocumentTemplate_FilterExpression\');\r\n " + +" var $ajaxLoading = $FilterExpression.nextAll(\'.ajaxLoading\').fi" + +"rst();\r\n var $ajaxRemove = $FilterExpression.next" + +"All(\'.ajaxRemove\').first();\r\n $FilterExpression\r\n" + +" .watermark(\'Filter Expression\')\r\n " + +" .focus(function () { $FilterExpression.select()" + +" })\r\n .keydown(function (e) {\r\n " + +" if (e.which == 13) {\r\n " + +" $(this).blur();\r\n " + +" }\r\n }).change(function () {\r\n " + +" updateFilterExpression($FilterExpression.va" + +"l());\r\n });\r\n " + +" if ($FilterExpression.val() != \'\')\r\n $aja" + +"xRemove.show();\r\n $ajaxRemove.click(function () {" + +"\r\n updateFilterExpression(\'\');\r\n " + +" $FilterExpression.val(\'\');\r\n " + +" });\r\n var updateFilterExpression = function (f" + +"ilterExpression) {\r\n $ajaxLoading.show();\r\n " + +" $ajaxRemove.hide();\r\n " + +" var data = { FilterExpression: filterExpression };\r\n " + +" $.ajax({\r\n url: \'"); - #line 368 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Url.Action(MVC.API.DocumentTemplate.UpdateFilterExpression(Model.DocumentTemplate.Id))); + #line 461 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Url.Action(MVC.API.DocumentTemplate.UpdateFilterExpression(Model.DocumentTemplate.Id))); #line default #line hidden WriteLiteral(@"', - dataType: 'json', - data: data, - success: function (d) { - if (d == 'OK') { - $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); - if (data.FilterExpression != '') - $ajaxRemove.fadeIn('fast'); - } else { - $ajaxLoading.hide(); - alert('Unable to update filter expression: ' + d); - } - }, - error: function (jqXHR, textStatus, errorThrown) { - alert('Unable to update filter expression: ' + textStatus); - $ajaxLoading.hide(); - } + dataType: 'json', + data: data, + success: function (d) { + if (d == 'OK') { + $ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow'); + if (data.FilterExpression != '') + $ajaxRemove.fadeIn('fast'); + } else { + $ajaxLoading.hide(); + alert('Unable to update filter expression: ' + d); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('Unable to update filter expression: ' + textStatus); + $ajaxLoading.hide(); + } + }); + }; }); - }; - }); - + "); - #line 389 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - else - { - if (string.IsNullOrWhiteSpace(Model.DocumentTemplate.FilterExpression)) - { + #line 482 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + else + { + if (string.IsNullOrWhiteSpace(Model.DocumentTemplate.FilterExpression)) + { #line default #line hidden -WriteLiteral(" <None Specified>\r\n"); - #line 395 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - else - { + #line 488 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + else + { #line default #line hidden -WriteLiteral(" \r\n"); -WriteLiteral(" "); +WriteLiteral(" "); - #line 399 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - Write(Model.DocumentTemplate.FilterExpression); + #line 492 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Model.DocumentTemplate.FilterExpression); #line default #line hidden -WriteLiteral("\r\n \r\n"); +WriteLiteral("\r\n \r\n"); - #line 401 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" - } - } + #line 494 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + } + } #line default #line hidden -WriteLiteral("
\r\n\r\n

Template Expressions<" + -"/h2>\r\n"); +WriteLiteral("

Linked Groups:\r\n
\r\n

\r\n \r\n

Template Expressions

\r\n"); + +WriteLiteral(" "); + + + #line 531 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + Write(Html.Partial(MVC.Config.DocumentTemplate.Views._ExpressionsTable, Model.TemplateExpressions)); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n\r\n\r\n"); - #line 447 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 572 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" #line default #line hidden - #line 447 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 572 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" if (Authorization.Has(Claims.Config.Show)) { @@ -1196,14 +1576,14 @@ WriteLiteral(">\r\n"); #line default #line hidden - #line 449 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 574 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" Write(Html.ActionLinkButton("Expression Browser", MVC.Config.DocumentTemplate.ExpressionBrowser())); #line default #line hidden - #line 449 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 574 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" } @@ -1213,7 +1593,7 @@ WriteLiteral(">\r\n"); WriteLiteral(" "); - #line 451 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 576 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" if (canBulkGenerate) { @@ -1236,16 +1616,16 @@ WriteLiteral(" id=\"dialogBulkGenerate\""); WriteLiteral(" class=\"hiddenDialog\""); -WriteAttribute("title", Tuple.Create(" title=\"", 23229), Tuple.Create("\"", 23280) -, Tuple.Create(Tuple.Create("", 23237), Tuple.Create("Bulk", 23237), true) -, Tuple.Create(Tuple.Create(" ", 23241), Tuple.Create("Generate:", 23242), true) +WriteAttribute("title", Tuple.Create(" title=\"", 33937), Tuple.Create("\"", 33988) +, Tuple.Create(Tuple.Create("", 33945), Tuple.Create("Bulk", 33945), true) +, Tuple.Create(Tuple.Create(" ", 33949), Tuple.Create("Generate:", 33950), true) - #line 454 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" -, Tuple.Create(Tuple.Create(" ", 23251), Tuple.Create(Model.DocumentTemplate.Id + #line 579 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" +, Tuple.Create(Tuple.Create(" ", 33959), Tuple.Create(Model.DocumentTemplate.Id #line default #line hidden -, 23252), false) +, 33960), false) ); WriteLiteral(">\r\n \r\n \r\n \r\n"); - #line 466 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 591 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" #line default #line hidden - #line 466 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 591 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" using (Html.BeginForm(MVC.API.DocumentTemplate.BulkGenerate(Model.DocumentTemplate.Id), FormMethod.Post)) { @@ -1312,7 +1692,7 @@ WriteLiteral(" data-val-required=\"Identifiers are required\""); WriteLiteral(">\r\n"); - #line 470 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 595 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" } @@ -1321,7 +1701,7 @@ WriteLiteral(">\r\n"); WriteLiteral(" \r\n"); - #line 472 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 597 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" @@ -1356,7 +1736,7 @@ WriteLiteral(" \r\n"); - #line 524 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 649 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" } @@ -1396,7 +1776,7 @@ WriteLiteral("\\\\rsmith\');\r\n break;\r\n WriteLiteral(" "); - #line 525 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 650 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" if (Authorization.Has(Claims.Config.DocumentTemplate.Delete)) { @@ -1404,14 +1784,14 @@ WriteLiteral(" "); #line default #line hidden - #line 527 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 652 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" Write(Html.ActionLinkButton("Delete", MVC.API.DocumentTemplate.Delete(Model.DocumentTemplate.Id, true), "buttonDelete")); #line default #line hidden - #line 527 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" + #line 652 "..\..\Areas\Config\Views\DocumentTemplate\Show.cshtml" } diff --git a/Disco.Web/Areas/Config/Views/JobQueue/Index.cshtml b/Disco.Web/Areas/Config/Views/JobQueue/Index.cshtml index 4278130d..77470af3 100644 --- a/Disco.Web/Areas/Config/Views/JobQueue/Index.cshtml +++ b/Disco.Web/Areas/Config/Views/JobQueue/Index.cshtml @@ -1,6 +1,6 @@ @model Disco.Web.Areas.Config.Models.JobQueue.IndexModel @{ - Authorization.RequireAll(Claims.Config.JobQueue.Create, Claims.Config.JobQueue.Configure); + Authorization.Require(Claims.Config.JobQueue.Show); ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "Job Queues", MVC.Config.JobQueue.Index(null)); }
@@ -54,7 +54,10 @@ } } -
- @Html.ActionLinkButton("Create Job Queue", MVC.Config.JobQueue.Create()) -
+ @if (Authorization.Has(Claims.Config.JobQueue.Create)) + { +
+ @Html.ActionLinkButton("Create Job Queue", MVC.Config.JobQueue.Create()) +
+ }
diff --git a/Disco.Web/Areas/Config/Views/JobQueue/Index.generated.cs b/Disco.Web/Areas/Config/Views/JobQueue/Index.generated.cs index 92699fbe..42b1e41f 100644 --- a/Disco.Web/Areas/Config/Views/JobQueue/Index.generated.cs +++ b/Disco.Web/Areas/Config/Views/JobQueue/Index.generated.cs @@ -47,7 +47,7 @@ namespace Disco.Web.Areas.Config.Views.JobQueue #line 2 "..\..\Areas\Config\Views\JobQueue\Index.cshtml" - Authorization.RequireAll(Claims.Config.JobQueue.Create, Claims.Config.JobQueue.Configure); + Authorization.Require(Claims.Config.JobQueue.Show); ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "Job Queues", MVC.Config.JobQueue.Index(null)); @@ -114,37 +114,37 @@ WriteLiteral(">\r\n \r\n Name\r\n #line hidden WriteLiteral(" \r\n \r\n (Url.Action(MVC.Config.JobQueue.Index(item.JobQueue.Id)) +, Tuple.Create(Tuple.Create("", 858), Tuple.Create(Url.Action(MVC.Config.JobQueue.Index(item.JobQueue.Id)) #line default #line hidden -, 897), false) +, 858), false) ); WriteLiteral(">\r\n (item.JobQueue.Icon +, Tuple.Create(Tuple.Create("", 962), Tuple.Create(item.JobQueue.Icon #line default #line hidden -, 1001), false) -, Tuple.Create(Tuple.Create(" ", 1022), Tuple.Create("fa-lg", 1023), true) -, Tuple.Create(Tuple.Create(" ", 1028), Tuple.Create("d-", 1029), true) +, 962), false) +, Tuple.Create(Tuple.Create(" ", 983), Tuple.Create("fa-lg", 984), true) +, Tuple.Create(Tuple.Create(" ", 989), Tuple.Create("d-", 990), true) #line 27 "..\..\Areas\Config\Views\JobQueue\Index.cshtml" -, Tuple.Create(Tuple.Create("", 1031), Tuple.Create(item.JobQueue.IconColour +, Tuple.Create(Tuple.Create("", 992), Tuple.Create(item.JobQueue.IconColour #line default #line hidden -, 1031), false) +, 992), false) ); WriteLiteral(">\r\n"); @@ -201,27 +201,27 @@ WriteLiteral("><none>
\r\n"); #line hidden WriteLiteral(" \r\n \r\n (item.JobQueue.Priority.ToString().ToLower() +, Tuple.Create(Tuple.Create("", 1596), Tuple.Create(item.JobQueue.Priority.ToString().ToLower() #line default #line hidden -, 1635), false) +, 1596), false) ); -WriteAttribute("title", Tuple.Create(" title=\"", 1682), Tuple.Create("\"", 1735) +WriteAttribute("title", Tuple.Create(" title=\"", 1643), Tuple.Create("\"", 1696) #line 41 "..\..\Areas\Config\Views\JobQueue\Index.cshtml" - , Tuple.Create(Tuple.Create("", 1690), Tuple.Create(item.JobQueue.Priority.ToString() + , Tuple.Create(Tuple.Create("", 1651), Tuple.Create(item.JobQueue.Priority.ToString() #line default #line hidden -, 1690), false) -, Tuple.Create(Tuple.Create(" ", 1726), Tuple.Create("Priority", 1727), true) +, 1651), false) +, Tuple.Create(Tuple.Create(" ", 1687), Tuple.Create("Priority", 1688), true) ); WriteLiteral(">\r\n \r\n \r\n"); @@ -288,22 +288,41 @@ WriteLiteral(" \r\n"); #line default #line hidden -WriteLiteral("