Feature: Device Batch Attachments
allows for attachments to be uploaded and associated with Device Batches
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Devices;
|
||||
using Disco.Services.Devices.ManagedGroups;
|
||||
using Disco.Services.Interop;
|
||||
using Disco.Services.Interop.ActiveDirectory;
|
||||
using Disco.Services.Tasks;
|
||||
using Disco.Services.Web;
|
||||
@@ -607,5 +609,137 @@ namespace Disco.Web.Areas.API.Controllers
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Attachements
|
||||
|
||||
[DiscoAuthorize(Claims.Config.DeviceBatch.Show)]
|
||||
[OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
|
||||
public virtual ActionResult AttachmentDownload(int id)
|
||||
{
|
||||
var attachment = Database.DeviceBatchAttachments.Find(id);
|
||||
if (attachment != null)
|
||||
{
|
||||
var filePath = attachment.RepositoryFilename(Database);
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
return File(filePath, attachment.MimeType, attachment.Filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpNotFound("Attachment reference exists, but file not found");
|
||||
}
|
||||
}
|
||||
return HttpNotFound("Invalid Attachment Number");
|
||||
}
|
||||
|
||||
[DiscoAuthorize(Claims.Config.DeviceBatch.Show)]
|
||||
[OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
|
||||
public virtual ActionResult AttachmentThumbnail(int id)
|
||||
{
|
||||
var attachment = Database.DeviceBatchAttachments.Find(id);
|
||||
if (attachment != null)
|
||||
{
|
||||
var thumbPath = attachment.RepositoryThumbnailFilename(Database);
|
||||
if (System.IO.File.Exists(thumbPath))
|
||||
{
|
||||
if (thumbPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
|
||||
return File(thumbPath, "image/png");
|
||||
else
|
||||
return File(thumbPath, "image/jpg");
|
||||
}
|
||||
else
|
||||
return File(ClientSource.Style.Images.AttachmentTypes.MimeTypeIcons.Icon(attachment.MimeType), "image/png");
|
||||
}
|
||||
return HttpNotFound("Invalid Attachment Number");
|
||||
}
|
||||
|
||||
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
|
||||
public virtual ActionResult AttachmentUpload(int id, string Comments)
|
||||
{
|
||||
var batch = Database.DeviceBatches.Find(id);
|
||||
if (batch != null)
|
||||
{
|
||||
if (Request.Files.Count > 0)
|
||||
{
|
||||
var file = Request.Files.Get(0);
|
||||
if (file.ContentLength > 0)
|
||||
{
|
||||
var contentType = file.ContentType;
|
||||
if (string.IsNullOrEmpty(contentType) || contentType.Equals("unknown/unknown", StringComparison.OrdinalIgnoreCase))
|
||||
contentType = MimeTypes.ResolveMimeType(file.FileName);
|
||||
|
||||
var attachment = new Disco.Models.Repository.DeviceBatchAttachment()
|
||||
{
|
||||
DeviceBatchId = batch.Id,
|
||||
TechUserId = CurrentUser.UserId,
|
||||
Filename = file.FileName,
|
||||
MimeType = contentType,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = Comments
|
||||
};
|
||||
Database.DeviceBatchAttachments.Add(attachment);
|
||||
Database.SaveChanges();
|
||||
|
||||
attachment.SaveAttachment(Database, file.InputStream);
|
||||
|
||||
attachment.GenerateThumbnail(Database);
|
||||
|
||||
return Json(attachment.Id, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
}
|
||||
throw new Exception("No Attachment Uploaded");
|
||||
}
|
||||
throw new Exception("Invalid Device Batch Id");
|
||||
}
|
||||
|
||||
[DiscoAuthorize(Claims.Config.DeviceBatch.Show)]
|
||||
public virtual ActionResult Attachment(int id)
|
||||
{
|
||||
var attachment = Database.DeviceBatchAttachments.Include("TechUser").Where(m => m.Id == id).FirstOrDefault();
|
||||
if (attachment != null)
|
||||
{
|
||||
|
||||
var m = new Models.Attachment.AttachmentModel()
|
||||
{
|
||||
Attachment = Models.Attachment._AttachmentModel.FromAttachment(attachment),
|
||||
Result = "OK"
|
||||
};
|
||||
|
||||
return Json(m, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
return Json(new Models.Attachment.AttachmentModel() { Result = "Invalid Attachment Number" }, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
[DiscoAuthorize(Claims.Config.DeviceBatch.Show)]
|
||||
public virtual ActionResult Attachments(int id)
|
||||
{
|
||||
var batch = Database.DeviceBatches.Include("DeviceBatchAttachments.TechUser").Where(m => m.Id == id).FirstOrDefault();
|
||||
if (batch != null)
|
||||
{
|
||||
var m = new Models.Attachment.AttachmentsModel()
|
||||
{
|
||||
Attachments = batch.DeviceBatchAttachments.Select(a => Models.Attachment._AttachmentModel.FromAttachment(a)).ToList(),
|
||||
Result = "OK"
|
||||
};
|
||||
|
||||
return Json(m, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
return Json(new Models.Attachment.AttachmentsModel() { Result = "Invalid Device Batch Id" }, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
|
||||
public virtual ActionResult AttachmentRemove(int id)
|
||||
{
|
||||
var attachment = Database.DeviceBatchAttachments.Include("TechUser").Where(m => m.Id == id).FirstOrDefault();
|
||||
if (attachment != null)
|
||||
{
|
||||
attachment.OnDelete(Database);
|
||||
Database.SaveChanges();
|
||||
return Json("OK", JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
return Json("Invalid Attachment Number", JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,22 @@ namespace Disco.Web.Areas.API.Models.Attachment
|
||||
MimeType = da.MimeType
|
||||
};
|
||||
}
|
||||
public static _AttachmentModel FromAttachment(Disco.Models.Repository.DeviceBatchAttachment attachment)
|
||||
{
|
||||
return new _AttachmentModel
|
||||
{
|
||||
ParentId = attachment.DeviceBatchId.ToString(),
|
||||
Id = attachment.Id,
|
||||
AuthorId = attachment.TechUserId,
|
||||
Author = attachment.TechUser.ToStringFriendly(),
|
||||
Timestamp = attachment.Timestamp,
|
||||
Comments = attachment.Comments,
|
||||
DocumentTemplateId = null, // not supported for DeviceBatchAttachment
|
||||
DocumentTemplateDescription = null, // not supported for DeviceBatchAttachment
|
||||
Filename = attachment.Filename,
|
||||
MimeType = attachment.MimeType
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Disco.Models.UI.Config.DeviceBatch;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.UI.Config.DeviceBatch;
|
||||
using Disco.Services;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Devices;
|
||||
@@ -20,7 +21,9 @@ namespace Disco.Web.Areas.Config.Controllers
|
||||
|
||||
if (id.HasValue)
|
||||
{
|
||||
var m = Database.DeviceBatches.Where(db => db.Id == id.Value)
|
||||
var m = Database.DeviceBatches
|
||||
.Include(nameof(DeviceBatch.DeviceBatchAttachments))
|
||||
.Where(db => db.Id == id.Value)
|
||||
.Select(db => new Models.DeviceBatch.ShowModel()
|
||||
{
|
||||
DeviceBatch = db,
|
||||
|
||||
@@ -13,11 +13,16 @@
|
||||
Model.DeviceBatch.AssignedUsersLinkedGroup == null &&
|
||||
Model.DeviceBatch.DevicesLinkedGroup == null;
|
||||
|
||||
Html.BundleDeferred("~/Style/Shadowbox");
|
||||
Html.BundleDeferred("~/ClientScripts/Modules/Shadowbox");
|
||||
Html.BundleDeferred("~/ClientScripts/Modules/jQuery-SignalR");
|
||||
|
||||
if (canConfig)
|
||||
{
|
||||
Html.BundleDeferred("~/ClientScripts/Modules/Disco-PropertyChangeHelpers");
|
||||
Html.BundleDeferred("~/ClientScripts/Modules/Disco-AjaxHelperIcons");
|
||||
Html.BundleDeferred("~/ClientScripts/Modules/tinymce");
|
||||
Html.BundleDeferred("~/ClientScripts/Modules/Disco-AttachmentUploader");
|
||||
}
|
||||
}
|
||||
<div class="form deviceBatches@(hideAdvanced ? " Config_HideAdvanced" : null)" style="width: 730px">
|
||||
@@ -660,6 +665,301 @@
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Attachments:</th>
|
||||
<td>
|
||||
|
||||
<div id="DeviceBatch_Attachments" class="@(canConfig ? "canAddAttachments" : "cannotAddAttachments")">
|
||||
<div class="Disco-AttachmentUpload-DropTarget">
|
||||
<h2>Drop Attachments Here</h2>
|
||||
</div>
|
||||
<div class="attachmentOutput">
|
||||
@if (Model.DeviceBatch.DeviceBatchAttachments != null)
|
||||
{
|
||||
foreach (var attachment in Model.DeviceBatch.DeviceBatchAttachments)
|
||||
{
|
||||
<a href="@Url.Action(MVC.API.DeviceBatch.AttachmentDownload(attachment.Id))" data-attachmentid="@attachment.Id" data-mimetype="@attachment.MimeType">
|
||||
<span class="icon" title="@attachment.Filename">
|
||||
<img alt="Attachment Thumbnail" src="@(Url.Action(MVC.API.DeviceBatch.AttachmentThumbnail(attachment.Id)))" />
|
||||
</span>
|
||||
<span class="comments" title="@attachment.Comments">
|
||||
@attachment.Comments
|
||||
</span><span class="author">@attachment.TechUser.ToString()</span>@if (canConfig)
|
||||
{<text><span class="remove fa fa-times-circle"></span></text>}<span class="timestamp" title="@attachment.Timestamp.ToFullDateTime()" data-livestamp="@attachment.Timestamp.ToUnixEpoc()">@attachment.Timestamp.ToFullDateTime()</span>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (canConfig)
|
||||
{
|
||||
<div class="Disco-AttachmentUpload-Progress"></div>
|
||||
<div class="attachmentInput clearfix">
|
||||
<span class="action upload fa fa-upload disabled" title="Attach File"></span><span class="action photo fa fa-camera disabled" title="Capture Image"></span>
|
||||
</div>
|
||||
<div id="dialogRemoveAttachment" class="dialog" title="Remove this Attachment?">
|
||||
<p>
|
||||
<i class="fa fa-exclamation-triangle fa-lg"></i> Are you sure?
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
<script type="text/javascript">
|
||||
Shadowbox.init({
|
||||
skipSetup: true,
|
||||
modal: true
|
||||
});
|
||||
$(function () {
|
||||
var $Attachments = $('#DeviceBatch_Attachments');
|
||||
var $attachmentOutput = $Attachments.find('.attachmentOutput');
|
||||
var $attachmentDownloadHost;
|
||||
|
||||
var $dialogRemoveAttachment = null;
|
||||
|
||||
// Connect to Hub
|
||||
var hub = $.connection.deviceBatchUpdates;
|
||||
|
||||
// Map Functions
|
||||
hub.client.addAttachment = onAddAttachment;
|
||||
hub.client.removeAttachment = onRemoveAttachment;
|
||||
|
||||
$.connection.hub.qs = { DeviceBatchId: '@(Model.DeviceBatch.Id)' };
|
||||
$.connection.hub.error(onHubFailed);
|
||||
$.connection.hub.disconnected(onHubFailed);
|
||||
|
||||
$.connection.hub.reconnecting(function () {
|
||||
$Attachments.find('span.action').addClass('disabled');
|
||||
});
|
||||
$.connection.hub.reconnected(function () {
|
||||
$Attachments.find('span.action').removeClass('disabled');
|
||||
});
|
||||
|
||||
// Start Connection
|
||||
$.connection.hub.start(function () {
|
||||
$Attachments.find('span.action').removeClass('disabled');
|
||||
}).fail(onHubFailed);
|
||||
|
||||
function onHubFailed(error) {
|
||||
// Disable UI
|
||||
$Attachments.find('span.action').addClass('disabled');
|
||||
|
||||
// Show Dialog Message
|
||||
if ($('.disconnected-dialog').length == 0) {
|
||||
$('<div>')
|
||||
.addClass('dialog disconnected-dialog')
|
||||
.html('<h3><span class="fa-stack fa-lg"><i class="fa fa-wifi fa-stack-1x"></i><i class="fa fa-ban fa-stack-2x error"></i></span>Disconnected from the Disco ICT Server</h3><div>This page is not receiving live updates. Please ensure you are connected to the server, then refresh this page to enable features.</div>')
|
||||
.dialog({
|
||||
resizable: false,
|
||||
title: 'Disconnected',
|
||||
width: 400,
|
||||
modal: true,
|
||||
buttons: {
|
||||
'Refresh Now': function () {
|
||||
$(this).dialog('option', 'buttons', null);
|
||||
window.location.reload(true);
|
||||
},
|
||||
'Close': function () {
|
||||
$(this).dialog('destroy');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onAddAttachment(id, quick) {
|
||||
var data = { id: id };
|
||||
$.ajax({
|
||||
url: '@Url.Action(MVC.API.DeviceBatch.Attachment())',
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
success: function (d) {
|
||||
if (d.Result == 'OK') {
|
||||
var a = d.Attachment;
|
||||
@if (canConfig)
|
||||
{
|
||||
<text>buildAttachment(a, true, quick);</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>buildAttachment(a, false, quick);</text>
|
||||
}
|
||||
} else {
|
||||
alert('Unable to add attachment: ' + d.Result);
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
alert('Unable to add attachment: ' + textStatus);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildAttachment(a, canRemove, quick) {
|
||||
var t = '<a><span class="icon"><img alt="Attachment Thumbnail" /></span><span class="comments"></span><span class="author"></span>';
|
||||
if (canRemove)
|
||||
t += '<span class="remove fa fa-times-circle"></span>';
|
||||
t += '<span class="timestamp"></span></a>';
|
||||
|
||||
var e = $(t);
|
||||
|
||||
e.attr('data-attachmentid', a.Id).attr('data-mimetype', a.MimeType).attr('href', '@(Url.Action(MVC.API.DeviceBatch.AttachmentDownload()))/' + a.Id);
|
||||
e.find('.comments').text(a.Description);
|
||||
e.find('.author').text(a.Author);
|
||||
e.find('.timestamp').text(a.TimestampFull).attr('title', a.TimestampFull).livestamp(a.TimestampUnixEpoc);
|
||||
if (canRemove)
|
||||
e.find('.remove').click(removeAttachment);
|
||||
if (!quick)
|
||||
e.hide();
|
||||
$attachmentOutput.append(e);
|
||||
if (!quick)
|
||||
e.show('slow');
|
||||
if (a.MimeType.toLowerCase().indexOf('image/') == 0)
|
||||
e.shadowbox({ gallery: 'attachments', player: 'img', title: a.Description });
|
||||
else
|
||||
e.click(onDownload);
|
||||
|
||||
// Add Thumbnail
|
||||
var buildThumbnail = function () {
|
||||
var retryCount = 0;
|
||||
var img = e.find('.icon img');
|
||||
|
||||
var setThumbnailUrl = function () {
|
||||
img.attr('src', '@(Url.Action(MVC.API.DeviceBatch.AttachmentThumbnail()))/' + a.Id + '?v=' + retryCount);
|
||||
};
|
||||
img.on('error', function () {
|
||||
img.addClass('loading');
|
||||
retryCount++;
|
||||
if (retryCount < 6)
|
||||
window.setTimeout(setThumbnailUrl, retryCount * 250);
|
||||
});
|
||||
img.on('load', function () {
|
||||
img.removeClass('loading');
|
||||
});
|
||||
window.setTimeout(setThumbnailUrl, 100);
|
||||
};
|
||||
buildThumbnail();
|
||||
}
|
||||
|
||||
function onRemoveAttachment(id) {
|
||||
var a = $attachmentOutput.find('a[data-attachmentid=' + id + ']');
|
||||
|
||||
a.hide(300).delay(300).queue(function () {
|
||||
var $this = $(this);
|
||||
if ($this.attr('data-mimetype').toLowerCase().indexOf('image/') == 0)
|
||||
Shadowbox.removeCache(this);
|
||||
$this.find('.timestamp').livestamp('destroy');
|
||||
$this.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function onDownload() {
|
||||
var $this = $(this);
|
||||
var url = $this.attr('href');
|
||||
|
||||
if ($.connection && $.connection.hub && $.connection.hub.transport &&
|
||||
$.connection.hub.transport.name == 'foreverFrame') {
|
||||
// SignalR active with foreverFrame transport - use popup window
|
||||
window.open(url, '_blank', 'height=150,width=250,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no');
|
||||
} else {
|
||||
// use iFrame
|
||||
if (!$attachmentDownloadHost) {
|
||||
$attachmentDownloadHost = $('<iframe>')
|
||||
.attr({ 'src': url, 'title': 'Attachment Download Host' })
|
||||
.addClass('hidden')
|
||||
.appendTo('body')
|
||||
.contents();
|
||||
} else {
|
||||
$attachmentDownloadHost[0].location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@if (canConfig)
|
||||
{<text>
|
||||
//#region Add Attachments
|
||||
var attachmentUploader = new document.Disco.AttachmentUploader(
|
||||
'@(Url.Action(MVC.API.DeviceBatch.AttachmentUpload(Model.DeviceBatch.Id, null)))',
|
||||
$Attachments.find('.Disco-AttachmentUpload-DropTarget'),
|
||||
$Attachments.find('.Disco-AttachmentUpload-Progress'));
|
||||
|
||||
var $attachmentInput = $Attachments.find('.attachmentInput');
|
||||
$attachmentInput.find('.photo').click(function () {
|
||||
if ($(this).hasClass('disabled'))
|
||||
alert('Disconnected from the Disco ICT Server, please refresh this page and try again');
|
||||
else
|
||||
attachmentUploader.uploadImage();
|
||||
});
|
||||
$attachmentInput.find('.upload').click(function () {
|
||||
if ($(this).hasClass('disabled'))
|
||||
alert('Disconnected from the Disco ICT Server, please refresh this page and try again');
|
||||
else
|
||||
attachmentUploader.uploadFiles();
|
||||
});
|
||||
//#endregion
|
||||
//#region Remove Attachments
|
||||
$attachmentOutput.find('span.remove').click(removeAttachment);
|
||||
|
||||
function removeAttachment() {
|
||||
$this = $(this).closest('a');
|
||||
|
||||
var data = { id: $this.attr('data-attachmentid') };
|
||||
|
||||
if (!$dialogRemoveAttachment) {
|
||||
$dialogRemoveAttachment = $('#dialogRemoveAttachment').dialog({
|
||||
resizable: false,
|
||||
height: 140,
|
||||
modal: true,
|
||||
autoOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
$dialogRemoveAttachment.dialog("enable");
|
||||
$dialogRemoveAttachment.dialog('option', 'buttons', {
|
||||
"Remove": function () {
|
||||
$dialogRemoveAttachment.dialog("disable");
|
||||
$dialogRemoveAttachment.dialog("option", "buttons", null);
|
||||
$.ajax({
|
||||
url: '@Url.Action(MVC.API.DeviceBatch.AttachmentRemove())',
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
success: function (d) {
|
||||
if (d == 'OK') {
|
||||
// Do nothing, await SignalR notification
|
||||
} else {
|
||||
alert('Unable to remove attachment: ' + d);
|
||||
}
|
||||
$dialogRemoveAttachment.dialog("close");
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
alert('Unable to remove attachment: ' + textStatus);
|
||||
$dialogRemoveAttachment.dialog("close");
|
||||
}
|
||||
});
|
||||
},
|
||||
Cancel: function () {
|
||||
$dialogRemoveAttachment.dialog("close");
|
||||
}
|
||||
});
|
||||
|
||||
$dialogRemoveAttachment.dialog('open');
|
||||
|
||||
return false;
|
||||
}
|
||||
//#endregion
|
||||
</text>}
|
||||
|
||||
$attachmentOutput.children('a').each(function () {
|
||||
$this = $(this);
|
||||
if ($this.attr('data-mimetype').toLowerCase().indexOf('image/') == 0)
|
||||
$this.shadowbox({ gallery: 'attachments', player: 'img', title: $this.find('.comments').text() });
|
||||
else
|
||||
$this.click(onDownload);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
@if (hideAdvanced)
|
||||
{
|
||||
<tr>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user