diff --git a/Disco.BI/BI/Extensions/JobQueueActionExtensions.cs b/Disco.BI/BI/Extensions/JobQueueActionExtensions.cs
index bd08f5a0..6568d53c 100644
--- a/Disco.BI/BI/Extensions/JobQueueActionExtensions.cs
+++ b/Disco.BI/BI/Extensions/JobQueueActionExtensions.cs
@@ -4,10 +4,7 @@ using Disco.Services.Authorization;
using Disco.Services.Jobs.JobQueues;
using Disco.Services.Users;
using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Disco.BI.Extensions
{
@@ -146,7 +143,7 @@ namespace Disco.BI.Extensions
public static void OnRemove(this JobQueueJob jqj, User Technician, string Comment)
{
if (!jqj.CanRemove())
- throw new InvalidOperationException("Removing job from queue is Denied");
+ throw new InvalidOperationException("Removing job from queue is denied");
jqj.RemovedDate = DateTime.Now;
jqj.RemovedUserId = Technician.UserId;
@@ -173,7 +170,7 @@ namespace Disco.BI.Extensions
return false;
// Already in Queue?
- if (j.JobQueues.Count(jjq => !jjq.RemovedDate.HasValue && jjq.JobQueueId == jq.Id) > 0)
+ if (j.JobQueues.Any(jjq => !jjq.RemovedDate.HasValue && jjq.JobQueueId == jq.Id))
return false;
// Can add ANY queue
@@ -191,7 +188,7 @@ namespace Disco.BI.Extensions
public static JobQueueJob OnAddQueue(this Job j, DiscoDataContext Database, JobQueue jq, User Technician, string Comment, DateTime? SLAExpires, JobQueuePriority Priority)
{
if (!j.CanAddQueue(jq))
- throw new InvalidOperationException("Adding job to queue is Denied");
+ throw new InvalidOperationException("Adding job to queue is denied");
if (SLAExpires.HasValue && SLAExpires.Value < DateTime.Now)
throw new ArgumentException("The SLA Date must be greater than the current time", "SLAExpires");
diff --git a/Disco.BI/BI/Extensions/UserFlagActionExtensions.cs b/Disco.BI/BI/Extensions/UserFlagActionExtensions.cs
new file mode 100644
index 00000000..aae008bb
--- /dev/null
+++ b/Disco.BI/BI/Extensions/UserFlagActionExtensions.cs
@@ -0,0 +1,85 @@
+using Disco.Data.Repository;
+using Disco.Models.Repository;
+using Disco.Services.Authorization;
+using Disco.Services.Users;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Disco.BI.Extensions
+{
+ public static class UserFlagActionExtensions
+ {
+
+ #region Edit Comments
+ public static bool CanEditComments(this UserFlagAssignment fa)
+ {
+ return UserService.CurrentAuthorization.Has(Claims.User.Actions.EditFlags);
+ }
+ public static void OnEditComments(this UserFlagAssignment fa, string Comments)
+ {
+ if (!fa.CanEditComments())
+ throw new InvalidOperationException("Editing comments for user flags is denied");
+
+ fa.Comments = string.IsNullOrWhiteSpace(Comments) ? null : Comments.Trim();
+ }
+ #endregion
+
+ #region Remove
+ public static bool CanRemove(this UserFlagAssignment fa)
+ {
+ if (fa.RemovedDate.HasValue)
+ return false;
+
+ return UserService.CurrentAuthorization.Has(Claims.User.Actions.RemoveFlags);
+ }
+ public static void OnRemove(this UserFlagAssignment fa, User Technician)
+ {
+ if (!fa.CanRemove())
+ throw new InvalidOperationException("Removing user flags is denied");
+
+ fa.RemovedDate = DateTime.Now;
+ fa.RemovedUserId = Technician.UserId;
+ }
+ #endregion
+
+ #region Add
+ public static bool CanAddUserFlags(this User u)
+ {
+ return UserService.CurrentAuthorization.Has(Claims.User.Actions.AddFlags);
+ }
+ public static bool CanAddUserFlag(this User u, UserFlag flag)
+ {
+ // Shortcut
+ if (!u.CanAddUserFlags())
+ return false;
+
+ // Already has User Flag?
+ if (u.UserFlagAssignments.Any(fa => !fa.RemovedDate.HasValue && fa.UserFlagId == flag.Id))
+ return false;
+
+ return true;
+ }
+ public static UserFlagAssignment OnAddUserFlag(this User u, DiscoDataContext Database, UserFlag flag, User Technician, string Comments)
+ {
+ if (!u.CanAddUserFlag(flag))
+ throw new InvalidOperationException("Adding user flag is denied");
+
+ var fa = new UserFlagAssignment()
+ {
+ UserFlagId = flag.Id,
+ UserId = u.UserId,
+ AddedDate = DateTime.Now,
+ AddedUserId = Technician.UserId,
+ Comments = string.IsNullOrWhiteSpace(Comments) ? null : Comments.Trim()
+ };
+
+ Database.UserFlagAssignments.Add(fa);
+ return fa;
+ }
+ #endregion
+
+ }
+}
diff --git a/Disco.BI/Disco.BI.csproj b/Disco.BI/Disco.BI.csproj
index d1ba0482..b0ee39a7 100644
--- a/Disco.BI/Disco.BI.csproj
+++ b/Disco.BI/Disco.BI.csproj
@@ -112,6 +112,7 @@
+
@@ -203,7 +204,7 @@
-
+
diff --git a/Disco.Data/Disco.Data.csproj b/Disco.Data/Disco.Data.csproj
index 3965d443..7ffc950b 100644
--- a/Disco.Data/Disco.Data.csproj
+++ b/Disco.Data/Disco.Data.csproj
@@ -139,6 +139,10 @@
201404080227546_DBv13.cs
+
+
+ 201406090652547_DBv14.cs
+
@@ -193,6 +197,9 @@
201404080227546_DBv13.cs
+
+ 201406090652547_DBv14.cs
+
ResXFileCodeGenerator
Resources.Designer.cs
@@ -206,7 +213,7 @@
-
+
diff --git a/Disco.Data/Migrations/201406090652547_DBv14.Designer.cs b/Disco.Data/Migrations/201406090652547_DBv14.Designer.cs
new file mode 100644
index 00000000..9bd62ca6
--- /dev/null
+++ b/Disco.Data/Migrations/201406090652547_DBv14.Designer.cs
@@ -0,0 +1,27 @@
+//
+namespace Disco.Data.Migrations
+{
+ using System.Data.Entity.Migrations;
+ using System.Data.Entity.Migrations.Infrastructure;
+ using System.Resources;
+
+ public sealed partial class DBv14 : IMigrationMetadata
+ {
+ private readonly ResourceManager Resources = new ResourceManager(typeof(DBv14));
+
+ string IMigrationMetadata.Id
+ {
+ get { return "201406090652547_DBv14"; }
+ }
+
+ string IMigrationMetadata.Source
+ {
+ get { return null; }
+ }
+
+ string IMigrationMetadata.Target
+ {
+ get { return Resources.GetString("Target"); }
+ }
+ }
+}
diff --git a/Disco.Data/Migrations/201406090652547_DBv14.cs b/Disco.Data/Migrations/201406090652547_DBv14.cs
new file mode 100644
index 00000000..20b1d472
--- /dev/null
+++ b/Disco.Data/Migrations/201406090652547_DBv14.cs
@@ -0,0 +1,61 @@
+namespace Disco.Data.Migrations
+{
+ using System;
+ using System.Data.Entity.Migrations;
+
+ public partial class DBv14 : DbMigration
+ {
+ public override void Up()
+ {
+ CreateTable(
+ "dbo.UserFlagAssignments",
+ c => new
+ {
+ Id = c.Int(nullable: false, identity: true),
+ UserFlagId = c.Int(nullable: false),
+ UserId = c.String(nullable: false, maxLength: 50),
+ AddedDate = c.DateTime(nullable: false),
+ AddedUserId = c.String(nullable: false, maxLength: 50),
+ RemovedDate = c.DateTime(),
+ RemovedUserId = c.String(maxLength: 50),
+ Comments = c.String(),
+ })
+ .PrimaryKey(t => t.Id)
+ .ForeignKey("dbo.UserFlags", t => t.UserFlagId)
+ .ForeignKey("dbo.Users", t => t.UserId)
+ .ForeignKey("dbo.Users", t => t.AddedUserId)
+ .ForeignKey("dbo.Users", t => t.RemovedUserId)
+ .Index(t => t.UserFlagId)
+ .Index(t => t.UserId)
+ .Index(t => t.AddedUserId)
+ .Index(t => t.RemovedUserId);
+
+ CreateTable(
+ "dbo.UserFlags",
+ c => new
+ {
+ Id = c.Int(nullable: false, identity: true),
+ Name = c.String(nullable: false, maxLength: 100),
+ Description = c.String(maxLength: 500),
+ Icon = c.String(nullable: false, maxLength: 25),
+ IconColour = c.String(nullable: false, maxLength: 10),
+ })
+ .PrimaryKey(t => t.Id);
+
+ }
+
+ public override void Down()
+ {
+ DropIndex("dbo.UserFlagAssignments", new[] { "RemovedUserId" });
+ DropIndex("dbo.UserFlagAssignments", new[] { "AddedUserId" });
+ DropIndex("dbo.UserFlagAssignments", new[] { "UserId" });
+ DropIndex("dbo.UserFlagAssignments", new[] { "UserFlagId" });
+ DropForeignKey("dbo.UserFlagAssignments", "RemovedUserId", "dbo.Users");
+ DropForeignKey("dbo.UserFlagAssignments", "AddedUserId", "dbo.Users");
+ DropForeignKey("dbo.UserFlagAssignments", "UserId", "dbo.Users");
+ DropForeignKey("dbo.UserFlagAssignments", "UserFlagId", "dbo.UserFlags");
+ DropTable("dbo.UserFlags");
+ DropTable("dbo.UserFlagAssignments");
+ }
+ }
+}
diff --git a/Disco.Data/Migrations/201406090652547_DBv14.resx b/Disco.Data/Migrations/201406090652547_DBv14.resx
new file mode 100644
index 00000000..cc734d75
--- /dev/null
+++ b/Disco.Data/Migrations/201406090652547_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.Data/Repository/DiscoDataContext.cs b/Disco.Data/Repository/DiscoDataContext.cs
index aa3f09b9..41d26cc0 100644
--- a/Disco.Data/Repository/DiscoDataContext.cs
+++ b/Disco.Data/Repository/DiscoDataContext.cs
@@ -23,6 +23,8 @@ namespace Disco.Data.Repository
public virtual DbSet Users { get; set; }
public virtual DbSet UserAttachments { get; set; }
+ public virtual DbSet UserFlags { get; set; }
+ public virtual DbSet UserFlagAssignments { get; set; }
public virtual DbSet AuthorizationRoles { get; set; }
public virtual DbSet DeviceUserAssignments { get; set; }
diff --git a/Disco.Data/Repository/Monitor/RepositoryMonitor.cs b/Disco.Data/Repository/Monitor/RepositoryMonitor.cs
index dd6c1c8a..57a494cf 100644
--- a/Disco.Data/Repository/Monitor/RepositoryMonitor.cs
+++ b/Disco.Data/Repository/Monitor/RepositoryMonitor.cs
@@ -177,6 +177,11 @@ namespace Disco.Data.Repository.Monitor
{
key["UserId"] = ((UserAttachment)entryState.Entity).UserId;
}
+ if (entryState.Entity is UserFlagAssignment)
+ {
+ key["UserFlagId"] = ((UserFlagAssignment)entryState.Entity).UserFlagId;
+ key["UserId"] = ((UserFlagAssignment)entryState.Entity).UserId;
+ }
return key;
}
diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj
index c5951838..08c29a56 100644
--- a/Disco.Models/Disco.Models.csproj
+++ b/Disco.Models/Disco.Models.csproj
@@ -47,6 +47,8 @@
+
+
@@ -154,6 +156,9 @@
+
+
+
@@ -176,7 +181,7 @@
-
+
diff --git a/Disco.Models/Repository/User/Flag/UserFlag.cs b/Disco.Models/Repository/User/Flag/UserFlag.cs
new file mode 100644
index 00000000..e7143b29
--- /dev/null
+++ b/Disco.Models/Repository/User/Flag/UserFlag.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace Disco.Models.Repository
+{
+ public class UserFlag
+ {
+ [Key]
+ public int Id { get; set; }
+
+ [Required, StringLength(100)]
+ public string Name { get; set; }
+
+ [StringLength(500), DataType(DataType.MultilineText)]
+ public string Description { get; set; }
+
+ [Required, StringLength(25)]
+ public string Icon { get; set; }
+ [Required, StringLength(10)]
+ public string IconColour { get; set; }
+
+ public virtual IList UserFlagAssignments { get; set; }
+
+ public override string ToString()
+ {
+ return this.Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Disco.Models/Repository/User/Flag/UserFlagAssignment.cs b/Disco.Models/Repository/User/Flag/UserFlagAssignment.cs
new file mode 100644
index 00000000..8e37a622
--- /dev/null
+++ b/Disco.Models/Repository/User/Flag/UserFlagAssignment.cs
@@ -0,0 +1,36 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Disco.Models.Repository
+{
+ public class UserFlagAssignment
+ {
+ [Key]
+ public int Id { get; set; }
+ [Required]
+ public int UserFlagId { get; set; }
+ [Required]
+ public string UserId { get; set; }
+
+ [Required]
+ public DateTime AddedDate { get; set; }
+ [Required]
+ public string AddedUserId { get; set; }
+ public DateTime? RemovedDate { get; set; }
+ public string RemovedUserId { get; set; }
+
+ public string Comments { get; set; }
+
+ [ForeignKey("UserFlagId"), InverseProperty("UserFlagAssignments")]
+ public virtual UserFlag UserFlag { get; set; }
+
+ [ForeignKey("UserId"), InverseProperty("UserFlagAssignments")]
+ public virtual User User { get; set; }
+
+ [ForeignKey("AddedUserId")]
+ public virtual User AddedUser { get; set; }
+ [ForeignKey("RemovedUserId")]
+ public virtual User RemovedUser { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Disco.Models/Repository/User/User.cs b/Disco.Models/Repository/User/User.cs
index fd79cc7f..4e7a4cb8 100644
--- a/Disco.Models/Repository/User/User.cs
+++ b/Disco.Models/Repository/User/User.cs
@@ -29,6 +29,7 @@ namespace Disco.Models.Repository
public virtual IList DeviceUserAssignments { get; set; }
[InverseProperty("UserId")]
public virtual IList Jobs { get; set; }
+ public virtual IList UserFlagAssignments { get; set; }
[NotMapped, Obsolete("Should be using Combined Domain\\User format - UserId")]
public string Id
diff --git a/Disco.Models/Services/Searching/UserSearchResultItem.cs b/Disco.Models/Services/Searching/UserSearchResultItem.cs
index 5fab3b33..9290c6a6 100644
--- a/Disco.Models/Services/Searching/UserSearchResultItem.cs
+++ b/Disco.Models/Services/Searching/UserSearchResultItem.cs
@@ -1,4 +1,5 @@
-using System;
+using Disco.Models.Repository;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -17,16 +18,22 @@ namespace Disco.Models.Services.Searching
}
public string Id { get; set; }
+ public string FriendlyId { get; set; }
public string Type { get { return type; } }
public string Description { get { return string.Format("{0} ({1})", this.DisplayName, this.Id); } }
public string[] ScoreValues { get { return LazyScoreValue.Value; } }
- public int AssignedDevicesCount { get; set; }
public string DisplayName { get; set; }
public string GivenName { get; set; }
- public int JobCount { get; set; }
public string Surname { get; set; }
+ public int AssignedDevicesCount { get; set; }
+
+ public int JobCount { get; set; }
+ public int JobCountOpen { get; set; }
+
+ public IList UserFlagAssignments { get; set; }
+
private string[] BuildScoreValues()
{
return new string[] {
diff --git a/Disco.Models/UI/Config/JobQueue/ConfigJobQueueShowModel.cs b/Disco.Models/UI/Config/JobQueue/ConfigJobQueueShowModel.cs
index 013ac969..64e76751 100644
--- a/Disco.Models/UI/Config/JobQueue/ConfigJobQueueShowModel.cs
+++ b/Disco.Models/UI/Config/JobQueue/ConfigJobQueueShowModel.cs
@@ -1,9 +1,5 @@
using Disco.Models.Services.Jobs.JobQueues;
-using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Disco.Models.UI.Config.JobQueue
{
@@ -14,6 +10,9 @@ namespace Disco.Models.UI.Config.JobQueue
int OpenJobCount { get; set; }
int TotalJobCount { get; set; }
+ IEnumerable> Icons { get; set; }
+ IEnumerable> ThemeColours { get; set; }
+
List JobTypes { get; set; }
bool CanDelete { get; set; }
diff --git a/Disco.Models/UI/Config/UserFlag/ConfigUserFlagCreateModel.cs b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagCreateModel.cs
new file mode 100644
index 00000000..28217cb1
--- /dev/null
+++ b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagCreateModel.cs
@@ -0,0 +1,7 @@
+namespace Disco.Models.UI.Config.UserFlag
+{
+ public interface ConfigUserFlagCreateModel : BaseUIModel
+ {
+ Repository.UserFlag UserFlag { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Disco.Models/UI/Config/UserFlag/ConfigUserFlagIndexModel.cs b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagIndexModel.cs
new file mode 100644
index 00000000..549e9a6e
--- /dev/null
+++ b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagIndexModel.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Disco.Models.UI.Config.UserFlag
+{
+ public interface ConfigUserFlagIndexModel : BaseUIModel
+ {
+ List UserFlags { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Disco.Models/UI/Config/UserFlag/ConfigUserFlagShowModel.cs b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagShowModel.cs
new file mode 100644
index 00000000..af582107
--- /dev/null
+++ b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagShowModel.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace Disco.Models.UI.Config.UserFlag
+{
+ public interface ConfigUserFlagShowModel : BaseUIModel
+ {
+ Repository.UserFlag UserFlag { get; set; }
+
+ int CurrentAssignmentCount { get; set; }
+ int TotalAssignmentCount { get; set; }
+
+ IEnumerable> Icons { get; set; }
+ IEnumerable> ThemeColours { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Disco.Models/UI/User/UserShowModel.cs b/Disco.Models/UI/User/UserShowModel.cs
index 5c257253..fbd7c607 100644
--- a/Disco.Models/UI/User/UserShowModel.cs
+++ b/Disco.Models/UI/User/UserShowModel.cs
@@ -1,4 +1,5 @@
-using Disco.Models.Services.Authorization;
+using Disco.Models.Repository;
+using Disco.Models.Services.Authorization;
using Disco.Models.Services.Jobs.JobLists;
using System.Collections.Generic;
@@ -8,7 +9,10 @@ namespace Disco.Models.UI.User
{
Disco.Models.Repository.User User { get; set; }
JobTableModel Jobs { get; set; }
- List DocumentTemplates { get; set; }
+ List DocumentTemplates { get; set; }
+
+ List AvailableUserFlags { get; set; }
+
IAuthorizationToken AuthorizationToken { get; set; }
IClaimNavigatorItem ClaimNavigator { get; set; }
}
diff --git a/Disco.Services/Authorization/Claims.cs b/Disco.Services/Authorization/Claims.cs
index a29dee2d..af35dcc8 100644
--- a/Disco.Services/Authorization/Claims.cs
+++ b/Disco.Services/Authorization/Claims.cs
@@ -73,6 +73,10 @@ namespace Disco.Services.Authorization
{ "Config.JobQueue.Create", new Tuple, Action, string, string, bool>(c => c.Config.JobQueue.Create, (c, v) => c.Config.JobQueue.Create = v, "Create Job Queues", "Can create job queues", false) },
{ "Config.JobQueue.Delete", new Tuple, Action, string, string, bool>(c => c.Config.JobQueue.Delete, (c, v) => c.Config.JobQueue.Delete = v, "Delete Job Queues", "Can delete job queues", false) },
{ "Config.JobQueue.Show", new Tuple, Action, string, string, bool>(c => c.Config.JobQueue.Show, (c, v) => c.Config.JobQueue.Show = v, "Show Job Queues", "Can show job queues", false) },
+ { "Config.UserFlag.Configure", new Tuple, Action, string, string, bool>(c => c.Config.UserFlag.Configure, (c, v) => c.Config.UserFlag.Configure = v, "Configure User Flags", "Can configure user flags", false) },
+ { "Config.UserFlag.Create", new Tuple, Action, string, string, bool>(c => c.Config.UserFlag.Create, (c, v) => c.Config.UserFlag.Create = v, "Create User Flags", "Can create user flags", false) },
+ { "Config.UserFlag.Delete", new Tuple, Action, string, string, bool>(c => c.Config.UserFlag.Delete, (c, v) => c.Config.UserFlag.Delete = v, "Delete User Flags", "Can delete user flags", false) },
+ { "Config.UserFlag.Show", new Tuple, Action, string, string, bool>(c => c.Config.UserFlag.Show, (c, v) => c.Config.UserFlag.Show = v, "Show User Flags", "Can show user flags", false) },
{ "Config.Show", new Tuple, Action, string, string, bool>(c => c.Config.Show, (c, v) => c.Config.Show = v, "Show Configuration", "Can show the configuration menu", false) },
{ "Job.Lists.AllOpen", new Tuple, Action, string, string, bool>(c => c.Job.Lists.AllOpen, (c, v) => c.Job.Lists.AllOpen = v, "All Open List", "Can show list", false) },
{ "Job.Lists.AwaitingFinanceAgreementBreach", new Tuple, Action, string, string, bool>(c => c.Job.Lists.AwaitingFinanceAgreementBreach, (c, v) => c.Job.Lists.AwaitingFinanceAgreementBreach = v, "Awaiting Finance Agreement Breach List", "Can show list (NOTE: Requires Awaiting Finance List)", false) },
@@ -198,9 +202,12 @@ namespace Disco.Services.Authorization
{ "Device.Show", new Tuple, Action, string, string, bool>(c => c.Device.Show, (c, v) => c.Device.Show = v, "Show Devices", "Can show devices", false) },
{ "Device.ShowJobs", new Tuple, Action, string, string, bool>(c => c.Device.ShowJobs, (c, v) => c.Device.ShowJobs = v, "Show Devices Jobs", "Can show jobs associated with devices", false) },
{ "User.Actions.AddAttachments", new Tuple, Action, string, string, bool>(c => c.User.Actions.AddAttachments, (c, v) => c.User.Actions.AddAttachments = v, "Add Attachments", "Can add attachments to users", false) },
+ { "User.Actions.AddFlags", new Tuple, Action, string, string, bool>(c => c.User.Actions.AddFlags, (c, v) => c.User.Actions.AddFlags = v, "Add User Flags", "Can add user flags", false) },
+ { "User.Actions.EditFlags", new Tuple, Action, string, string, bool>(c => c.User.Actions.EditFlags, (c, v) => c.User.Actions.EditFlags = v, "Edit User Flags", "Can edit user flags", false) },
{ "User.Actions.GenerateDocuments", new Tuple, Action, string, string, bool>(c => c.User.Actions.GenerateDocuments, (c, v) => c.User.Actions.GenerateDocuments = v, "Generate Documents", "Can generate documents for users", false) },
{ "User.Actions.RemoveAnyAttachments", new Tuple, Action, string, string, bool>(c => c.User.Actions.RemoveAnyAttachments, (c, v) => c.User.Actions.RemoveAnyAttachments = v, "Remove Any Attachments", "Can remove any attachments from users", false) },
{ "User.Actions.RemoveOwnAttachments", new Tuple, Action, string, string, bool>(c => c.User.Actions.RemoveOwnAttachments, (c, v) => c.User.Actions.RemoveOwnAttachments = v, "Remove Own Attachments", "Can remove own attachments from users", false) },
+ { "User.Actions.RemoveFlags", new Tuple, Action, string, string, bool>(c => c.User.Actions.RemoveFlags, (c, v) => c.User.Actions.RemoveFlags = v, "Remove User Flags", "Can remove user flags", false) },
{ "User.Search", new Tuple, Action, string, string, bool>(c => c.User.Search, (c, v) => c.User.Search = v, "Search Users", "Can search users", false) },
{ "User.ShowAttachments", new Tuple, Action, string, string, bool>(c => c.User.ShowAttachments, (c, v) => c.User.ShowAttachments = v, "Show Attachments", "Can show user attachments", false) },
{ "User.ShowAssignmentHistory", new Tuple, Action, string, string, bool>(c => c.User.ShowAssignmentHistory, (c, v) => c.User.ShowAssignmentHistory = v, "Show Device Assignment History", "Can show the device assignment history for users", false) },
@@ -208,6 +215,7 @@ namespace Disco.Services.Authorization
{ "User.Show", new Tuple, Action, string, string, bool>(c => c.User.Show, (c, v) => c.User.Show = v, "Show Users", "Can show users", false) },
{ "User.ShowAuthorization", new Tuple, Action, string, string, bool>(c => c.User.ShowAuthorization, (c, v) => c.User.ShowAuthorization = v, "Show Users Authorization", "Can show authorization permissions associated with users", false) },
{ "User.ShowDetails", new Tuple, Action, string, string, bool>(c => c.User.ShowDetails, (c, v) => c.User.ShowDetails = v, "Show Users Details", "Can show users contact and personal details", false) },
+ { "User.ShowFlagAssignments", new Tuple, Action, string, string, bool>(c => c.User.ShowFlagAssignments, (c, v) => c.User.ShowFlagAssignments = v, "Show Users Flag Assignments", "Can show flags associated with users", false) },
{ "User.ShowJobs", new Tuple, Action, string, string, bool>(c => c.User.ShowJobs, (c, v) => c.User.ShowJobs = v, "Show Users Jobs", "Can show jobs associated with users", false) },
{ "ComputerAccount", new Tuple, Action, string, string, bool>(c => c.ComputerAccount, (c, v) => c.ComputerAccount = v, "Computer Account", "Represents a computer account", true) },
{ "DiscoAdminAccount", new Tuple, Action, string, string, bool>(c => c.DiscoAdminAccount, (c, v) => c.DiscoAdminAccount = v, "Disco Administrator Account", "Represents a Disco Administrator account", true) }
@@ -291,6 +299,12 @@ namespace Disco.Services.Authorization
new ClaimNavigatorItem("Config.System.ConfigureProxy", false),
new ClaimNavigatorItem("Config.System.Show", false)
}),
+ new ClaimNavigatorItem("Config.UserFlag", "User Flags", "Permissions related to User Flags", false, new List() {
+ new ClaimNavigatorItem("Config.UserFlag.Configure", false),
+ new ClaimNavigatorItem("Config.UserFlag.Create", false),
+ new ClaimNavigatorItem("Config.UserFlag.Delete", false),
+ new ClaimNavigatorItem("Config.UserFlag.Show", false)
+ }),
new ClaimNavigatorItem("Config.Show", false)
}),
new ClaimNavigatorItem("Job", "Job", "Permissions related to Jobs", false, new List() {
@@ -441,9 +455,12 @@ namespace Disco.Services.Authorization
new ClaimNavigatorItem("User", "User", "Permissions related to Users", false, new List() {
new ClaimNavigatorItem("User.Actions", "Actions", "Permissions related to User Actions", false, new List() {
new ClaimNavigatorItem("User.Actions.AddAttachments", false),
+ new ClaimNavigatorItem("User.Actions.AddFlags", false),
+ new ClaimNavigatorItem("User.Actions.EditFlags", false),
new ClaimNavigatorItem("User.Actions.GenerateDocuments", false),
new ClaimNavigatorItem("User.Actions.RemoveAnyAttachments", false),
- new ClaimNavigatorItem("User.Actions.RemoveOwnAttachments", false)
+ new ClaimNavigatorItem("User.Actions.RemoveOwnAttachments", false),
+ new ClaimNavigatorItem("User.Actions.RemoveFlags", false)
}),
new ClaimNavigatorItem("User.Search", false),
new ClaimNavigatorItem("User.ShowAttachments", false),
@@ -452,6 +469,7 @@ namespace Disco.Services.Authorization
new ClaimNavigatorItem("User.Show", false),
new ClaimNavigatorItem("User.ShowAuthorization", false),
new ClaimNavigatorItem("User.ShowDetails", false),
+ new ClaimNavigatorItem("User.ShowFlagAssignments", false),
new ClaimNavigatorItem("User.ShowJobs", false)
}),
new ClaimNavigatorItem("ComputerAccount", true),
@@ -567,6 +585,10 @@ namespace Disco.Services.Authorization
c.Config.JobQueue.Create = true;
c.Config.JobQueue.Delete = true;
c.Config.JobQueue.Show = true;
+ c.Config.UserFlag.Configure = true;
+ c.Config.UserFlag.Create = true;
+ c.Config.UserFlag.Delete = true;
+ c.Config.UserFlag.Show = true;
c.Config.Show = true;
c.Job.Lists.AllOpen = true;
c.Job.Lists.AwaitingFinanceAgreementBreach = true;
@@ -692,9 +714,12 @@ namespace Disco.Services.Authorization
c.Device.Show = true;
c.Device.ShowJobs = true;
c.User.Actions.AddAttachments = true;
+ c.User.Actions.AddFlags = true;
+ c.User.Actions.EditFlags = true;
c.User.Actions.GenerateDocuments = true;
c.User.Actions.RemoveAnyAttachments = true;
c.User.Actions.RemoveOwnAttachments = true;
+ c.User.Actions.RemoveFlags = true;
c.User.Search = true;
c.User.ShowAttachments = true;
c.User.ShowAssignmentHistory = true;
@@ -702,6 +727,7 @@ namespace Disco.Services.Authorization
c.User.Show = true;
c.User.ShowAuthorization = true;
c.User.ShowDetails = true;
+ c.User.ShowFlagAssignments = true;
c.User.ShowJobs = true;
c.DiscoAdminAccount = true;
#endregion
@@ -1051,6 +1077,33 @@ namespace Disco.Services.Authorization
public const string Show = "Config.JobQueue.Show";
}
+ /// User Flags
+ /// Permissions related to User Flags
+ ///
+ public static class UserFlag
+ {
+
+ /// Configure User Flags
+ /// Can configure user flags
+ ///
+ public const string Configure = "Config.UserFlag.Configure";
+
+ /// Create User Flags
+ /// Can create user flags
+ ///
+ public const string Create = "Config.UserFlag.Create";
+
+ /// Delete User Flags
+ /// Can delete user flags
+ ///
+ public const string Delete = "Config.UserFlag.Delete";
+
+ /// Show User Flags
+ /// Can show user flags
+ ///
+ public const string Show = "Config.UserFlag.Show";
+ }
+
/// Show Configuration
/// Can show the configuration menu
///
@@ -1766,6 +1819,16 @@ namespace Disco.Services.Authorization
///
public const string AddAttachments = "User.Actions.AddAttachments";
+ /// Add User Flags
+ /// Can add user flags
+ ///
+ public const string AddFlags = "User.Actions.AddFlags";
+
+ /// Edit User Flags
+ /// Can edit user flags
+ ///
+ public const string EditFlags = "User.Actions.EditFlags";
+
/// Generate Documents
/// Can generate documents for users
///
@@ -1780,6 +1843,11 @@ namespace Disco.Services.Authorization
/// Can remove own attachments from users
///
public const string RemoveOwnAttachments = "User.Actions.RemoveOwnAttachments";
+
+ /// Remove User Flags
+ /// Can remove user flags
+ ///
+ public const string RemoveFlags = "User.Actions.RemoveFlags";
}
/// Search Users
@@ -1817,6 +1885,11 @@ namespace Disco.Services.Authorization
///
public const string ShowDetails = "User.ShowDetails";
+ /// Show Users Flag Assignments
+ /// Can show flags associated with users
+ ///
+ public const string ShowFlagAssignments = "User.ShowFlagAssignments";
+
/// Show Users Jobs
/// Can show jobs associated with users
///
diff --git a/Disco.Services/Authorization/Roles/ClaimGroups/Configuration/ConfigClaims.cs b/Disco.Services/Authorization/Roles/ClaimGroups/Configuration/ConfigClaims.cs
index 5d95c2f5..ccf68ce9 100644
--- a/Disco.Services/Authorization/Roles/ClaimGroups/Configuration/ConfigClaims.cs
+++ b/Disco.Services/Authorization/Roles/ClaimGroups/Configuration/ConfigClaims.cs
@@ -10,6 +10,7 @@ using Disco.Services.Authorization.Roles.ClaimGroups.Configuration.Logging;
using Disco.Services.Authorization.Roles.ClaimGroups.Configuration.Origanisation;
using Disco.Services.Authorization.Roles.ClaimGroups.Configuration.Plugin;
using Disco.Services.Authorization.Roles.ClaimGroups.Configuration.System;
+using Disco.Services.Authorization.Roles.ClaimGroups.Configuration.UserFlag;
namespace Disco.Services.Authorization.Roles.ClaimGroups.Configuration
{
@@ -30,6 +31,7 @@ namespace Disco.Services.Authorization.Roles.ClaimGroups.Configuration
this.Organisation = new OrganisationClaims();
this.JobPreferences = new JobPreferencesClaims();
this.JobQueue = new JobQueueClaims();
+ this.UserFlag = new UserFlagClaims();
}
[ClaimDetails("Show Configuration", "Can show the configuration menu")]
@@ -58,5 +60,7 @@ namespace Disco.Services.Authorization.Roles.ClaimGroups.Configuration
public JobPreferencesClaims JobPreferences { get; set; }
public JobQueueClaims JobQueue { get; set; }
+
+ public UserFlagClaims UserFlag { get; set; }
}
}
diff --git a/Disco.Services/Authorization/Roles/ClaimGroups/Configuration/UserFlag/UserFlagClaims.cs b/Disco.Services/Authorization/Roles/ClaimGroups/Configuration/UserFlag/UserFlagClaims.cs
new file mode 100644
index 00000000..3ee39a51
--- /dev/null
+++ b/Disco.Services/Authorization/Roles/ClaimGroups/Configuration/UserFlag/UserFlagClaims.cs
@@ -0,0 +1,18 @@
+namespace Disco.Services.Authorization.Roles.ClaimGroups.Configuration.UserFlag
+{
+ [ClaimDetails("User Flags", "Permissions related to User Flags")]
+ public class UserFlagClaims : BaseRoleClaimGroup
+ {
+ [ClaimDetails("Configure User Flags", "Can configure user flags")]
+ public bool Configure { get; set; }
+
+ [ClaimDetails("Create User Flags", "Can create user flags")]
+ public bool Create { get; set; }
+
+ [ClaimDetails("Delete User Flags", "Can delete user flags")]
+ public bool Delete { get; set; }
+
+ [ClaimDetails("Show User Flags", "Can show user flags")]
+ public bool Show { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Disco.Services/Authorization/Roles/ClaimGroups/User/UserActionsClaims.cs b/Disco.Services/Authorization/Roles/ClaimGroups/User/UserActionsClaims.cs
index 913fdf96..76ed7684 100644
--- a/Disco.Services/Authorization/Roles/ClaimGroups/User/UserActionsClaims.cs
+++ b/Disco.Services/Authorization/Roles/ClaimGroups/User/UserActionsClaims.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Disco.Services.Authorization.Roles.ClaimGroups.User
+namespace Disco.Services.Authorization.Roles.ClaimGroups.User
{
[ClaimDetails("Actions", "Permissions related to User Actions")]
public class UserActionsClaims : BaseRoleClaimGroup
@@ -18,5 +12,12 @@ namespace Disco.Services.Authorization.Roles.ClaimGroups.User
[ClaimDetails("Generate Documents", "Can generate documents for users")]
public bool GenerateDocuments { get; set; }
+
+ [ClaimDetails("Add User Flags", "Can add user flags")]
+ public bool AddFlags { get; set; }
+ [ClaimDetails("Remove User Flags", "Can remove user flags")]
+ public bool RemoveFlags { get; set; }
+ [ClaimDetails("Edit User Flags", "Can edit user flags")]
+ public bool EditFlags { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/Disco.Services/Authorization/Roles/ClaimGroups/User/UserClaims.cs b/Disco.Services/Authorization/Roles/ClaimGroups/User/UserClaims.cs
index 42160e5c..f76927b5 100644
--- a/Disco.Services/Authorization/Roles/ClaimGroups/User/UserClaims.cs
+++ b/Disco.Services/Authorization/Roles/ClaimGroups/User/UserClaims.cs
@@ -35,6 +35,9 @@ namespace Disco.Services.Authorization.Roles.ClaimGroups.User
[ClaimDetails("Show Users Jobs", "Can show jobs associated with users")]
public bool ShowJobs { get; set; }
+ [ClaimDetails("Show Users Flag Assignments", "Can show flags associated with users")]
+ public bool ShowFlagAssignments { get; set; }
+
[ClaimDetails("Show Users Authorization", "Can show authorization permissions associated with users")]
public bool ShowAuthorization { get; set; }
diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj
index cd8649c4..dd239a4f 100644
--- a/Disco.Services/Disco.Services.csproj
+++ b/Disco.Services/Disco.Services.csproj
@@ -154,6 +154,7 @@
+
@@ -206,6 +207,7 @@
+
@@ -297,6 +299,9 @@
+
+
+
@@ -343,7 +348,7 @@
-
+
diff --git a/Disco.Services/Extensions/UIHelpers.cs b/Disco.Services/Extensions/UIHelpers.cs
new file mode 100644
index 00000000..fda11315
--- /dev/null
+++ b/Disco.Services/Extensions/UIHelpers.cs
@@ -0,0 +1,226 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Disco.Services.Extensions
+{
+ public static class UIHelpers
+ {
+ ///
+ /// FontAwesome Category Icons
+ ///
+ public static ReadOnlyCollection> Icons { get; private set; }
+ ///
+ /// User-selectable Colour Themes
+ ///
+ public static ReadOnlyCollection> ThemeColours { get; private set; }
+
+ ///
+ /// Returns a randomly selected Icon using .
+ ///
+ public static string RandomIcon()
+ {
+ return RandomIcon(null);
+ }
+ ///
+ /// Returns a randomly selected Icon using .
+ ///
+ /// A list of Icons which will be ignored (if all are excluded, a random one will be returned)
+ public static string RandomIcon(IEnumerable Except)
+ {
+ var rnd = new Random();
+ if (Except != null)
+ {
+ var availableIcons = UIHelpers.Icons.Select(i => i.Key).Except(Except).ToList();
+ if (availableIcons.Count > 0)
+ return availableIcons[rnd.Next(availableIcons.Count - 1)];
+ }
+ return UIHelpers.Icons[rnd.Next(UIHelpers.Icons.Count - 1)].Key;
+ }
+
+ ///
+ /// Returns a randomly selected Theme Colour using .
+ ///
+ public static string RandomThemeColour()
+ {
+ return RandomThemeColour(null);
+ }
+ ///
+ /// Returns a randomly selected Theme Colour using .
+ ///
+ /// A list of Theme Colours which will be ignored (if all are excluded, a random one will be returned)
+ public static string RandomThemeColour(IEnumerable Except)
+ {
+ var rnd = new Random();
+ if (Except != null)
+ {
+ var availableColours = UIHelpers.ThemeColours.Select(i => i.Key).Except(Except).ToList();
+ if (availableColours.Count > 0)
+ return availableColours[rnd.Next(availableColours.Count - 1)];
+ }
+ return UIHelpers.ThemeColours[rnd.Next(UIHelpers.ThemeColours.Count - 1)].Key;
+ }
+
+ static UIHelpers()
+ {
+ // Icons
+ Icons = new List>(){
+ new KeyValuePair("ambulance" , "Ambulance"),
+ new KeyValuePair("anchor" , "Anchor"),
+ new KeyValuePair("android" , "Android"),
+ new KeyValuePair("apple" , "Apple"),
+ new KeyValuePair("archive" , "Archive"),
+ new KeyValuePair("arrow-circle-down" , "Arrow Circle Down"),
+ new KeyValuePair("arrow-circle-left" , "Arrow Circle Left"),
+ new KeyValuePair("arrow-circle-right" , "Arrow Circle Right"),
+ new KeyValuePair("arrow-circle-up" , "Arrow Circle Up"),
+ new KeyValuePair("asterisk" , "Asterisk"),
+ new KeyValuePair("ban" , "Ban"),
+ new KeyValuePair("beer" , "Beer"),
+ new KeyValuePair("bell" , "Bell"),
+ new KeyValuePair("bolt" , "Bolt"),
+ new KeyValuePair("bomb" , "Bomb"),
+ new KeyValuePair("book" , "Book"),
+ new KeyValuePair("bookmark" , "Bookmark"),
+ new KeyValuePair("briefcase" , "Briefcase"),
+ new KeyValuePair("bug" , "Bug"),
+ new KeyValuePair("building-o" , "Building"),
+ new KeyValuePair("bullhorn" , "Bullhorn"),
+ new KeyValuePair("bullseye" , "Bullseye"),
+ new KeyValuePair("cab" , "Cab"),
+ new KeyValuePair("calendar" , "Calendar"),
+ new KeyValuePair("calendar-o" , "Calendar"),
+ new KeyValuePair("car" , "Car"),
+ new KeyValuePair("check-circle" , "Check Circle"),
+ new KeyValuePair("child" , "Child"),
+ new KeyValuePair("clock-o" , "Clock"),
+ new KeyValuePair("cloud" , "Cloud"),
+ new KeyValuePair("coffee" , "Coffee"),
+ new KeyValuePair("comments" , "Comments"),
+ new KeyValuePair("compass" , "Compass"),
+ new KeyValuePair("credit-card" , "Credit Card"),
+ new KeyValuePair("crosshairs" , "Crosshairs"),
+ new KeyValuePair("cube" , "Cube"),
+ new KeyValuePair("cubes" , "Cubes"),
+ new KeyValuePair("desktop" , "Desktop"),
+ new KeyValuePair("dollar" , "Dollar"),
+ new KeyValuePair("dot-circle-o" , "Dot Circle"),
+ new KeyValuePair("envelope" , "Envelope"),
+ new KeyValuePair("exclamation" , "Exclamation"),
+ new KeyValuePair("eye" , "Eye"),
+ new KeyValuePair("fax" , "Fax"),
+ new KeyValuePair("female" , "Female"),
+ new KeyValuePair("fighter-jet" , "Fighter Jet"),
+ new KeyValuePair("film" , "Film"),
+ new KeyValuePair("filter" , "Filter"),
+ new KeyValuePair("fire" , "Fire"),
+ new KeyValuePair("fire-extinguisher" , "Fire Extinguisher"),
+ new KeyValuePair("flask" , "Flask"),
+ new KeyValuePair("frown-o" , "Frown"),
+ new KeyValuePair("gamepad" , "Gamepad"),
+ new KeyValuePair("gift" , "Gift"),
+ new KeyValuePair("glass" , "Glass"),
+ new KeyValuePair("globe" , "Globe"),
+ new KeyValuePair("graduation-cap" , "Graduation Cap"),
+ new KeyValuePair("hand-o-down" , "Hand Down"),
+ new KeyValuePair("hand-o-left" , "Hand Left"),
+ new KeyValuePair("hand-o-right" , "Hand Right"),
+ new KeyValuePair("hand-o-up" , "Hand Up"),
+ new KeyValuePair("hdd-o" , "Hdd"),
+ new KeyValuePair("heart" , "Heart"),
+ new KeyValuePair("history" , "History"),
+ new KeyValuePair("home" , "Home"),
+ new KeyValuePair("info" , "Info"),
+ new KeyValuePair("key" , "Key"),
+ new KeyValuePair("keyboard-o" , "Keyboard"),
+ new KeyValuePair("language" , "Language"),
+ new KeyValuePair("laptop" , "Laptop"),
+ new KeyValuePair("leaf" , "Leaf"),
+ new KeyValuePair("legal" , "Legal"),
+ new KeyValuePair("life-ring" , "Life Ring"),
+ new KeyValuePair("lightbulb-o" , "Lightbulb"),
+ new KeyValuePair("linux" , "Linux"),
+ new KeyValuePair("location-arrow" , "Location Arrow"),
+ new KeyValuePair("magnet" , "Magnet"),
+ new KeyValuePair("male" , "Male"),
+ new KeyValuePair("map-marker" , "Map Marker"),
+ new KeyValuePair("medkit" , "Medkit"),
+ new KeyValuePair("meh-o" , "Meh"),
+ new KeyValuePair("microphone" , "Microphone"),
+ new KeyValuePair("microphone-slash" , "Microphone Slash"),
+ new KeyValuePair("minus-circle" , "Minus Circle"),
+ new KeyValuePair("mobile" , "Mobile"),
+ new KeyValuePair("money" , "Money"),
+ new KeyValuePair("moon-o" , "Moon"),
+ new KeyValuePair("music" , "Music"),
+ new KeyValuePair("paper-plane" , "Paper Plane"),
+ new KeyValuePair("paperclip" , "Paperclip"),
+ new KeyValuePair("paw" , "Paw"),
+ new KeyValuePair("pencil" , "Pencil"),
+ new KeyValuePair("phone" , "Phone"),
+ new KeyValuePair("picture-o" , "Picture"),
+ new KeyValuePair("plane" , "Plane"),
+ new KeyValuePair("power-off" , "Power Off"),
+ new KeyValuePair("print" , "Print"),
+ new KeyValuePair("puzzle-piece" , "Puzzle Piece"),
+ new KeyValuePair("question" , "Question"),
+ new KeyValuePair("question-circle" , "Question Circle"),
+ new KeyValuePair("random" , "Random"),
+ new KeyValuePair("recycle" , "Recycle"),
+ new KeyValuePair("retweet" , "Retweet"),
+ new KeyValuePair("road" , "Road"),
+ new KeyValuePair("rocket" , "Rocket"),
+ new KeyValuePair("shield" , "Shield"),
+ new KeyValuePair("shopping-cart" , "Shopping Cart"),
+ new KeyValuePair("smile-o" , "Smile"),
+ new KeyValuePair("space-shuttle" , "Space Shuttle"),
+ new KeyValuePair("star" , "Star"),
+ new KeyValuePair("suitcase" , "Suitcase"),
+ new KeyValuePair("sun-o" , "Sun"),
+ new KeyValuePair("tablet" , "Tablet"),
+ new KeyValuePair("tachometer" , "Tachometer"),
+ new KeyValuePair("tasks" , "Tasks"),
+ new KeyValuePair("thumbs-down" , "Thumbs Down"),
+ new KeyValuePair("thumbs-o-down" , "Thumbs Down"),
+ new KeyValuePair("thumbs-o-up" , "Thumbs Up"),
+ new KeyValuePair("thumbs-up" , "Thumbs Up"),
+ new KeyValuePair("thumb-tack" , "Thumb Tack"),
+ new KeyValuePair("trash-o" , "Trash"),
+ new KeyValuePair("trophy" , "Trophy"),
+ new KeyValuePair("truck" , "Truck"),
+ new KeyValuePair("umbrella" , "Umbrella"),
+ new KeyValuePair("university" , "University"),
+ new KeyValuePair("wheelchair" , "Wheelchair"),
+ new KeyValuePair("windows" , "Windows"),
+ new KeyValuePair("wrench" , "Wrench")
+ }.AsReadOnly();
+
+ // Icon Colours
+ ThemeColours = new List>(){
+ new KeyValuePair("lime" , "Lime"),
+ new KeyValuePair("green" , "Green"),
+ new KeyValuePair("emerald" , "Emerald"),
+ new KeyValuePair("teal" , "Teal"),
+ new KeyValuePair("cyan" , "Cyan"),
+ new KeyValuePair("cobalt" , "Cobalt"),
+ new KeyValuePair("indigo" , "Indigo"),
+ new KeyValuePair("violet" , "Violet"),
+ new KeyValuePair("pink" , "Pink"),
+ new KeyValuePair("magenta" , "Magenta"),
+ new KeyValuePair("crimson" , "Crimson"),
+ new KeyValuePair("red" , "Red"),
+ new KeyValuePair("orange" , "Orange"),
+ new KeyValuePair("amber" , "Amber"),
+ new KeyValuePair("yellow" , "Yellow"),
+ new KeyValuePair("brown" , "Brown"),
+ new KeyValuePair("olive" , "Olive"),
+ new KeyValuePair("steel" , "Steel"),
+ new KeyValuePair("mauve" , "Mauve"),
+ new KeyValuePair("sienna" , "Sienna")
+ }.AsReadOnly();
+ }
+ }
+}
diff --git a/Disco.Services/Jobs/JobQueues/Cache.cs b/Disco.Services/Jobs/JobQueues/Cache.cs
index 09a056de..3c4fb0a3 100644
--- a/Disco.Services/Jobs/JobQueues/Cache.cs
+++ b/Disco.Services/Jobs/JobQueues/Cache.cs
@@ -15,8 +15,6 @@ namespace Disco.Services.Jobs.JobQueues
private ConcurrentDictionary _Cache;
private Dictionary> _SubjectCache;
- private ReadOnlyCollection> _Icons;
- private ReadOnlyCollection> _IconColourCache;
private ReadOnlyCollection> _SlaOptions;
public Cache(DiscoDataContext Database)
@@ -29,20 +27,6 @@ namespace Disco.Services.Jobs.JobQueues
// Queues from Database
var queues = Database.JobQueues.ToList();
- // Default System Queue
- //var defaultQueue = new JobQueue()
- //{
- // Id = 0,
- // Name = "Default Queue",
- // Description = "Default system queue for orphaned jobs",
- // Icon = "question-circle",
- // IconColour = "F0A30A",
- // DefaultSLAExpiry = null,
- // Priority = JobQueuePriority.Normal,
- // SubjectIds = null
- //};
- //queues.Add(defaultQueue);
-
// Add Queues to In-Memory Cache
this._Cache = new ConcurrentDictionary(queues.Select(q => new KeyValuePair(q.Id, JobQueueToken.FromJobQueue(q))));
@@ -50,168 +34,6 @@ namespace Disco.Services.Jobs.JobQueues
CalculateSubjectCache();
#region Predefined Options
- // Icons
- if (this._Icons == null)
- {
- this._Icons = new List>(){
- new KeyValuePair("ambulance" , "Ambulance"),
- new KeyValuePair("anchor" , "Anchor"),
- new KeyValuePair("android" , "Android"),
- new KeyValuePair("apple" , "Apple"),
- new KeyValuePair("archive" , "Archive"),
- new KeyValuePair("arrow-circle-down" , "Arrow Circle Down"),
- new KeyValuePair