423 lines
20 KiB
Plaintext
423 lines
20 KiB
Plaintext
@{
|
|
Authorization.Require(Claims.Config.Enrolment.ShowStatus);
|
|
|
|
ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "Device Enrolment", MVC.Config.Enrolment.Index(), "Status");
|
|
Html.BundleDeferred("~/ClientScripts/Modules/Knockout");
|
|
Html.BundleDeferred("~/ClientScripts/Modules/jQuery-SignalR");
|
|
Html.BundleDeferred("~/ClientScripts/Modules/jQuery-Isotope");
|
|
}
|
|
<div id="enrolStatus">
|
|
@Html.AntiForgeryToken()
|
|
<div id="noSessions" data-bind="visible: noSessions">
|
|
<h2>No enrolment sessions today</h2>
|
|
</div>
|
|
<div id="sessions" data-bind="visible: !noSessions(), foreach: { data: sessions, afterRender: sessionRendered, afterAdd: sessionAdded }" style="display: none">
|
|
<div class="session" data-bind="style: { backgroundImage: deviceModelImageUrl }, click: select">
|
|
<h3>
|
|
<span data-bind="text: title"></span>
|
|
<span class="details" data-bind="text: '(' + deviceModelDescription() + ')'"></span>
|
|
<span class="pending" data-bind="visible: isPending"><code data-bind="text: pendingIdentifier"></code> <i class="fa fa-exclamation-circle"></i></span>
|
|
|
|
</h3>
|
|
<p class="sessionStart" data-bind="text: startTime"></p>
|
|
<p class="sessionStatus" data-bind="text: progressStatus"></p>
|
|
<div data-bind="visible: !sessionEnded() && progressValue >= 0, progressValue: progressValue" class="sessionProgress"></div>
|
|
</div>
|
|
</div>
|
|
<div id="dialogSession" data-bind="with: currentSession">
|
|
<div class="sessionProgress clearfix">
|
|
<p class="sessionStart" data-bind="text: startTime"></p>
|
|
<p class="sessionStatus" data-bind="text: progressStatus"></p>
|
|
<div data-bind="visible: !sessionEnded() && progressValue >= 0, progressValue: progressValue"></div>
|
|
<div id="formResolveSessionPending" data-bind="visible: isPending">
|
|
@using (Html.BeginForm(MVC.API.Enrolment.ResolveSessionPending(), FormMethod.Post))
|
|
{
|
|
<code data-bind="text: pendingIdentifier"></code>
|
|
@Html.AntiForgeryToken();
|
|
<input type="hidden" name="sessionId" data-bind="value: id" />
|
|
<div class="reason">
|
|
<input type="text" name="reason" placeholder="Reason (optional)" />
|
|
</div>
|
|
<div class="buttons">
|
|
<button type="button" value="True" class="button">Approve</button>
|
|
<button type="button" value="False" class="button">Reject</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="sessionHeader clearfix" data-bind="style: { backgroundImage: deviceModelImageUrl }">
|
|
<h2><a href="" target="_blank" data-bind="text: title, attr: { href: deviceUrl }"></a></h2>
|
|
<h3 data-bind="text: deviceModelDescription"></h3>
|
|
<table data-bind="if: sessionDeviceInfo">
|
|
<tr>
|
|
<th style="width: 128px">Computer Name:</th>
|
|
<td data-bind="text: sessionDeviceInfo().Arguments[3]"></td>
|
|
</tr>
|
|
<tr>
|
|
<th style="width: 128px">UUID:</th>
|
|
<td data-bind="text: sessionDeviceInfo().Arguments[2]"></td>
|
|
</tr>
|
|
<tr>
|
|
<th style="width: 128px">LAN Mac Address:</th>
|
|
<td data-bind="text: sessionDeviceInfo().Arguments[4]"></td>
|
|
</tr>
|
|
<tr>
|
|
<th style="width: 128px">WLAN Mac Address:</th>
|
|
<td data-bind="text: sessionDeviceInfo().Arguments[5]"></td>
|
|
</tr>
|
|
<tr>
|
|
<th style="width: 128px">Manufacturer/Model:</th>
|
|
<td data-bind="text: sessionDeviceInfo().Arguments[6] + ' ' + sessionDeviceInfo().Arguments[7]"></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="sessionInfoContainer clearfix">
|
|
<div class="sessionInfoMessages">
|
|
<table class="logEventsViewport">
|
|
<thead>
|
|
<tr>
|
|
<th class="icon"> </th>
|
|
<th class="message">Message</th>
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
<div class="logEventsViewportContainer">
|
|
<div class="logEventsViewportNoLogs" data-bind="visible: messages().length == 0" style="display: none">No logs</div>
|
|
<table class="logEventsViewport" data-bind="visible: messages().length > 0" style="display: none">
|
|
<tbody data-bind="foreach: messages">
|
|
<tr>
|
|
<td class="icon"><i class="fa" data-bind="css: { 'fa-info-circle': EventTypeSeverity == 0, 'fa-exclamation-triangle': EventTypeSeverity == 1, 'fa-exclamation-circle': EventTypeSeverity == 2 }"></i></td>
|
|
<td class="message" data-bind="text: FormattedMessage, attr: { title: EventTypeName }"></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="sessionInfoConsole" data-bind="foreach: console">
|
|
<span data-bind="text: Arguments[1]"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script type="text/javascript">
|
|
ko.bindingHandlers.progressValue = {
|
|
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
|
|
var v = ko.utils.unwrapObservable(valueAccessor());
|
|
var vInt = parseInt(v);
|
|
if (vInt >= 0) {
|
|
$element = $(element);
|
|
if (!$element.is('.ui-progressbar'))
|
|
$element.progressbar();
|
|
$(element).progressbar('option', 'value', vInt);
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
<script type="text/javascript">
|
|
$(function () {
|
|
var vm;
|
|
var host = $('#enrolStatus');
|
|
var hostSessions = $('#sessions');
|
|
var hostDialogSessions = $('#dialogSession');
|
|
//var hostDialogSessionsProgress = $('#dialogSession').find('.sessionProgress');
|
|
var deviceModels = {};
|
|
var logHub;
|
|
var deviceBaseUrl = '@(Url.Action(MVC.Device.Show()))/'
|
|
var deviceModelImageUrl = '@(Url.Action(MVC.API.DeviceModel.Image()))/'
|
|
var iconWarningUrl = 'url(@(Links.ClientSource.Style.Images.Status.warning32_png))';
|
|
var iconErrorUrl = 'url(@(Links.ClientSource.Style.Images.Status.fail32_png))';
|
|
|
|
function pageViewModel() {
|
|
var self = this;
|
|
|
|
self.noSessions = ko.observable(true);
|
|
self.sessions = ko.observableArray();
|
|
self.sessionIndex = {};
|
|
|
|
self.isotopeInited = false;
|
|
|
|
self.currentSession = ko.observable();
|
|
|
|
self.sessionRendered = function (e, d) {
|
|
if (!d.sessionEnded()) {
|
|
d.progressbar = $(e).find('.sessionProgress').progressbar();
|
|
}
|
|
};
|
|
self.sessionAdded = function (e, d) {
|
|
if (self.isotopeInited) {
|
|
hostSessions.isotope('reloadItems').isotope({ sortBy: 'original-order' });
|
|
}
|
|
};
|
|
}
|
|
function sessionViewModel(id) {
|
|
var self = this;
|
|
|
|
self.id = id;
|
|
self.title = ko.observable(id);
|
|
self.isPending = ko.observable(false);
|
|
self.pendingIdentifier = ko.observable();
|
|
self.messages = ko.observableArray();
|
|
self.console = ko.observableArray();
|
|
self.serialNumber = ko.observable();
|
|
self.sessionDeviceInfo = ko.observable();
|
|
self.progressStatus = ko.observable();
|
|
self.progressValue = ko.observable();
|
|
self.startTime = ko.observable();
|
|
self.sessionEnded = ko.observable(false);
|
|
self.progressbar = null;
|
|
self.hasError = ko.observable(false);
|
|
self.hasWarning = ko.observable(false);
|
|
self.deviceModelId = ko.observable();
|
|
self.deviceModelDescription = ko.computed(function () {
|
|
var deviceModelId = self.deviceModelId();
|
|
var sessionDeviceInfo = self.sessionDeviceInfo();
|
|
if (deviceModelId) {
|
|
var dm = deviceModels[deviceModelId];
|
|
if (dm) {
|
|
if (dm.Description)
|
|
return dm.Description;
|
|
else
|
|
return dm.Manufacturer + ' ' + dm.Model;
|
|
}
|
|
}
|
|
if (sessionDeviceInfo) {
|
|
return sessionDeviceInfo.Arguments[6] + ' ' + sessionDeviceInfo.Arguments[7];
|
|
}
|
|
});
|
|
self.deviceUrl = ko.computed(function () {
|
|
var serialNumber = self.serialNumber();
|
|
if (serialNumber)
|
|
return deviceBaseUrl + serialNumber;
|
|
else
|
|
return null;
|
|
});
|
|
self.deviceModelImageUrl = ko.computed(function () {
|
|
var deviceModelImage;
|
|
if (self.deviceModelId())
|
|
deviceModelImage = 'url(' + deviceModelImageUrl + self.deviceModelId() + ')';
|
|
else
|
|
deviceModelImage = 'url(' + deviceModelImageUrl + ')';
|
|
if (self.hasError())
|
|
return iconErrorUrl + ', ' + deviceModelImage;
|
|
else
|
|
if (self.hasWarning())
|
|
return iconWarningUrl + ', ' + deviceModelImage;
|
|
else
|
|
return 'none, ' + deviceModelImage;
|
|
});
|
|
self.select = function (e, d) {
|
|
vm.currentSession(self);
|
|
hostDialogSessions.dialog('open');
|
|
hostDialogSessions.dialog('option', 'title', 'Device Enrolment: ' + self.title());
|
|
}
|
|
}
|
|
|
|
function parseLog(log) {
|
|
if (log.ModuleId === 50 && log.Arguments && log.Arguments.length > 0) {
|
|
// find session
|
|
var sessionId = log.Arguments[0];
|
|
var session = vm.sessionIndex[sessionId];
|
|
if (!session && log.EventTypeId === 10) { // Starting Session (Ignore 'partial' sessions)
|
|
session = new sessionViewModel(sessionId);
|
|
vm.sessionIndex[sessionId] = session;
|
|
vm.sessions.unshift(session);
|
|
vm.noSessions(false);
|
|
}
|
|
if (session) {
|
|
switch (log.EventTypeId) {
|
|
case 10: // SessionStarting
|
|
session.title(log.Arguments[1]);
|
|
session.startTime(log.FormattedTimestamp.substring(log.FormattedTimestamp.indexOf(' ') + 1));
|
|
session.messages.unshift(log);
|
|
break;
|
|
case 11: // SessionProgress
|
|
//session.progressbar.progressbar('option', 'value', log.Arguments[1]);
|
|
session.progressValue(log.Arguments[1]);
|
|
session.progressStatus(log.Arguments[2]);
|
|
break;
|
|
case 12: // SessionDevice
|
|
session.title(log.Arguments[1]);
|
|
session.serialNumber(log.Arguments[1]);
|
|
if (log.Arguments.length >= 3 && log.Arguments[2])
|
|
session.deviceModelId(log.Arguments[2]);
|
|
break;
|
|
break;
|
|
case 13: // SessionDeviceInfo
|
|
session.title(log.Arguments[1]);
|
|
session.serialNumber(log.Arguments[1]);
|
|
session.sessionDeviceInfo(log);
|
|
if (log.Arguments.length >= 10 && log.Arguments[9])
|
|
session.deviceModelId(log.Arguments[9]);
|
|
break;
|
|
case 14: // SessionPending
|
|
session.isPending(true);
|
|
session.pendingIdentifier(log.Arguments[4]);
|
|
session.messages.unshift(log);
|
|
session.progressValue(-1);
|
|
session.progressStatus('Pending enrolment approval');
|
|
break;
|
|
case 15: // SessionPendingApproved
|
|
session.isPending(false);
|
|
session.messages.unshift(log);
|
|
session.progressValue(-1);
|
|
session.progressStatus('Enrolment approval, waiting for client');
|
|
break;
|
|
case 16: // SessionPendingRejected
|
|
session.isPending(false);
|
|
session.messages.unshift(log);
|
|
session.progressValue(-1);
|
|
session.progressStatus('Enrolment rejected, waiting for client');
|
|
break;
|
|
case 17: // SessionContinuing
|
|
session.isPending(false);
|
|
session.messages.unshift(log);
|
|
break;
|
|
case 20: // SessionFinished
|
|
session.sessionEnded(true);
|
|
session.isPending(false);
|
|
if (session.hasError())
|
|
session.progressStatus('Enrolment Finished with an Error');
|
|
else
|
|
if (session.hasWarning())
|
|
session.progressStatus('Enrolment Finished with a Warning');
|
|
else
|
|
session.progressStatus('Enrolment Finished Successfully');
|
|
session.messages.unshift(log);
|
|
break;
|
|
case 21: // SessionDiagnosticInformation
|
|
session.console.push(log);
|
|
break;
|
|
case 22: // SessionWarning
|
|
session.hasWarning(true);
|
|
session.messages.unshift(log);
|
|
break;
|
|
case 23: // SessionError
|
|
case 24: // SessionErrorWithInner
|
|
case 25: // SessionClientError
|
|
session.hasError(true);
|
|
session.messages.unshift(log);
|
|
break;
|
|
default:
|
|
session.messages.unshift(log);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function init() {
|
|
hostDialogSessions.dialog({
|
|
modal: true,
|
|
height: 574,
|
|
width: 900,
|
|
resizable: false,
|
|
autoOpen: false
|
|
});
|
|
//hostDialogSessionsProgress.progressbar();
|
|
|
|
// Create View Model
|
|
vm = new pageViewModel();
|
|
$.ajax({
|
|
url: '@(Url.Action(MVC.API.DeviceModel.Index()))',
|
|
dataType: 'json',
|
|
type: 'POST',
|
|
success: init_loadedDeviceModels,
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
alert('Unable to retrieve device models: ' + errorThrown);
|
|
}
|
|
});
|
|
}
|
|
function init_loadedDeviceModels(models) {
|
|
for (var i = 0; i < models.length; i++) {
|
|
var m = models[i];
|
|
deviceModels[m.Id] = m;
|
|
}
|
|
|
|
// Load Logs
|
|
var d = new Date();
|
|
var loadData = {
|
|
Format: "json",
|
|
Start: d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate(),
|
|
End: null,
|
|
ModuleId: 50,
|
|
Take: 2000,
|
|
'__RequestVerificationToken': host.find('input[name="__RequestVerificationToken"]').val()
|
|
};
|
|
$.ajax({
|
|
url: '@(Url.Action(MVC.API.Logging.RetrieveEvents()))',
|
|
dataType: 'json',
|
|
type: 'POST',
|
|
traditional: true,
|
|
data: loadData,
|
|
success: init_loadedLogs,
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
alert('Unable to retrieve logs: ' + errorThrown);
|
|
}
|
|
});
|
|
}
|
|
function init_loadedLogs(logs) {
|
|
logs.reverse();
|
|
for (var i = 0; i < logs.length; i++) {
|
|
parseLog(logs[i]);
|
|
}
|
|
// Bind
|
|
ko.applyBindings(vm);
|
|
|
|
// Isotope
|
|
hostSessions.isotope({
|
|
itemSelector: '.session',
|
|
layoutMode: 'fitRows'
|
|
});
|
|
vm.isotopeInited = true;
|
|
|
|
// Init Persistent Connection
|
|
logHub = $.connection.logNotifications;
|
|
logHub.client.receiveLog = parseLog
|
|
|
|
$.connection.hub.qs = { LogModules: '@(Disco.Services.Devices.Enrolment.EnrolmentLog.Current.LiveLogGroupName)' };
|
|
$.connection.hub.error(onHubFailed);
|
|
|
|
$.connection.hub.start()
|
|
.done(function () { isLive = true; })
|
|
.fail(onHubFailed);
|
|
|
|
function onHubFailed(error) {
|
|
// 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');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
$('#dialogSession').on('click', '#formResolveSessionPending button', function (e) {
|
|
const $button = $(this);
|
|
const $form = $button.closest('form');
|
|
const body = new FormData($form[0]);
|
|
body.append('approve', $button.val());
|
|
fetch($form.attr('action'), {
|
|
method: 'POST',
|
|
body: body
|
|
}).then(function (response) {
|
|
if (!response.ok) {
|
|
alert('Failed to resolve pending session: ' + response.statusText);
|
|
}
|
|
});
|
|
});
|
|
init();
|
|
});
|
|
</script>
|