feature: custom details first-class
custom details (such as those from the UserDetails plugin) can now be more deeply integrated throughtout the system
This commit is contained in:
@@ -150,6 +150,15 @@ namespace Disco.Data.Configuration
|
||||
return System.IO.Path.Combine(DataStoreLocation, @"PluginPackages\");
|
||||
}
|
||||
}
|
||||
|
||||
public string PluginUserPhotosLocation
|
||||
=> Path.Combine(DataStoreLocation, @"PluginUserPhotos\");
|
||||
|
||||
public DateTime PluginDetailsCacheExpiration
|
||||
{
|
||||
get => Get(DateTime.MinValue);
|
||||
set => Set(value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Organisation Details
|
||||
@@ -292,6 +301,11 @@ namespace Disco.Data.Configuration
|
||||
get => Get<string>(null);
|
||||
set => Set(value);
|
||||
}
|
||||
public string EmailReplyToAddress
|
||||
{
|
||||
get => Get<string>(null);
|
||||
set => Set(value);
|
||||
}
|
||||
public string EmailUsername
|
||||
{
|
||||
get => Get<string>(null);
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Disco.Data.Repository
|
||||
public virtual DbSet<DocumentTemplate> DocumentTemplates { get; set; }
|
||||
|
||||
public virtual DbSet<User> Users { get; set; }
|
||||
public virtual DbSet<UserDetail> UserDetails { get; set; }
|
||||
public virtual DbSet<UserAttachment> UserAttachments { get; set; }
|
||||
public virtual DbSet<UserFlag> UserFlags { get; set; }
|
||||
public virtual DbSet<UserFlagAssignment> UserFlagAssignments { get; set; }
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
<Compile Include="Services\Jobs\Noticeboards\IHeldDeviceItem.cs" />
|
||||
<Compile Include="Services\Messaging\Email.cs" />
|
||||
<Compile Include="Services\Messaging\EmailAttachment.cs" />
|
||||
<Compile Include="Services\Plugins\Details\DetailsResult.cs" />
|
||||
<Compile Include="Services\Searching\DeviceSearchResultItem.cs" />
|
||||
<Compile Include="Services\Searching\ISearchResultItem.cs" />
|
||||
<Compile Include="Services\Searching\JobSearchResultItem.cs" />
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Disco.Models.Services.Plugins.Details
|
||||
{
|
||||
public class DetailsResult
|
||||
{
|
||||
public DateTime GatheredOn { get; private set; }
|
||||
public DateTime ExpiresOn { get; private set; }
|
||||
public Dictionary<string, string> Details { get; }
|
||||
|
||||
public bool SetExpiration(DateTime expireOn)
|
||||
{
|
||||
if (ExpiresOn > expireOn)
|
||||
{
|
||||
// only set the expiration if it is sooner
|
||||
ExpiresOn = expireOn;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public DetailsResult()
|
||||
{
|
||||
GatheredOn = DateTime.Now;
|
||||
ExpiresOn = DateTime.Now.AddDays(7);
|
||||
Details = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public DetailsResult(DateTime gatheredOn, DateTime expiresOn, Dictionary<string, string> details)
|
||||
{
|
||||
GatheredOn = gatheredOn;
|
||||
ExpiresOn = expiresOn;
|
||||
Details = details ?? new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Disco.Models.BI.Config;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Disco.Models.UI.Device
|
||||
@@ -20,5 +21,8 @@ namespace Disco.Models.UI.Device
|
||||
|
||||
List<Repository.DocumentTemplate> DocumentTemplates { get; set; }
|
||||
List<DocumentTemplatePackage> DocumentTemplatePackages { get; set; }
|
||||
DetailsResult DeviceDetails { get; set; }
|
||||
DetailsResult AssignedUserDetails { get; set; }
|
||||
bool HasAssignedUserPhoto { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Job;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -17,5 +18,8 @@ namespace Disco.Models.UI.Job
|
||||
|
||||
LocationModes LocationMode { get; set; }
|
||||
List<JobLocationReference> LocationOptions { get; set; }
|
||||
DetailsResult UserDetails { get; set; }
|
||||
bool HasUserPhoto { get; set; }
|
||||
DetailsResult DeviceDetails { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Disco.Models.Services.Authorization;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Disco.Models.UI.User
|
||||
@@ -17,5 +18,8 @@ namespace Disco.Models.UI.User
|
||||
|
||||
IAuthorizationToken AuthorizationToken { get; set; }
|
||||
IClaimNavigatorItem ClaimNavigator { get; set; }
|
||||
DetailsResult UserDetails { get; set; }
|
||||
bool HasUserPhoto { get; set; }
|
||||
Dictionary<string, DetailsResult> AssignedDevicesDetails { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -322,6 +322,7 @@
|
||||
<Compile Include="Expressions\Extensions\ImageResultImplementations\FileMontageImageExpressionResult.cs" />
|
||||
<Compile Include="Expressions\Extensions\UserExt.cs" />
|
||||
<Compile Include="Expressions\IExpressionPart.cs" />
|
||||
<Compile Include="Expressions\LazyDictionary.cs" />
|
||||
<Compile Include="Expressions\TextExpressionPart.cs" />
|
||||
<Compile Include="Extensions\DateTimeExtensions.cs" />
|
||||
<Compile Include="Extensions\EnumerableExtensions.cs" />
|
||||
@@ -394,6 +395,10 @@
|
||||
<Compile Include="Plugins\Features\CertificateAuthorityProvider\CertificateAuthorityProviderFeature.cs" />
|
||||
<Compile Include="Plugins\Features\CertificateProvider\ProvisionPersonalCertificateResult.cs" />
|
||||
<Compile Include="Plugins\Features\CertificateAuthorityProvider\ProvisionAuthorityCertificatesResult.cs" />
|
||||
<Compile Include="Plugins\Features\DetailsProvider\DetailsProviderExtensions.cs" />
|
||||
<Compile Include="Plugins\Features\DetailsProvider\DetailsProviderFeature.cs" />
|
||||
<Compile Include="Plugins\Features\DetailsProvider\DetailsProviderService.cs" />
|
||||
<Compile Include="Plugins\Features\DocumentHandlerProvider\DocumentHandlerProviderFeature.cs" />
|
||||
<Compile Include="Plugins\Features\WirelessProfileProvider\ProvisionWirelessProfilesResult.cs" />
|
||||
<Compile Include="Plugins\Features\WirelessProfileProvider\WirelessProfile.cs" />
|
||||
<Compile Include="Plugins\Features\WirelessProfileProvider\WirelessProfileTransformation.cs" />
|
||||
|
||||
@@ -76,6 +76,36 @@ namespace Disco.Services
|
||||
return destination;
|
||||
}
|
||||
|
||||
public static Bitmap ResizeImage(this Image Source, int MaxHeight, Brush BackgroundColor = null)
|
||||
{
|
||||
// Determine Width
|
||||
int Height = (Source.Height > MaxHeight) ?
|
||||
MaxHeight :
|
||||
Source.Height;
|
||||
|
||||
int Width = (Source.Height > Height) ?
|
||||
(int)(((float)Height / Source.Height) * Source.Width) :
|
||||
Source.Width;
|
||||
|
||||
Bitmap destination = new Bitmap(Width, Height);
|
||||
destination.SetResolution(72, 72);
|
||||
using (Graphics destinationGraphics = Graphics.FromImage(destination))
|
||||
{
|
||||
destinationGraphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
destinationGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
destinationGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
if (BackgroundColor != null)
|
||||
destinationGraphics.FillRectangle(BackgroundColor, destinationGraphics.VisibleClipBounds);
|
||||
|
||||
float ratio = Math.Min((float)(destination.Width) / (float)(Source.Width), (float)(destination.Height) / (float)(Source.Height));
|
||||
|
||||
destinationGraphics.DrawImageResized(Source, ratio);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
public static RectangleF CalculateResize(int SourceWidth, int SourceHeight, int TargetWidth, int TargetHeight, out float scaleRatio)
|
||||
{
|
||||
scaleRatio = Math.Min((float)(TargetWidth) / SourceWidth, (float)(TargetHeight) / SourceHeight);
|
||||
@@ -119,6 +149,33 @@ namespace Disco.Services
|
||||
graphics.DrawImage(SourceImage, resizeBounds, new RectangleF(0, 0, SourceImage.Width, SourceImage.Height), GraphicsUnit.Pixel);
|
||||
}
|
||||
|
||||
public static void DrawImageResized(this Graphics graphics, Image SourceImage, float? Scale = null, float LocationX = -1, float LocationY = -1)
|
||||
{
|
||||
RectangleF clipBounds = graphics.VisibleClipBounds;
|
||||
if (Scale == null) // Calculate Scale
|
||||
Scale = Math.Min(clipBounds.Width / SourceImage.Width, clipBounds.Height / SourceImage.Height);
|
||||
float newWidth = SourceImage.Width * Scale.Value;
|
||||
float newHeight = SourceImage.Height * Scale.Value;
|
||||
float newLeft = LocationX;
|
||||
float newTop = LocationY;
|
||||
|
||||
if (newLeft < 0 || newTop < 0)
|
||||
{
|
||||
if (newWidth < clipBounds.Width)
|
||||
newLeft = (clipBounds.Width - newWidth) / 2;
|
||||
else
|
||||
newLeft = 0;
|
||||
if (newHeight < clipBounds.Height)
|
||||
newTop = (clipBounds.Height - newHeight) / 2;
|
||||
else
|
||||
newTop = 0;
|
||||
}
|
||||
newLeft += clipBounds.Left;
|
||||
newTop += clipBounds.Top;
|
||||
|
||||
graphics.DrawImage(SourceImage, new RectangleF(newLeft, newTop, newWidth, newHeight), new RectangleF(0, 0, SourceImage.Width, SourceImage.Height), GraphicsUnit.Pixel);
|
||||
}
|
||||
|
||||
public static void DrawImageResized(this Graphics graphics, Image SourceImage, float Scale, float LocationX, float LocationY)
|
||||
{
|
||||
RectangleF clipBounds = graphics.VisibleClipBounds;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using Disco.Models.UI.Device;
|
||||
using Disco.Models.UI.Job;
|
||||
using Disco.Models.UI.User;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.DetailsProvider
|
||||
{
|
||||
public static class DetailsProviderExtensions
|
||||
{
|
||||
|
||||
public static void PopulateDetails(this UserShowModel model, DiscoDataContext database)
|
||||
{
|
||||
var service = new DetailsProviderService(database);
|
||||
|
||||
model.UserDetails = service.GetDetails(model.User);
|
||||
model.HasUserPhoto = service.HasUserPhoto(model.User);
|
||||
|
||||
var currentAssignments = model.User.CurrentDeviceUserAssignments();
|
||||
if (currentAssignments.Count > 0)
|
||||
{
|
||||
model.AssignedDevicesDetails = new Dictionary<string, DetailsResult>(currentAssignments.Count);
|
||||
|
||||
foreach (var device in currentAssignments)
|
||||
{
|
||||
model.AssignedDevicesDetails[device.DeviceSerialNumber] = service.GetDetails(device.Device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PopulateDetails(this DeviceShowModel model, DiscoDataContext database)
|
||||
{
|
||||
var service = new DetailsProviderService(database);
|
||||
|
||||
model.DeviceDetails = service.GetDetails(model.Device);
|
||||
|
||||
if (model.Device.AssignedUser != null)
|
||||
{
|
||||
model.AssignedUserDetails = service.GetDetails(model.Device.AssignedUser);
|
||||
model.HasAssignedUserPhoto = service.HasUserPhoto(model.Device.AssignedUser);
|
||||
}
|
||||
}
|
||||
|
||||
public static void PopulateDetails(this JobShowModel model, DiscoDataContext database)
|
||||
{
|
||||
var service = new DetailsProviderService(database);
|
||||
|
||||
if (model.Job.Device != null)
|
||||
model.DeviceDetails = service.GetDetails(model.Job.Device);
|
||||
|
||||
if (model.Job.User != null)
|
||||
{
|
||||
model.UserDetails = service.GetDetails(model.Job.User);
|
||||
model.HasUserPhoto = service.HasUserPhoto(model.Job.User);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using System;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.DetailsProvider
|
||||
{
|
||||
[PluginFeatureCategory(DisplayName = "Detail Providers")]
|
||||
public abstract class DetailsProviderFeature : PluginFeature
|
||||
{
|
||||
public abstract DetailsResult GetDetails(DiscoDataContext database, User user, DateTime? cacheTimestamp);
|
||||
public abstract DetailsResult GetDetails(DiscoDataContext database, Device device, DateTime? cacheTimestamp);
|
||||
public abstract byte[] GetUserPhoto(DiscoDataContext database, User user, DateTime? cacheTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Users;
|
||||
using Exceptionless.Json;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.DetailsProvider
|
||||
{
|
||||
public class DetailsProviderService
|
||||
{
|
||||
private const string DetailsScope = "Details";
|
||||
private readonly DiscoDataContext database;
|
||||
|
||||
public DetailsProviderService(DiscoDataContext database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public bool HasUserPhoto(User user)
|
||||
{
|
||||
var cachePath = GetUserPhotoCachePath(user);
|
||||
if (File.Exists(cachePath))
|
||||
return true;
|
||||
|
||||
// slow-path: this should only happen once,
|
||||
// the first time, before we cache
|
||||
var photo = GetUserPhoto(user);
|
||||
|
||||
return photo != null;
|
||||
}
|
||||
|
||||
public byte[] GetUserPhoto(User user)
|
||||
{
|
||||
var cachePath = GetUserPhotoCachePath(user);
|
||||
var cacheAge = default(DateTime?);
|
||||
if (File.Exists(cachePath))
|
||||
cacheAge = File.GetLastWriteTime(cachePath);
|
||||
|
||||
var features = Plugins.GetPluginFeatures(typeof(DetailsProviderFeature));
|
||||
|
||||
foreach (var feature in features)
|
||||
{
|
||||
var instance = feature.CreateInstance<DetailsProviderFeature>();
|
||||
var result = instance.GetUserPhoto(database, user, cacheAge);
|
||||
if (result != null)
|
||||
{
|
||||
// resize image
|
||||
using (var originalStream = new MemoryStream(result))
|
||||
{
|
||||
using (var originalImage = Image.FromStream(originalStream))
|
||||
{
|
||||
using (var resizedImage = originalImage.ResizeImage(192, Brushes.White))
|
||||
{
|
||||
using (var savedResizedImage = (MemoryStream)resizedImage.SaveJpg(85))
|
||||
{
|
||||
result = savedResizedImage.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
|
||||
File.WriteAllBytes(cachePath, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// serve from cache
|
||||
if (cacheAge.HasValue)
|
||||
return File.ReadAllBytes(cachePath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetUserPhotoCachePath(User user)
|
||||
{
|
||||
var hasher = new SHA1Managed();
|
||||
var userHash = BitConverter.ToString(hasher.ComputeHash(Encoding.UTF8.GetBytes(user.UserId))).Replace("-", string.Empty);
|
||||
return Path.Combine(database.DiscoConfiguration.PluginUserPhotosLocation, userHash.Substring(0, 2), $"{userHash}.jpg");
|
||||
}
|
||||
|
||||
public DetailsResult GetDetails(User user)
|
||||
{
|
||||
var result = new DetailsResult();
|
||||
var saveChangesRequired = false;
|
||||
|
||||
if (!UserService.CurrentAuthorization.HasAll(Claims.User.Show, Claims.User.ShowDetails))
|
||||
return result;
|
||||
|
||||
var features = Plugins.GetPluginFeatures(typeof(DetailsProviderFeature));
|
||||
|
||||
if (features.Count == 0)
|
||||
return result;
|
||||
|
||||
var cache = user.UserDetails?.Where(d => d.Scope == DetailsScope).ToDictionary(d => d.Key, d => new { DbDetails = d, Details = JsonConvert.DeserializeObject<DetailsResult>(d.Value) }, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var feature in features)
|
||||
{
|
||||
var featureResult = default(DetailsResult);
|
||||
if (!cache.TryGetValue(feature.Id, out var cacheResult) || cacheResult.Details.ExpiresOn < DateTime.Now || cacheResult.Details.GatheredOn < database.DiscoConfiguration.PluginDetailsCacheExpiration)
|
||||
{
|
||||
var timestamp = cacheResult?.Details.GatheredOn;
|
||||
if (timestamp.HasValue && timestamp.Value < database.DiscoConfiguration.PluginDetailsCacheExpiration)
|
||||
timestamp = null;
|
||||
|
||||
try
|
||||
{
|
||||
var featureInstance = feature.CreateInstance<DetailsProviderFeature>();
|
||||
featureResult = featureInstance.GetDetails(database, user, timestamp);
|
||||
|
||||
if (featureResult != null)
|
||||
{
|
||||
if (featureResult.ExpiresOn > DateTime.Now)
|
||||
{
|
||||
if (cacheResult == null)
|
||||
database.UserDetails.Add(new UserDetail() { UserId = user.UserId, Scope = DetailsScope, Key = feature.Id, Value = JsonConvert.SerializeObject(featureResult) });
|
||||
else
|
||||
cacheResult.DbDetails.Value = JsonConvert.SerializeObject(featureResult);
|
||||
saveChangesRequired = true;
|
||||
}
|
||||
else if (cacheResult != null)
|
||||
{
|
||||
database.UserDetails.Remove(cacheResult.DbDetails);
|
||||
saveChangesRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignore exceptions when plugins behave badly
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
featureResult = cacheResult.Details;
|
||||
}
|
||||
|
||||
// apply feature results
|
||||
if (featureResult != null)
|
||||
{
|
||||
result.SetExpiration(featureResult.ExpiresOn);
|
||||
foreach (var value in featureResult.Details)
|
||||
{
|
||||
result.Details[value.Key] = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (saveChangesRequired)
|
||||
database.SaveChanges();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public DetailsResult GetDetails(Device device)
|
||||
{
|
||||
var result = new DetailsResult();
|
||||
var saveChangesRequired = false;
|
||||
|
||||
if (!UserService.CurrentAuthorization.HasAll(Claims.Device.Show, Claims.Device.ShowDetails))
|
||||
return result;
|
||||
|
||||
var features = Plugins.GetPluginFeatures(typeof(DetailsProviderFeature));
|
||||
|
||||
if (features.Count == 0)
|
||||
return result;
|
||||
|
||||
var cache = device.DeviceDetails?.Where(d => d.Scope == DetailsScope).ToDictionary(d => d.Key, d => new { DbDetails = d, Details = JsonConvert.DeserializeObject<DetailsResult>(d.Value) }, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var feature in features)
|
||||
{
|
||||
var featureResult = default(DetailsResult);
|
||||
if (!cache.TryGetValue(feature.Id, out var cacheResult) || cacheResult.Details.ExpiresOn < DateTime.Now || cacheResult.Details.GatheredOn < database.DiscoConfiguration.PluginDetailsCacheExpiration)
|
||||
{
|
||||
var timestamp = cacheResult?.Details.GatheredOn;
|
||||
if (timestamp.HasValue && timestamp.Value < database.DiscoConfiguration.PluginDetailsCacheExpiration)
|
||||
timestamp = null;
|
||||
|
||||
try
|
||||
{
|
||||
var featureInstance = feature.CreateInstance<DetailsProviderFeature>();
|
||||
featureResult = featureInstance.GetDetails(database, device, timestamp);
|
||||
|
||||
if (featureResult != null)
|
||||
{
|
||||
if (featureResult.ExpiresOn > DateTime.Now)
|
||||
{
|
||||
if (cacheResult == null)
|
||||
database.DeviceDetails.Add(new DeviceDetail() { DeviceSerialNumber = device.SerialNumber, Scope = DetailsScope, Key = feature.Id, Value = JsonConvert.SerializeObject(featureResult) });
|
||||
else
|
||||
cacheResult.DbDetails.Value = JsonConvert.SerializeObject(featureResult);
|
||||
saveChangesRequired = true;
|
||||
}
|
||||
else if (cacheResult != null)
|
||||
{
|
||||
database.DeviceDetails.Remove(cacheResult.DbDetails);
|
||||
saveChangesRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignore exceptions when plugins behave badly
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
featureResult = cacheResult.Details;
|
||||
}
|
||||
|
||||
// apply feature results
|
||||
if (featureResult != null)
|
||||
{
|
||||
result.SetExpiration(featureResult.ExpiresOn);
|
||||
foreach (var value in featureResult.Details)
|
||||
{
|
||||
result.Details[value.Key] = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (saveChangesRequired)
|
||||
database.SaveChanges();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,13 @@ using Disco.Services.Authorization;
|
||||
using Disco.Services.Documents;
|
||||
using Disco.Services.Interop;
|
||||
using Disco.Services.Interop.ActiveDirectory;
|
||||
using Disco.Services.Plugins.Features.DetailsProvider;
|
||||
using Disco.Services.Users;
|
||||
using Disco.Services.Web;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace Disco.Web.Areas.API.Controllers
|
||||
@@ -186,5 +188,29 @@ namespace Disco.Web.Areas.API.Controllers
|
||||
return RedirectToAction(MVC.API.DocumentTemplatePackage.Generate(DocumentTemplatePackageId, userId));
|
||||
}
|
||||
|
||||
public virtual ActionResult Photo(string userId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
|
||||
userId = ActiveDirectory.ParseDomainAccountId(userId);
|
||||
var user = UserService.GetUser(userId);
|
||||
|
||||
if (user == null)
|
||||
return HttpNotFound();
|
||||
|
||||
var service = new DetailsProviderService(Database);
|
||||
|
||||
if (!service.HasUserPhoto(user))
|
||||
return HttpNotFound();
|
||||
|
||||
var photo = service.GetUserPhoto(user);
|
||||
|
||||
if (photo == null)
|
||||
return HttpNotFound();
|
||||
|
||||
return File(photo, "image/jpg");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,14 @@
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACWUlEQVQ4y6XRXWiSURgHcJsXa4WNNuuyiy6CoAupixERoXXhmljuxaJiFrVA1i72cVFCOSMt8rNt2YfGO5g5Z1NstWW+c4ZBq4QpqMkEbZDSCObAMprjdf90sIjxsgUdODd/zvmd5zwPCwDrf/aGB7q6utgmk8ngdruzVqt10eVyTWu1Wuk/AXK5vMpoNPpjsRgGbU8/9fbdH/J4PAuRSARKpfLKhoBYLG595nTCaDSZVjPp6TPbHQ5H0mAwfBeJRHXrAp0dna9JcqCguX2H/Xd+S625aLFYQBDE8XWBd+8/TI6Njc+vzcfGX4nLX4FOp5OuC0wGAlS53NzaPPAm2Gi32+H3+5tYJEl+pigKoVAIPp+PnpqaosPhMF1uHB2Px2mv14vya6VgMKhhHGN3d/dSMplENptFIpHA3NwcCoUCSqUSKqvScZVKBbPZHGQEFApFMZ1OI5PJIBqNrkD5fB40Ta8AlcrUajVsNpufEbh+42YxHEkh+/UbUqlZpGd/lAH8WTMzMzDd64d7NMAMDOobi/OpHqh6rqK9jcCvBQncQzK0Xm5DPn0BJ4lz6GgVIkedYAaamxqK0dEDePl4FziczehTsZGLs7BnNwdiwRac4lejvp6La83VzABv/8FF/qG9oD/WQS/fhNptHEw8rEJiuAo7ubXACAtH9m0Fu2YHxQzweEuEVIYnaiFmvQ04f1aItksi5KaP4ZFGjDB5GG/7j4LL5YYYgZYW2c/yiJbv6h/A0EvC4RjGiOsFnK4J+KgABmyjsDufL0skki8CgYCoXOLz+TWrwG+kXMkgQ6yv+QAAAABJRU5ErkJggg==) /*Images/Actions/unlocked.png*/;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#Device_Show #Device_Show_Subjects #Device_Show_Details #Device_Show_User #Device_Show_User_Photo_Container {
|
||||
float: left;
|
||||
padding-right: 2px;
|
||||
}
|
||||
#Device_Show #Device_Show_Subjects #Device_Show_Details #Device_Show_User #Device_Show_User_Photo {
|
||||
max-width: 48px;
|
||||
height: auto;
|
||||
}
|
||||
#Device_Show #Device_Show_Subjects #Device_Show_Details #Device_Show_User #Device_Show_User_Flags {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
& > tbody > tr > td:not(:last-child) {
|
||||
border-right: 1px dashed #aaa;
|
||||
}
|
||||
& > tbody > tr > td:not(:last-child) {
|
||||
border-right: 1px dashed #aaa;
|
||||
}
|
||||
|
||||
#Device_Show_Details {
|
||||
|
||||
@@ -69,6 +69,17 @@
|
||||
}
|
||||
|
||||
#Device_Show_User {
|
||||
|
||||
#Device_Show_User_Photo_Container {
|
||||
float: left;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
#Device_Show_User_Photo {
|
||||
max-width: 48px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#Device_Show_User_Flags {
|
||||
font-size: 16px;
|
||||
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -202,6 +202,14 @@
|
||||
#Job_Show #Job_Show_Subjects #Job_Show_Device #Job_Show_Device_DeviceHeld table > tbody > tr > td:first-child {
|
||||
width: 62px;
|
||||
}
|
||||
#Job_Show #Job_Show_Subjects #Job_Show_User #Job_Show_User_Photo_Container {
|
||||
float: left;
|
||||
padding-right: 4px;
|
||||
}
|
||||
#Job_Show #Job_Show_Subjects #Job_Show_User #Job_Show_User_Photo {
|
||||
height: auto;
|
||||
max-width: 96px;
|
||||
}
|
||||
#Job_Show #Job_Show_Subjects #Job_Show_User #Job_Show_User_Flags {
|
||||
margin: 4px 0;
|
||||
font-size: 16px;
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
& > tbody > tr > td:not(:last-child) {
|
||||
border-right: 1px dashed #aaa;
|
||||
}
|
||||
& > tbody > tr > td:not(:last-child) {
|
||||
border-right: 1px dashed #aaa;
|
||||
}
|
||||
|
||||
#Job_Show_Job {
|
||||
#Job_Show_Job_Type {
|
||||
@@ -83,7 +83,7 @@
|
||||
text-transform: uppercase;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
& > table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
@@ -113,10 +113,10 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& > tbody > tr > td:first-child {
|
||||
font-weight: @FontWeightBodyBold;
|
||||
width: 60px;
|
||||
}
|
||||
& > tbody > tr > td:first-child {
|
||||
font-weight: @FontWeightBodyBold;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,17 @@
|
||||
}
|
||||
|
||||
#Job_Show_User {
|
||||
|
||||
#Job_Show_User_Photo_Container {
|
||||
float: left;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#Job_Show_User_Photo {
|
||||
height: auto;
|
||||
max-width: 96px;
|
||||
}
|
||||
|
||||
#Job_Show_User_Flags {
|
||||
margin: 4px 0;
|
||||
font-size: 16px;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -94,9 +94,6 @@
|
||||
padding-top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
#User_Show #User_Show_Subjects > tbody > tr > td > div {
|
||||
position: relative;
|
||||
}
|
||||
#User_Show #User_Show_Subjects > tbody > tr > td > div div.status {
|
||||
margin-top: 2px;
|
||||
padding-top: 2px;
|
||||
@@ -111,6 +108,20 @@
|
||||
#User_Show #User_Show_Subjects #User_Show_Details {
|
||||
width: 330px;
|
||||
}
|
||||
#User_Show #User_Show_Subjects #User_Show_Details.hasPhoto {
|
||||
width: 450px;
|
||||
}
|
||||
#User_Show #User_Show_Subjects #User_Show_Details #User_Show_Details_Photo_Container {
|
||||
float: left;
|
||||
padding-right: 4px;
|
||||
}
|
||||
#User_Show #User_Show_Subjects #User_Show_Details #User_Show_Details_Photo {
|
||||
max-height: 192px;
|
||||
width: auto;
|
||||
}
|
||||
#User_Show #User_Show_Subjects #User_Show_Details table.verticalHeadings {
|
||||
width: auto;
|
||||
}
|
||||
#User_Show #User_Show_Subjects #User_Show_Details table.verticalHeadings > tbody > tr > td:first-child {
|
||||
width: 104px;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
height: 100%;
|
||||
|
||||
& > div /* Extra DIV added for FireFox TD relative position incompatibility */ {
|
||||
position: relative;
|
||||
|
||||
div.status {
|
||||
margin-top: 2px;
|
||||
@@ -40,14 +39,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
& > tbody > tr > td:not(:last-child) {
|
||||
border-right: 1px dashed #aaa;
|
||||
}
|
||||
& > tbody > tr > td:not(:last-child) {
|
||||
border-right: 1px dashed #aaa;
|
||||
}
|
||||
|
||||
#User_Show_Details {
|
||||
width: 330px;
|
||||
|
||||
&.hasPhoto {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
#User_Show_Details_Photo_Container {
|
||||
float: left;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#User_Show_Details_Photo {
|
||||
max-height: 192px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
table.verticalHeadings {
|
||||
width: auto;
|
||||
|
||||
& > tbody > tr > td:first-child {
|
||||
width: 104px;
|
||||
font-weight: @FontWeightBodyBold;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ using Disco.Models.UI.Device;
|
||||
using Disco.Services;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Devices.Exporting;
|
||||
using Disco.Services.Plugins;
|
||||
using Disco.Services.Plugins.Features.DetailsProvider;
|
||||
using Disco.Services.Plugins.Features.UIExtension;
|
||||
using Disco.Services.Users;
|
||||
using Disco.Services.Web;
|
||||
@@ -205,9 +205,16 @@ namespace Disco.Web.Controllers
|
||||
Database.Configuration.LazyLoadingEnabled = true;
|
||||
|
||||
m.Device = Database.Devices
|
||||
.Include("DeviceModel").Include("DeviceProfile").Include("DeviceBatch").Include("DeviceDetails")
|
||||
.Include("DeviceUserAssignments.AssignedUser.UserFlagAssignments").Include("AssignedUser.UserFlagAssignments").Include("DeviceCertificates")
|
||||
.Include("DeviceAttachments.TechUser").Include("DeviceAttachments.DocumentTemplate")
|
||||
.Include("DeviceModel")
|
||||
.Include("DeviceProfile")
|
||||
.Include("DeviceBatch")
|
||||
.Include("DeviceDetails")
|
||||
.Include("DeviceUserAssignments.AssignedUser.UserFlagAssignments")
|
||||
.Include("AssignedUser.UserFlagAssignments")
|
||||
.Include("AssignedUser.UserDetails")
|
||||
.Include("DeviceCertificates")
|
||||
.Include("DeviceAttachments.TechUser")
|
||||
.Include("DeviceAttachments.DocumentTemplate")
|
||||
.FirstOrDefault(d => d.SerialNumber == id);
|
||||
|
||||
if (m.Device == null)
|
||||
@@ -262,6 +269,9 @@ namespace Disco.Web.Controllers
|
||||
m.DeviceProfileWirelessProfileProviders = m.Device.DeviceProfile.GetWirelessProfileProviders().ToList();
|
||||
}
|
||||
|
||||
// Populate Custom Details
|
||||
m.PopulateDetails(Database);
|
||||
|
||||
// UI Extensions
|
||||
UIExtensions.ExecuteExtensions<DeviceShowModel>(this.ControllerContext, m);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Disco.Services.Jobs.JobLists;
|
||||
using Disco.Services.Jobs.JobQueues;
|
||||
using Disco.Services.Jobs.Statistics;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Plugins.Features.DetailsProvider;
|
||||
using Disco.Services.Plugins.Features.RepairProvider;
|
||||
using Disco.Services.Plugins.Features.UIExtension;
|
||||
using Disco.Services.Plugins.Features.WarrantyProvider;
|
||||
@@ -298,9 +299,21 @@ namespace Disco.Web.Controllers
|
||||
var m = new Models.Job.ShowModel();
|
||||
|
||||
m.Job = Database.Jobs
|
||||
.Include("Device.DeviceModel").Include("Device.DeviceBatch").Include("DeviceHeldTechUser").Include("DeviceReadyForReturnTechUser").Include("DeviceReturnedTechUser")
|
||||
.Include("OpenedTechUser").Include("ClosedTechUser").Include("JobType").Include("JobSubTypes").Include("User.UserFlagAssignments").Include("JobLogs.TechUser")
|
||||
.Include("JobAttachments.TechUser").Include("JobAttachments.DocumentTemplate")
|
||||
.Include("Device.DeviceModel")
|
||||
.Include("Device.DeviceBatch")
|
||||
.Include("Device.DeviceDetails")
|
||||
.Include("DeviceHeldTechUser")
|
||||
.Include("DeviceReadyForReturnTechUser")
|
||||
.Include("DeviceReturnedTechUser")
|
||||
.Include("OpenedTechUser")
|
||||
.Include("ClosedTechUser")
|
||||
.Include("JobType")
|
||||
.Include("JobSubTypes")
|
||||
.Include("User.UserFlagAssignments")
|
||||
.Include("User.UserDetails")
|
||||
.Include("JobLogs.TechUser")
|
||||
.Include("JobAttachments.TechUser")
|
||||
.Include("JobAttachments.DocumentTemplate")
|
||||
.FirstOrDefault(j => j.Id == id.Value);
|
||||
|
||||
if (m.Job == null)
|
||||
@@ -364,6 +377,9 @@ namespace Disco.Web.Controllers
|
||||
m.LocationOptions = ManagedJobList.OpenJobsTable(j => j).Items.Cast<JobTableStatusItemModel>().JobLocationReferences(Database.DiscoConfiguration.JobPreferences.LocationList).ToList();
|
||||
}
|
||||
|
||||
// Populate Custom Details
|
||||
m.PopulateDetails(Database);
|
||||
|
||||
// UI Extensions
|
||||
UIExtensions.ExecuteExtensions<JobShowModel>(this.ControllerContext, m);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using Disco.Services;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Authorization.Roles;
|
||||
using Disco.Services.Interop.ActiveDirectory;
|
||||
using Disco.Services.Plugins.Features.DetailsProvider;
|
||||
using Disco.Services.Plugins.Features.UIExtension;
|
||||
using Disco.Services.Users;
|
||||
using Disco.Services.Users.UserFlags;
|
||||
@@ -56,10 +57,12 @@ namespace Disco.Web.Controllers
|
||||
.Include("DeviceUserAssignments.Device.DeviceModel")
|
||||
.Include("DeviceUserAssignments.Device.DeviceProfile")
|
||||
.Include("DeviceUserAssignments.Device.DeviceBatch")
|
||||
.Include("DeviceUserAssignments.Device.DeviceDetails")
|
||||
.Include("UserAttachments.TechUser")
|
||||
.Include("UserAttachments.DocumentTemplate")
|
||||
.Include("UserFlagAssignments.AddedUser")
|
||||
.Include("UserFlagAssignments.RemovedUser")
|
||||
.Include("UserDetails")
|
||||
.FirstOrDefault(um => um.UserId == id);
|
||||
|
||||
if (m.User == null)
|
||||
@@ -110,6 +113,9 @@ namespace Disco.Web.Controllers
|
||||
m.DocumentTemplatePackages = m.User.AvailableDocumentTemplatePackages(Database, UserService.CurrentUser);
|
||||
}
|
||||
|
||||
// Populate Custom Details
|
||||
m.PopulateDetails(Database);
|
||||
|
||||
// UI Extensions
|
||||
UIExtensions.ExecuteExtensions<UserShowModel>(this.ControllerContext, m);
|
||||
|
||||
|
||||
@@ -827,6 +827,11 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Queues.cshtml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Shared\_CustomDetailValueRender.generated.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>_CustomDetailValueRender.cshtml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Shared\DisplayTemplates\AccessDeniedException.generated.cs">
|
||||
<DependentUpon>AccessDeniedException.cshtml</DependentUpon>
|
||||
<AutoGen>True</AutoGen>
|
||||
@@ -1445,6 +1450,7 @@
|
||||
<DependentUpon>tinymce.js</DependentUpon>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="ClientSource\Style\Images\UnknownPhoto.png" />
|
||||
<Content Include="ClientSource\Style\Shadowbox.min.css">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -2156,6 +2162,10 @@
|
||||
<Generator>RazorGenerator</Generator>
|
||||
<LastGenOutput>Licence.generated.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Include="Views\Shared\_CustomDetailValueRender.cshtml">
|
||||
<Generator>RazorGenerator</Generator>
|
||||
<LastGenOutput>_CustomDetailValueRender.generated.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Include="Views\Shared\DisplayTemplates\AccessDeniedException.cshtml">
|
||||
<Generator>RazorGenerator</Generator>
|
||||
<LastGenOutput>AccessDeniedException.generated.cs</LastGenOutput>
|
||||
|
||||
@@ -105,6 +105,12 @@ namespace Disco.Web.Areas.API.Controllers
|
||||
{
|
||||
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.GeneratePdfPackage);
|
||||
}
|
||||
[NonAction]
|
||||
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
|
||||
public virtual System.Web.Mvc.ActionResult Photo()
|
||||
{
|
||||
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Photo);
|
||||
}
|
||||
|
||||
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
|
||||
public UserController Actions { get { return MVC.API.User; } }
|
||||
@@ -129,6 +135,7 @@ namespace Disco.Web.Areas.API.Controllers
|
||||
public readonly string AttachmentRemove = "AttachmentRemove";
|
||||
public readonly string GeneratePdf = "GeneratePdf";
|
||||
public readonly string GeneratePdfPackage = "GeneratePdfPackage";
|
||||
public readonly string Photo = "Photo";
|
||||
}
|
||||
|
||||
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
|
||||
@@ -142,6 +149,7 @@ namespace Disco.Web.Areas.API.Controllers
|
||||
public const string AttachmentRemove = "AttachmentRemove";
|
||||
public const string GeneratePdf = "GeneratePdf";
|
||||
public const string GeneratePdfPackage = "GeneratePdfPackage";
|
||||
public const string Photo = "Photo";
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +224,14 @@ namespace Disco.Web.Areas.API.Controllers
|
||||
public readonly string Domain = "Domain";
|
||||
public readonly string DocumentTemplatePackageId = "DocumentTemplatePackageId";
|
||||
}
|
||||
static readonly ActionParamsClass_Photo s_params_Photo = new ActionParamsClass_Photo();
|
||||
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
|
||||
public ActionParamsClass_Photo PhotoParams { get { return s_params_Photo; } }
|
||||
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
|
||||
public class ActionParamsClass_Photo
|
||||
{
|
||||
public readonly string userId = "userId";
|
||||
}
|
||||
static readonly ViewsClass s_views = new ViewsClass();
|
||||
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
|
||||
public ViewsClass Views { get { return s_views; } }
|
||||
@@ -338,6 +354,18 @@ namespace Disco.Web.Areas.API.Controllers
|
||||
return callInfo;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
partial void PhotoOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string userId);
|
||||
|
||||
[NonAction]
|
||||
public override System.Web.Mvc.ActionResult Photo(string userId)
|
||||
{
|
||||
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Photo);
|
||||
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "userId", userId);
|
||||
PhotoOverride(callInfo, userId);
|
||||
return callInfo;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace T4MVC
|
||||
public _ViewNamesClass ViewNames { get { return s_ViewNames; } }
|
||||
public class _ViewNamesClass
|
||||
{
|
||||
public readonly string _CustomDetailValueRender = "_CustomDetailValueRender";
|
||||
public readonly string _DialogLayout = "_DialogLayout";
|
||||
public readonly string _EmptyLayout = "_EmptyLayout";
|
||||
public readonly string _GenerateDocumentControl = "_GenerateDocumentControl";
|
||||
@@ -46,6 +47,7 @@ namespace T4MVC
|
||||
public readonly string _SearchDialog = "_SearchDialog";
|
||||
public readonly string Error = "Error";
|
||||
}
|
||||
public readonly string _CustomDetailValueRender = "~/Views/Shared/_CustomDetailValueRender.cshtml";
|
||||
public readonly string _DialogLayout = "~/Views/Shared/_DialogLayout.cshtml";
|
||||
public readonly string _EmptyLayout = "~/Views/Shared/_EmptyLayout.cshtml";
|
||||
public readonly string _GenerateDocumentControl = "~/Views/Shared/_GenerateDocumentControl.cshtml";
|
||||
|
||||
@@ -620,6 +620,7 @@ namespace Links
|
||||
public static readonly string warning32_png = Url("warning32.png");
|
||||
}
|
||||
|
||||
public static readonly string UnknownPhoto_png = Url("UnknownPhoto.png");
|
||||
}
|
||||
|
||||
public static readonly string IsotopeStyles_css = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/IsotopeStyles.min.css") ? Url("IsotopeStyles.min.css") : Url("IsotopeStyles.css");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using Disco.Models.UI.Device;
|
||||
using Disco.Services.Plugins;
|
||||
using Disco.Web.Models.Shared;
|
||||
@@ -30,5 +31,9 @@ namespace Disco.Web.Models.Device
|
||||
Templates = DocumentTemplates,
|
||||
TemplatePackages = DocumentTemplatePackages,
|
||||
};
|
||||
|
||||
public DetailsResult DeviceDetails { get; set; }
|
||||
public DetailsResult AssignedUserDetails { get; set; }
|
||||
public bool HasAssignedUserPhoto { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Job;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using Disco.Models.UI.Job;
|
||||
using Disco.Web.Models.Shared;
|
||||
using System;
|
||||
@@ -28,5 +29,9 @@ namespace Disco.Web.Models.Job
|
||||
|
||||
public LocationModes LocationMode { get; set; }
|
||||
public List<JobLocationReference> LocationOptions { get; set; }
|
||||
|
||||
public DetailsResult UserDetails { get; set; }
|
||||
public bool HasUserPhoto { get; set; }
|
||||
public DetailsResult DeviceDetails { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using Disco.Models.Services.Authorization;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Models.Services.Plugins.Details;
|
||||
using Disco.Models.UI.User;
|
||||
using Disco.Web.Models.Shared;
|
||||
using System.Collections.Generic;
|
||||
@@ -27,6 +28,10 @@ namespace Disco.Web.Models.User
|
||||
public IAuthorizationToken AuthorizationToken { get; set; }
|
||||
public IClaimNavigatorItem ClaimNavigator { get; set; }
|
||||
|
||||
public DetailsResult UserDetails { get; set; }
|
||||
public bool HasUserPhoto { get; set; }
|
||||
public Dictionary<string, DetailsResult> AssignedDevicesDetails { get; set; }
|
||||
|
||||
public FancyTreeNode[] ClaimNavigatorFancyTreeNodes
|
||||
{
|
||||
get
|
||||
@@ -55,5 +60,6 @@ namespace Disco.Web.Models.User
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,17 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@if (Model.DeviceDetails != null && Model.DeviceDetails.Details.Count > 0)
|
||||
{
|
||||
<div id="Device_Show_CustomDetails" class="status clearfix">
|
||||
@foreach (var detail in Model.DeviceDetails.Details)
|
||||
{
|
||||
<div>
|
||||
<strong>@detail.Key:</strong> @Html.Partial(MVC.Shared.Views._CustomDetailValueRender, detail)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="status">
|
||||
@{
|
||||
var assignedUser = Model.Device.AssignedUser;
|
||||
@@ -199,7 +210,13 @@
|
||||
<td>
|
||||
@if (assignedUser != null)
|
||||
{
|
||||
<div id="Device_Show_User">
|
||||
<div id="Device_Show_User" class="clearfix @(Model.HasAssignedUserPhoto ? "hasPhoto" : "noPhoto")">
|
||||
@if (Model.HasAssignedUserPhoto)
|
||||
{
|
||||
<div id="Device_Show_User_Photo_Container">
|
||||
<img id="Device_Show_User_Photo" src="@Url.Action(MVC.API.User.Photo(assignedUser.UserId))" />
|
||||
</div>
|
||||
}
|
||||
<div id="Device_Show_User_DisplayName" title="Display Name">
|
||||
@if (Authorization.Has(Claims.User.Show))
|
||||
{
|
||||
@@ -266,6 +283,17 @@
|
||||
</script>
|
||||
</div>
|
||||
}
|
||||
@if (Model.AssignedUserDetails != null && Model.AssignedUserDetails.Details.Count > 0)
|
||||
{
|
||||
<div id="Device_Show_User_CustomDetails" class="status clearfix">
|
||||
@foreach (var detail in Model.AssignedUserDetails.Details)
|
||||
{
|
||||
<div>
|
||||
<strong>@detail.Key:</strong> @Html.Partial(MVC.Shared.Views._CustomDetailValueRender, detail)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,9 +71,9 @@
|
||||
{
|
||||
@CommonHelpers.FriendlyDate(Model.Job.ExpectedClosedDate)
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (Model.Job.ClosedDate.HasValue)
|
||||
{
|
||||
<tr>
|
||||
@@ -180,142 +180,153 @@
|
||||
{@Html.ActionLink(Model.Job.DeviceSerialNumber, MVC.Device.Show(Model.Job.DeviceSerialNumber))}
|
||||
else
|
||||
{@Model.Job.DeviceSerialNumber}
|
||||
</h2>
|
||||
<div class="clearfix">
|
||||
<div id="Job_Show_Device_Details">
|
||||
<img id="Job_Show_Device_Model_Image" alt="Model Image" src="@Url.Action(MVC.API.DeviceModel.Image(Model.Job.Device.DeviceModelId, Model.Job.Device.DeviceModel.ImageHash()))" />
|
||||
<div id="Job_Show_Device_ComputerName" title="Computer Name">@Model.Job.Device.ComputerName</div>
|
||||
<div id="Job_Show_Device_Model" title="Model">@Html.ActionLink(Model.Job.Device.DeviceModel.ToString(), MVC.Config.DeviceModel.Index(Model.Job.Device.DeviceModelId))</div>
|
||||
</h2>
|
||||
<div class="clearfix">
|
||||
<div id="Job_Show_Device_Details">
|
||||
<img id="Job_Show_Device_Model_Image" alt="Model Image" src="@Url.Action(MVC.API.DeviceModel.Image(Model.Job.Device.DeviceModelId, Model.Job.Device.DeviceModel.ImageHash()))" />
|
||||
<div id="Job_Show_Device_ComputerName" title="Computer Name">@Model.Job.Device.ComputerName</div>
|
||||
<div id="Job_Show_Device_Model" title="Model">@Html.ActionLink(Model.Job.Device.DeviceModel.ToString(), MVC.Config.DeviceModel.Index(Model.Job.Device.DeviceModelId))</div>
|
||||
@if (Model.Job.Device.DeviceBatch != null)
|
||||
{
|
||||
<div id="Job_Show_Device_Batch" title="Batch">@Html.ActionLink(Model.Job.Device.DeviceBatch.Name, MVC.Config.DeviceBatch.Index(Model.Job.Device.DeviceBatchId))</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.Job.Device.DeviceBatch != null)
|
||||
{
|
||||
<div id="Job_Show_Device_Batch" title="Batch">@Html.ActionLink(Model.Job.Device.DeviceBatch.Name, MVC.Config.DeviceBatch.Index(Model.Job.Device.DeviceBatchId))</div>
|
||||
if (Model.Job.JobTypeId == JobType.JobTypeIds.HWar)
|
||||
{
|
||||
<div id="Job_Show_Device_Details_HWar">
|
||||
<div>DEVICE WARRANTY</div>
|
||||
<div>Until: <span id="Job_Show_Device_Details_HWar_ValidUntil" data-livestamp="@Model.Job.Device.DeviceBatch.WarrantyValidUntil.ToUnixEpoc()">@Model.Job.Device.DeviceBatch.WarrantyValidUntil.ToFullDateTime("Unknown")</span></div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Job.Device.DeviceBatch.WarrantyDetails))
|
||||
{
|
||||
<a id="Job_Show_Device_Details_HWar_Details_Button" href="#">Show Details</a>
|
||||
<div id="Job_Show_Device_Details_HWar_Details_Dialog" class="dialog" title="Warranty Details for @(Model.Job.Device.DeviceBatch.Name)">
|
||||
<div>@(new HtmlString(Model.Job.Device.DeviceBatch.WarrantyDetails))</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var d;
|
||||
$('#Job_Show_Device_Details_HWar_Details_Button').click(function () {
|
||||
if (!d)
|
||||
d = $('#Job_Show_Device_Details_HWar_Details_Dialog').dialog({
|
||||
width: 570,
|
||||
modal: true
|
||||
});
|
||||
else
|
||||
d.dialog('open');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
if (Model.Job.JobTypeId == JobType.JobTypeIds.HNWar)
|
||||
{
|
||||
<div id="Job_Show_Device_Details_HNWar">
|
||||
<div>INSURANCE</div>
|
||||
<div id="Job_Show_Device_Details_HNWar_InsuranceSupplier">@Model.Job.Device.DeviceBatch.InsuranceSupplier</div>
|
||||
<div>Until: <span id="Job_Show_Device_Details_HNWar_ValidUntil" data-livestamp="@Model.Job.Device.DeviceBatch.InsuredUntil.ToUnixEpoc()">@Model.Job.Device.DeviceBatch.InsuredUntil.ToFullDateTime("Unknown")</span></div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Job.Device.DeviceBatch.InsuranceDetails))
|
||||
{
|
||||
<a id="Job_Show_Device_Details_HNWar_Details_Button" href="#">Show Details</a>
|
||||
<div id="Job_Show_Device_Details_HNWar_Details_Dialog" class="dialog" title="Insurance Details for @(Model.Job.Device.DeviceBatch.Name)">
|
||||
<div>@(new HtmlString(Model.Job.Device.DeviceBatch.InsuranceDetails))</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var d;
|
||||
$('#Job_Show_Device_Details_HNWar_Details_Button').click(function () {
|
||||
if (!d)
|
||||
d = $('#Job_Show_Device_Details_HNWar_Details_Dialog').dialog({
|
||||
width: 570,
|
||||
modal: true
|
||||
});
|
||||
else
|
||||
d.dialog('open');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (Model.Job.Device.DeviceBatch != null)
|
||||
@if (Model.DeviceDetails != null && Model.DeviceDetails.Details.Count > 0)
|
||||
{
|
||||
if (Model.Job.JobTypeId == JobType.JobTypeIds.HWar)
|
||||
{
|
||||
<div id="Job_Show_Device_Details_HWar">
|
||||
<div>DEVICE WARRANTY</div>
|
||||
<div>Until: <span id="Job_Show_Device_Details_HWar_ValidUntil" data-livestamp="@Model.Job.Device.DeviceBatch.WarrantyValidUntil.ToUnixEpoc()">@Model.Job.Device.DeviceBatch.WarrantyValidUntil.ToFullDateTime("Unknown")</span></div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Job.Device.DeviceBatch.WarrantyDetails))
|
||||
{
|
||||
<a id="Job_Show_Device_Details_HWar_Details_Button" href="#">Show Details</a>
|
||||
<div id="Job_Show_Device_Details_HWar_Details_Dialog" class="dialog" title="Warranty Details for @(Model.Job.Device.DeviceBatch.Name)">
|
||||
<div>@(new HtmlString(Model.Job.Device.DeviceBatch.WarrantyDetails))</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var d;
|
||||
$('#Job_Show_Device_Details_HWar_Details_Button').click(function () {
|
||||
if (!d)
|
||||
d = $('#Job_Show_Device_Details_HWar_Details_Dialog').dialog({
|
||||
width: 570,
|
||||
modal: true
|
||||
});
|
||||
else
|
||||
d.dialog('open');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
if (Model.Job.JobTypeId == JobType.JobTypeIds.HNWar)
|
||||
{
|
||||
<div id="Job_Show_Device_Details_HNWar">
|
||||
<div>INSURANCE</div>
|
||||
<div id="Job_Show_Device_Details_HNWar_InsuranceSupplier">@Model.Job.Device.DeviceBatch.InsuranceSupplier</div>
|
||||
<div>Until: <span id="Job_Show_Device_Details_HNWar_ValidUntil" data-livestamp="@Model.Job.Device.DeviceBatch.InsuredUntil.ToUnixEpoc()">@Model.Job.Device.DeviceBatch.InsuredUntil.ToFullDateTime("Unknown")</span></div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Job.Device.DeviceBatch.InsuranceDetails))
|
||||
{
|
||||
<a id="Job_Show_Device_Details_HNWar_Details_Button" href="#">Show Details</a>
|
||||
<div id="Job_Show_Device_Details_HNWar_Details_Dialog" class="dialog" title="Insurance Details for @(Model.Job.Device.DeviceBatch.Name)">
|
||||
<div>@(new HtmlString(Model.Job.Device.DeviceBatch.InsuranceDetails))</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var d;
|
||||
$('#Job_Show_Device_Details_HNWar_Details_Button').click(function () {
|
||||
if (!d)
|
||||
d = $('#Job_Show_Device_Details_HNWar_Details_Dialog').dialog({
|
||||
width: 570,
|
||||
modal: true
|
||||
});
|
||||
else
|
||||
d.dialog('open');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div id="Job_Show_Device_CustomDetails" class="status clearfix">
|
||||
@foreach (var detail in Model.DeviceDetails.Details)
|
||||
{
|
||||
<div>
|
||||
<strong>@detail.Key:</strong> @Html.Partial(MVC.Shared.Views._CustomDetailValueRender, detail)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.Job.DeviceHeld.HasValue)
|
||||
{
|
||||
var canEditLocation = Authorization.Has(Claims.Job.Properties.DeviceHeldLocation);
|
||||
<div id="Job_Show_Device_DeviceHeld" class="status">
|
||||
<table class="none">
|
||||
<tr>
|
||||
<td>Location:</td>
|
||||
<td>
|
||||
<span id="Job_Show_Device_DeviceHeld_Location">
|
||||
@if (canEditLocation)
|
||||
{
|
||||
switch (Model.LocationMode)
|
||||
@if (Model.Job.DeviceHeld.HasValue)
|
||||
{
|
||||
var canEditLocation = Authorization.Has(Claims.Job.Properties.DeviceHeldLocation);
|
||||
<div id="Job_Show_Device_DeviceHeld" class="status">
|
||||
<table class="none">
|
||||
<tr>
|
||||
<td>Location:</td>
|
||||
<td>
|
||||
<span id="Job_Show_Device_DeviceHeld_Location">
|
||||
@if (canEditLocation)
|
||||
{
|
||||
case LocationModes.Unrestricted:
|
||||
case LocationModes.OptionalList:
|
||||
@Html.TextBoxFor(m => m.Job.DeviceHeldLocation, new { @class = "small discreet" })
|
||||
break;
|
||||
case LocationModes.RestrictedList:
|
||||
List<SelectListItem> listOptions = new List<SelectListItem>() { new SelectListItem() { Value = "", Text = "<Unknown>" } };
|
||||
if (!string.IsNullOrWhiteSpace(Model.Job.DeviceHeldLocation) && !Model.LocationOptions.Any(l => l.Location.Equals(Model.Job.DeviceHeldLocation)))
|
||||
{
|
||||
listOptions.Add(new SelectListItem() { Value = Model.Job.DeviceHeldLocation, Text = string.Format("Custom: {0}", Model.Job.DeviceHeldLocation) });
|
||||
}
|
||||
listOptions.AddRange(Model.LocationOptions.Select(l => new SelectListItem() { Value = l.Location, Text = (l.References.Count == 0 ? l.Location : (l.References.Count == 1 ? string.Format("{0} [Job {1}]", l.Location, l.References[0].JobId) : string.Format("{0} [{1} jobs]", l.Location, l.References.Count))) }));
|
||||
@Html.DropDownListFor(m => m.Job.DeviceHeldLocation, listOptions, new { @class = "small discreet" });
|
||||
break;
|
||||
switch (Model.LocationMode)
|
||||
{
|
||||
case LocationModes.Unrestricted:
|
||||
case LocationModes.OptionalList:
|
||||
@Html.TextBoxFor(m => m.Job.DeviceHeldLocation, new { @class = "small discreet" })
|
||||
break;
|
||||
case LocationModes.RestrictedList:
|
||||
List<SelectListItem> listOptions = new List<SelectListItem>() { new SelectListItem() { Value = "", Text = "<Unknown>" } };
|
||||
if (!string.IsNullOrWhiteSpace(Model.Job.DeviceHeldLocation) && !Model.LocationOptions.Any(l => l.Location.Equals(Model.Job.DeviceHeldLocation)))
|
||||
{
|
||||
listOptions.Add(new SelectListItem() { Value = Model.Job.DeviceHeldLocation, Text = string.Format("Custom: {0}", Model.Job.DeviceHeldLocation) });
|
||||
}
|
||||
listOptions.AddRange(Model.LocationOptions.Select(l => new SelectListItem() { Value = l.Location, Text = (l.References.Count == 0 ? l.Location : (l.References.Count == 1 ? string.Format("{0} [Job {1}]", l.Location, l.References[0].JobId) : string.Format("{0} [{1} jobs]", l.Location, l.References.Count))) }));
|
||||
@Html.DropDownListFor(m => m.Job.DeviceHeldLocation, listOptions, new { @class = "small discreet" });
|
||||
break;
|
||||
}
|
||||
@AjaxHelpers.AjaxSave() @AjaxHelpers.AjaxLoader()
|
||||
}
|
||||
@AjaxHelpers.AjaxSave() @AjaxHelpers.AjaxLoader()
|
||||
}
|
||||
else if (string.IsNullOrEmpty(Model.Job.DeviceHeldLocation))
|
||||
{
|
||||
<span class="smallMessage"><None/Unknown></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@Model.Job.DeviceHeldLocation
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Held Since:</td>
|
||||
<td><span id="Job_Show_Device_DeviceHeld_DeviceHeld">@CommonHelpers.FriendlyDateAndTitleUser(Model.Job.DeviceHeld, Model.Job.DeviceHeldTechUser)</span></td>
|
||||
</tr>
|
||||
@if (Model.Job.DeviceReadyForReturn.HasValue)
|
||||
{
|
||||
<tr>
|
||||
<td>Ready:</td>
|
||||
<td><span id="Job_Show_Device_DeviceHeld_DeviceReadyForReturn">@CommonHelpers.FriendlyDateAndTitleUser(Model.Job.DeviceReadyForReturn, Model.Job.DeviceReadyForReturnTechUser)</span></td>
|
||||
else if (string.IsNullOrEmpty(Model.Job.DeviceHeldLocation))
|
||||
{
|
||||
<span class="smallMessage"><None/Unknown></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@Model.Job.DeviceHeldLocation
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (Model.Job.DeviceReturnedDate.HasValue)
|
||||
{
|
||||
<tr>
|
||||
<td>Returned:</td>
|
||||
<td><span id="Job_Show_Device_DeviceHeld_DeviceReturnedDate">@CommonHelpers.FriendlyDateAndTitleUser(Model.Job.DeviceReturnedDate, Model.Job.DeviceReturnedTechUser)</span></td>
|
||||
<td>Held Since:</td>
|
||||
<td><span id="Job_Show_Device_DeviceHeld_DeviceHeld">@CommonHelpers.FriendlyDateAndTitleUser(Model.Job.DeviceHeld, Model.Job.DeviceHeldTechUser)</span></td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
@if (canEditLocation)
|
||||
{
|
||||
<script type="text/javascript">
|
||||
@if (Model.Job.DeviceReadyForReturn.HasValue)
|
||||
{
|
||||
<tr>
|
||||
<td>Ready:</td>
|
||||
<td><span id="Job_Show_Device_DeviceHeld_DeviceReadyForReturn">@CommonHelpers.FriendlyDateAndTitleUser(Model.Job.DeviceReadyForReturn, Model.Job.DeviceReadyForReturnTechUser)</span></td>
|
||||
</tr>
|
||||
}
|
||||
@if (Model.Job.DeviceReturnedDate.HasValue)
|
||||
{
|
||||
<tr>
|
||||
<td>Returned:</td>
|
||||
<td><span id="Job_Show_Device_DeviceHeld_DeviceReturnedDate">@CommonHelpers.FriendlyDateAndTitleUser(Model.Job.DeviceReturnedDate, Model.Job.DeviceReturnedTechUser)</span></td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
@if (canEditLocation)
|
||||
{
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
@switch (Model.LocationMode)
|
||||
{
|
||||
@@ -417,85 +428,102 @@
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
@if (Model.Job.User != null)
|
||||
{
|
||||
<td id="Job_Show_User">
|
||||
<div>
|
||||
@if (Model.HasUserPhoto)
|
||||
{
|
||||
<div id="Job_Show_User_Photo_Container">
|
||||
<img id="Job_Show_User_Photo" src="@Url.Action(MVC.API.User.Photo(Model.Job.UserId))" />
|
||||
</div>
|
||||
}
|
||||
<h2 id="Job_Show_User_DisplayName" title="Display Name">
|
||||
@if (Authorization.Has(Claims.User.Show))
|
||||
{@Html.ActionLink(Model.Job.User.DisplayName, MVC.User.Show(Model.Job.UserId))}
|
||||
else
|
||||
{@Model.Job.User.DisplayName}
|
||||
</h2>
|
||||
<div id="Job_Show_User_Id" title="Id">@Model.Job.User.FriendlyId()</div>
|
||||
@if (Authorization.Has(Claims.User.ShowDetails))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Model.Job.User.PhoneNumber))
|
||||
{<div id="Job_Show_User_PhoneNumber" title="Phone Number">Phone: @Model.Job.User.PhoneNumber</div>}
|
||||
if (!string.IsNullOrWhiteSpace(Model.Job.User.EmailAddress))
|
||||
{<div id="Job_Show_User_EmailAddress" title="Email Address">Email: <a href="mailto:@(Model.Job.User.EmailAddress)">@Model.Job.User.EmailAddress</a></div>}
|
||||
</h2>
|
||||
<div id="Job_Show_User_Id" title="Id">@Model.Job.User.FriendlyId()</div>
|
||||
@if (Authorization.Has(Claims.User.ShowDetails))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Model.Job.User.PhoneNumber))
|
||||
{<div id="Job_Show_User_PhoneNumber" title="Phone Number">Phone: @Model.Job.User.PhoneNumber</div>}
|
||||
if (!string.IsNullOrWhiteSpace(Model.Job.User.EmailAddress))
|
||||
{<div id="Job_Show_User_EmailAddress" title="Email Address">Email: <a href="mailto:@(Model.Job.User.EmailAddress)">@Model.Job.User.EmailAddress</a></div>}
|
||||
}
|
||||
@if (Authorization.Has(Claims.User.ShowFlagAssignments))
|
||||
{
|
||||
<div id="Job_Show_User_Flags">
|
||||
@foreach (var flag in Model.Job.User.UserFlagAssignments.Where(f => !f.RemovedDate.HasValue).Select(f => Tuple.Create(f, UserFlagService.GetUserFlag(f.UserFlagId))))
|
||||
{
|
||||
<i class="flag fa fa-@(flag.Item2.Icon) fa-fw d-@(flag.Item2.IconColour)">
|
||||
<span class="details">
|
||||
<span class="name">@flag.Item2.Name</span>@if (flag.Item1.Comments != null)
|
||||
{<span class="comments">@flag.Item1.Comments.ToHtmlComment()</span>}<span class="added">@CommonHelpers.FriendlyDateAndUser(flag.Item1.AddedDate, flag.Item1.AddedUserId)</span>
|
||||
</span>
|
||||
</i>
|
||||
}
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$('#Job_Show_User_Flags')
|
||||
.tooltip({
|
||||
items: 'i.flag',
|
||||
content: function () {
|
||||
var $this = $(this);
|
||||
return $this.children('.details').html();
|
||||
},
|
||||
tooltipClass: 'User_FlagAssignment_Tooltip',
|
||||
position: {
|
||||
my: "right top",
|
||||
at: "right bottom",
|
||||
collision: "flipfit flip"
|
||||
},
|
||||
hade: {
|
||||
effect: ''
|
||||
},
|
||||
close: function (e, ui) {
|
||||
ui.tooltip.hover(
|
||||
function () {
|
||||
$(this).stop(true).fadeTo(100, 1);
|
||||
},
|
||||
function () {
|
||||
$(this).fadeOut(100, function () { $(this).remove(); });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
}
|
||||
@if (Model.Job.WaitingForUserAction.HasValue)
|
||||
{
|
||||
<div id="Job_Show_User_WaitingForUserAction" class="status">
|
||||
<h4>Awaiting Action</h4>
|
||||
Since: <span data-livestamp="@Model.Job.WaitingForUserAction.ToUnixEpoc()">@Model.Job.WaitingForUserAction.ToFullDateTime()</span>
|
||||
</div>
|
||||
}
|
||||
@if (Model.UserDetails != null && Model.UserDetails.Details.Count > 0)
|
||||
{
|
||||
<div id="Job_Show_User_CustomDetails" class="status clearfix">
|
||||
@foreach (var detail in Model.UserDetails.Details)
|
||||
{
|
||||
<div>
|
||||
<strong>@detail.Key:</strong> @Html.Partial(MVC.Shared.Views._CustomDetailValueRender, detail)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
@if (Authorization.Has(Claims.User.ShowFlagAssignments))
|
||||
{
|
||||
<div id="Job_Show_User_Flags">
|
||||
@foreach (var flag in Model.Job.User.UserFlagAssignments.Where(f => !f.RemovedDate.HasValue).Select(f => Tuple.Create(f, UserFlagService.GetUserFlag(f.UserFlagId))))
|
||||
{
|
||||
<i class="flag fa fa-@(flag.Item2.Icon) fa-fw d-@(flag.Item2.IconColour)">
|
||||
<span class="details">
|
||||
<span class="name">@flag.Item2.Name</span>@if (flag.Item1.Comments != null)
|
||||
{<span class="comments">@flag.Item1.Comments.ToHtmlComment()</span>}<span class="added">@CommonHelpers.FriendlyDateAndUser(flag.Item1.AddedDate, flag.Item1.AddedUserId)</span>
|
||||
</span>
|
||||
</i>
|
||||
}
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$('#Job_Show_User_Flags')
|
||||
.tooltip({
|
||||
items: 'i.flag',
|
||||
content: function () {
|
||||
var $this = $(this);
|
||||
return $this.children('.details').html();
|
||||
},
|
||||
tooltipClass: 'User_FlagAssignment_Tooltip',
|
||||
position: {
|
||||
my: "right top",
|
||||
at: "right bottom",
|
||||
collision: "flipfit flip"
|
||||
},
|
||||
hade: {
|
||||
effect: ''
|
||||
},
|
||||
close: function (e, ui) {
|
||||
ui.tooltip.hover(
|
||||
function () {
|
||||
$(this).stop(true).fadeTo(100, 1);
|
||||
},
|
||||
function () {
|
||||
$(this).fadeOut(100, function () { $(this).remove(); });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
}
|
||||
@if (Model.Job.WaitingForUserAction.HasValue)
|
||||
{
|
||||
<div id="Job_Show_User_WaitingForUserAction" class="status">
|
||||
<h4>Awaiting Action</h4>
|
||||
Since: <span data-livestamp="@Model.Job.WaitingForUserAction.ToUnixEpoc()">@Model.Job.WaitingForUserAction.ToFullDateTime()</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
<tr id="Job_Show_Subjects_Actions">
|
||||
<td id="Job_Show_Job_Actions">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
@model System.Collections.Generic.KeyValuePair<string, string>
|
||||
@using System.Text.RegularExpressions
|
||||
@{
|
||||
var emailMatch = Regex.Match(Model.Value, @"^(?<name>.+)\s?<(?<address>.+@.+)>$");
|
||||
if (!emailMatch.Success)
|
||||
{
|
||||
emailMatch = Regex.Match(Model.Value, @"^(?<address>.+@.+)$");
|
||||
}
|
||||
}
|
||||
@if (emailMatch.Success)
|
||||
{
|
||||
var emailAddress = emailMatch.Groups["address"].Value;
|
||||
var emailName = emailAddress;
|
||||
if (emailMatch.Groups["name"].Success)
|
||||
{
|
||||
emailName = emailMatch.Groups["name"].Value;
|
||||
}
|
||||
<a href="mailto:@emailAddress">@emailName</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@Model.Value</span>
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma warning disable 1591
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Disco.Web.Views.Shared
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
#line 2 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
using System.Web;
|
||||
using System.Web.Helpers;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Mvc.Ajax;
|
||||
using System.Web.Mvc.Html;
|
||||
using System.Web.Routing;
|
||||
using System.Web.Security;
|
||||
using System.Web.UI;
|
||||
using System.Web.WebPages;
|
||||
using Disco;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Web;
|
||||
using Disco.Web;
|
||||
using Disco.Web.Extensions;
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")]
|
||||
[System.Web.WebPages.PageVirtualPathAttribute("~/Views/Shared/_CustomDetailValueRender.cshtml")]
|
||||
public partial class _CustomDetailValueRender : Disco.Services.Web.WebViewPage<System.Collections.Generic.KeyValuePair<string, string>>
|
||||
{
|
||||
public _CustomDetailValueRender()
|
||||
{
|
||||
}
|
||||
public override void Execute()
|
||||
{
|
||||
|
||||
#line 3 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
|
||||
var emailMatch = Regex.Match(Model.Value, @"^(?<name>.+)\s?<(?<address>.+@.+)>$");
|
||||
if (!emailMatch.Success)
|
||||
{
|
||||
emailMatch = Regex.Match(Model.Value, @"^(?<address>.+@.+)$");
|
||||
}
|
||||
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
WriteLiteral("\r\n");
|
||||
|
||||
|
||||
#line 10 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
if (emailMatch.Success)
|
||||
{
|
||||
var emailAddress = emailMatch.Groups["address"].Value;
|
||||
var emailName = emailAddress;
|
||||
if (emailMatch.Groups["name"].Success)
|
||||
{
|
||||
emailName = emailMatch.Groups["name"].Value;
|
||||
}
|
||||
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
WriteLiteral(" <a");
|
||||
|
||||
WriteAttribute("href", Tuple.Create(" href=\"", 556), Tuple.Create("\"", 583)
|
||||
, Tuple.Create(Tuple.Create("", 563), Tuple.Create("mailto:", 563), true)
|
||||
|
||||
#line 18 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
, Tuple.Create(Tuple.Create("", 570), Tuple.Create<System.Object, System.Int32>(emailAddress
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
, 570), false)
|
||||
);
|
||||
|
||||
WriteLiteral(">");
|
||||
|
||||
|
||||
#line 18 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
Write(emailName);
|
||||
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
WriteLiteral("</a>\r\n");
|
||||
|
||||
|
||||
#line 19 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
WriteLiteral(" <span>");
|
||||
|
||||
|
||||
#line 22 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
Write(Model.Value);
|
||||
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
WriteLiteral("</span>\r\n");
|
||||
|
||||
|
||||
#line 23 "..\..\Views\Shared\_CustomDetailValueRender.cshtml"
|
||||
}
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
@@ -3,11 +3,18 @@
|
||||
Authorization.Require(Claims.User.Show);
|
||||
|
||||
var currentDeviceAssignments = Model.User.DeviceUserAssignments.Where(dua => !dua.UnassignedDate.HasValue).OrderByDescending(dua => dua.AssignedDate).ToList();
|
||||
Disco.Models.Services.Plugins.Details.DetailsResult deviceDetails;
|
||||
}
|
||||
<table id="User_Show_Subjects">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td id="User_Show_Details">
|
||||
<td id="User_Show_Details" class="clearfix @(Model.HasUserPhoto ? "hasPhoto" : "noPhoto")">
|
||||
@if (Model.HasUserPhoto)
|
||||
{
|
||||
<div id="User_Show_Details_Photo_Container">
|
||||
<img id="User_Show_Details_Photo" src="@Url.Action(MVC.API.User.Photo(Model.User.UserId))" />
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<div id="User_Show_Details_Identity">
|
||||
<table class="none verticalHeadings">
|
||||
@@ -66,6 +73,21 @@
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@if (Model.UserDetails != null && Model.UserDetails.Details.Count > 0)
|
||||
{
|
||||
<div id="User_Show_CustomDetails" class="status clearfix">
|
||||
<table class="none verticalHeadings">
|
||||
@foreach (var detail in Model.UserDetails.Details)
|
||||
{
|
||||
<tr>
|
||||
<td>@detail.Key:</td>
|
||||
<td>@Html.Partial(MVC.Shared.Views._CustomDetailValueRender, detail)</td>
|
||||
</tr>
|
||||
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@if (Authorization.Has(Claims.User.Actions.GenerateDocuments))
|
||||
{
|
||||
<div id="User_Show_GenerateDocument_Container" class="status">
|
||||
@@ -91,52 +113,53 @@
|
||||
<ul id="CreateJob_Assignments" class="none">
|
||||
@foreach (var assignment in currentDeviceAssignments)
|
||||
{
|
||||
<li class="CreateJob_Assignment clearfix" data-createjoburl="@Url.Action(MVC.Job.Create(assignment.DeviceSerialNumber, Model.User.UserId))">
|
||||
<table class="none">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="5">
|
||||
<img class="CreateJob_Assignment_Image" alt="Model Image" src="@Url.Action(MVC.API.DeviceModel.Image(assignment.Device.DeviceModel.Id, assignment.Device.DeviceModel.ImageHash()))" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Serial Number:
|
||||
</td>
|
||||
<td>
|
||||
<span>@assignment.Device.SerialNumber</span> (<span>@assignment.Device.ComputerName</span>)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Model:
|
||||
</td>
|
||||
<td>
|
||||
<span>@assignment.Device.DeviceModel.ToString()</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Asset:</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(assignment.Device.AssetNumber))
|
||||
{
|
||||
<span>@assignment.Device.AssetNumber</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="smallMessage">Unknown</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Assigned:</td>
|
||||
<td>
|
||||
<span>@CommonHelpers.FriendlyDate(assignment.AssignedDate)</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>}
|
||||
<li class="CreateJob_Assignment clearfix" data-createjoburl="@Url.Action(MVC.Job.Create(assignment.DeviceSerialNumber, Model.User.UserId))">
|
||||
<table class="none">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="5">
|
||||
<img class="CreateJob_Assignment_Image" alt="Model Image" src="@Url.Action(MVC.API.DeviceModel.Image(assignment.Device.DeviceModel.Id, assignment.Device.DeviceModel.ImageHash()))" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Serial Number:
|
||||
</td>
|
||||
<td>
|
||||
<span>@assignment.Device.SerialNumber</span> (<span>@assignment.Device.ComputerName</span>)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Model:
|
||||
</td>
|
||||
<td>
|
||||
<span>@assignment.Device.DeviceModel.ToString()</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Asset:</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(assignment.Device.AssetNumber))
|
||||
{
|
||||
<span>@assignment.Device.AssetNumber</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="smallMessage">Unknown</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Assigned:</td>
|
||||
<td>
|
||||
<span>@CommonHelpers.FriendlyDate(assignment.AssignedDate)</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -376,6 +399,16 @@
|
||||
<span class="User_Show_AssignedDevices_CurrentAssignment_Assigned">@CommonHelpers.FriendlyDate(assignment.AssignedDate)</span>
|
||||
</td>
|
||||
</tr>
|
||||
@if (Model.AssignedDevicesDetails != null && Model.AssignedDevicesDetails.TryGetValue(assignment.DeviceSerialNumber, out deviceDetails) && deviceDetails.Details.Count > 0)
|
||||
{
|
||||
foreach (var detail in deviceDetails.Details)
|
||||
{
|
||||
<tr>
|
||||
<td>@detail.Key:</td>
|
||||
<td>@Html.Partial(MVC.Shared.Views._CustomDetailValueRender, detail)</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user