From 119a4f198ae7c5f15e22b065d4ff638aaa6ea771 Mon Sep 17 00:00:00 2001 From: erenguney Date: Thu, 7 Dec 2023 16:27:13 +0300 Subject: [PATCH] Index.cshtml: Recreated index.cshtml Index.cshtml.cs: Recreated index.cshtml.cs DeviceStatusCheckerService.csproj: Added scriban -Added wwwroot folder DeviceModel.cs: Managed order of properties (just for cosmetic purposes for the index.cshtml) --- DeviceStatusCheckerService.csproj | 7 + Models/DeviceModel.cs | 7 +- Pages/Index.cshtml | 1056 +++++++++++++++++++++++++---- Pages/Index.cshtml.cs | 197 ++++++ 4 files changed, 1129 insertions(+), 138 deletions(-) diff --git a/DeviceStatusCheckerService.csproj b/DeviceStatusCheckerService.csproj index cdd1b77..1a4444b 100644 --- a/DeviceStatusCheckerService.csproj +++ b/DeviceStatusCheckerService.csproj @@ -12,6 +12,7 @@ + @@ -20,4 +21,10 @@ + + + + + + diff --git a/Models/DeviceModel.cs b/Models/DeviceModel.cs index b20a77c..6978732 100644 --- a/Models/DeviceModel.cs +++ b/Models/DeviceModel.cs @@ -39,7 +39,7 @@ public class DeviceModel public string Password { get; set; } = ""; public string DescriptionLocation { get; set; } = ""; public string PresentationURL { get; set; } = ""; - public List Streams { get; set; } = new List(); + public DeviceActiveStatus DeviceActiveStatus { get; set; } = DeviceActiveStatus.UNKNOWN; public string Notes { get; set; } = ""; public string LastCheckTime @@ -49,12 +49,17 @@ public string LastCheckTime return DateTimeOffset.FromUnixTimeMilliseconds(LastCheckTimeMs).ToString("MM/dd/yyyy HH:mm:ss.fff"); } } + public long LastCheckTimeMs { get; set; } = 0; public DeviceModel? Clone() { + var obj = System.Text.Json.JsonSerializer.Serialize(this); return System.Text.Json.JsonSerializer.Deserialize(obj); + } + public List Streams { get; set; } = new List(); + } } diff --git a/Pages/Index.cshtml b/Pages/Index.cshtml index 58fa97a..0da217e 100644 --- a/Pages/Index.cshtml +++ b/Pages/Index.cshtml @@ -1,177 +1,959 @@ @page +@using Microsoft.AspNetCore.Hosting +@using System.Net +@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment WebHostEnvironment + +@* @using DeviceStatusCheckerService.Models.DeviceModel *@ @model IndexModel @{ ViewData["Title"] = "Device Monitoring"; } - - -
-
-
- @foreach (var dev in @Model.Devices.OrderBy(d => d.DeviceActiveStatus)) - { -
-
- @dev.LastCheckTime - -
-
-
#
-
@dev.UUID
-
-
-
IP
-
@dev.IP
-
- @if (!string.IsNullOrEmpty(dev.Hostname)) - { -
-
Hostname
-
@dev.Hostname
-
- } - @if (!string.IsNullOrEmpty(dev.FriendlyName)) - { -
-
FriendlyName
-
@dev.FriendlyName
-
- } - @if (!string.IsNullOrEmpty(dev.Model)) - { -
-
Model
-
@dev.Model
-
- } - @if (!string.IsNullOrEmpty(dev.DescriptionLocation)) - { -
-
Description Location
- -
- } - @if (!string.IsNullOrEmpty(dev.PresentationURL)) - { -
-
Presentation URL
- -
- } - @if (!string.IsNullOrEmpty(dev.User)) - { -
-
User
-
@dev.User
-
- } - @if (!string.IsNullOrEmpty(dev.Password)) - { -
-
Password
-
@dev.Password
-
- } - @if (!string.IsNullOrEmpty(dev.Notes)) - { -
-
Notes
-
- @(dev.Notes.Contains("discovered", StringComparison.InvariantCultureIgnoreCase) ? Html.Raw(" ") : "") - @(dev.Notes.Contains("static", StringComparison.InvariantCultureIgnoreCase) ? Html.Raw(" ") : "") - @dev.Notes + + + +
+ @* Accordion Header *@ +
+ +
+ + @* Accordion Body *@ +
+
+
+
+ + +
+ +
+ +
+ + +
-
- } -
-
Status
-
- @switch (dev.DeviceActiveStatus) + + @foreach (var property in typeof(Models.DeviceModel).GetProperties()) { - case Models.DeviceActiveStatus.ONLINE: - Online - break; - case Models.DeviceActiveStatus.OFFLINE: - Offline - break; - default: - Unknown - break; +
+ +
+
+ + +
+
+ : +
+
+ + +
+
+
}
+ +
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+                                        
+                                    
+                                 
+
+
+
- @if (dev.Streams.Count > 0) - { - @for (int i = 0; i < dev.Streams.Count; i++) - { -
-
Stream@(i)
- @try +
+
+
+ +
+
+
+
+ + + + + + + + + + + @* Camera Cards Body *@ +
+
+ @* Getting Model.Devices *@ + @foreach (var dev in @Model.Devices.OrderBy(d => d.DeviceActiveStatus)) + { + @* Creating Container as a row double cam *@ +
+ + @* Creating a CameraCard*@ +
+
+
+ @* Thumbnail *@ +
+ @{ + var baseUrl = "http://localhost:90"; + var imagePath = $"/thumbnails/{dev.UUID}/stream_0__1.jpeg"; + var absolutePath = baseUrl + imagePath; + var imagePathRelative = $"./thumbnails/{dev.UUID}/stream_0__1.jpeg"; + bool imageExists = ImageExists(absolutePath); + } + + @if (imageExists) { - @switch (dev.Streams[i].Status) + + } + else + { + + + } + + @functions { + private bool ImageExists(string imageUrl) + { + try + { + var request = (HttpWebRequest)WebRequest.Create(imageUrl); + request.Method = "HEAD"; + + using (var response = (HttpWebResponse)request.GetResponse()) + { + return response.StatusCode == HttpStatusCode.OK; + } + } + catch (WebException) { - case Models.StreamStatus.OK: - - break; - case Models.StreamStatus.ERROR: - - break; - default: - - break; + return false; } } - catch { } + } + @* Creating Camera Image in a Column*@ +
+
+ + +
- } - } +
+ +
+
+ @*LastCheckTimeDate*@ +
+
Last Checked
+
@dev.LastCheckTime.Split(' ')[0]
+
+ + @*LastCheckHours*@ +
+
+
@dev.LastCheckTime.Split(' ')[1]
+
+
+
#
+
@dev.UUID
+
+ @*Ip*@ +
+
IP
+
@dev.IP
+
+ @*Hostname*@ + @if (!string.IsNullOrEmpty(dev.Hostname)) + { +
+
Hostname
+
@dev.Hostname
+
+ } + + @*Hostname*@ + @if (!string.IsNullOrEmpty(dev.SerialNumber)) + { +
+
Serial Number
+
@dev.SerialNumber
+
+ } + + @*FriendlyName*@ + @if (!string.IsNullOrEmpty(dev.FriendlyName)) + { +
+
FriendlyName
+
@dev.FriendlyName
+
+ } + + @*Model*@ + @if (!string.IsNullOrEmpty(dev.Model)) + { +
+
Model
+
@dev.Model
+
+ } + + @*Location*@ + @if (!string.IsNullOrEmpty(dev.DescriptionLocation)) + { +
+
Description Location
+ +
+ } + + @*Presentation URL*@ + @if (!string.IsNullOrEmpty(dev.PresentationURL)) + { +
+
Presentation URL
+ +
+ } + + @*Presentation User*@ + @if (!string.IsNullOrEmpty(dev.User)) + { +
+
User
+
@dev.User
+
+ } + + @*Password*@ + @if (!string.IsNullOrEmpty(dev.Password)) + { +
+
Password
+
@dev.Password
+
+ } + + @*Notes*@ + @if (!string.IsNullOrEmpty(dev.Notes)) + { +
+
Notes
+
+ @(dev.Notes.Contains("discovered", StringComparison.InvariantCultureIgnoreCase) ? Html.Raw(" ") : "") + @(dev.Notes.Contains("static", StringComparison.InvariantCultureIgnoreCase) ? Html.Raw(" ") : "") + @dev.Notes +
+
+ } + + @*DeviceStatus*@ +
+
Status
+
+ @switch (dev.DeviceActiveStatus) + { + case Models.DeviceActiveStatus.ONLINE: + Online + break; + case Models.DeviceActiveStatus.OFFLINE: + Offline + break; + default: + Unknown + break; + } +
+
+ + @*DeviceStreams*@ + @if (dev.Streams.Count > 0) + { +
+ + @for (int i = 0; i < dev.Streams.Count; i++) + { +
+
Stream@(i)
+ @try + { + @switch (dev.Streams[i].Status) + { + case Models.StreamStatus.OK: + + break; + case Models.StreamStatus.ERROR: + + break; + default: + + break; + } + } + catch + { + + } +
+ } +
+ } +
+
+
-
- } + } +
-
+ + @section Scripts { + + + + + + + + + + + + + + + + + -} + function copyDevicesJSON() { + var htmlToCopy = $('#modalJSONResult').text(); + navigator.clipboard.writeText(htmlToCopy).then(() => { + alert("Content is copied to clipboard") + }).catch((error) => { + alert("Unable to copy the content", error); + }); + } + + function checkVisibleFilteredDevices() { + $('#cameraCardsBody .form-check input[type="checkbox"]:visible').prop('checked', true); + } + + function resetInput(propertyName) { + var renameInputToReset = $('#btnRenameInput_' + propertyName); + renameInputToReset.val(null); + } + + function exportSelectedDevicesAdvanced() { + var selectedCheckboxIds = []; + $('#cameraCardsBody .form-check input[type="checkbox"]').each(function () { + // Check if the checkbox is checked + if ($(this).is(':checked')) { + // Add the ID to the array + selectedCheckboxIds.push(this.id.split("_")[1]); + } + }); + const userTemplate = $('#templateInput').val(); + const encodedTemplate = btoa(userTemplate); + + $.ajax({ + url: `/Index?handler=ExportDevices&tpl=${encodedTemplate}&selectedCheckboxIdsParam=${btoa(JSON.stringify(selectedCheckboxIds))}`, + contentType: 'application/json', + success: function (result) { + jsonDeviceArray = JSON.parse(result); + var jsonString = JSON.stringify(jsonDeviceArray, null, 2); + + $('#templateOutputAdvanced').removeAttr('data-highlighted'); + $('#templateOutputAdvanced').html(jsonString); + + //IMPORTANT, When adding highlight.js + //you should use JS DOM selector not JQuery selector, + //it is not working with jquery + hljs.highlightElement(document.querySelector('#templateOutputAdvanced')); + }, + error: function (xhr, textStatus, errorThrown) { + console.error("Export error:", textStatus, errorThrown); + + // Check if the response has an 'error' property + if (xhr.responseJSON && xhr.responseJSON.error) { + alert("Export error: " + xhr.responseJSON.error); + } else { + // If no specific error message, show a generic message + alert("Export error occurred."); + } + } + }); + } + + function toggleVisibility(elementId) { + $(elementId).toggleClass('d-none'); + } + + function toggleExportOptions() { + var exportOptionsButton = $("#exportOptionsButton"); + + exportOptionsButton.text(exportOptionsButton.text() === 'Simple Export Options' ? 'Advanced Export Options' : 'Simple Export Options'); + toggleVisibility('#dynamicKeys'); + toggleVisibility('#advancedExportInput'); + + var exportButton = $('#exportButton'); + exportButton.attr('onclick', (exportButton.attr('onclick') === 'exportSelectedDevicesAdvanced()' ? 'exportSelectedDevices()' : 'exportSelectedDevicesAdvanced()')); + } + + function showCreateDeviceModal() { + $('#createDeviceModal').modal('show'); + } + + function showPublishDeviceModal() { + $('#publishDeviceModal').modal('show'); + } + + function publishDevices() { + var url = $('#urlInput').val(); + var username = $('#usernameInput').val(); + var password = $('#passwordInput').val(); + var deviceData = jsonDeviceArray; + + $.ajax({ + type: "POST", + url: url, + data: JSON.stringify({ + username: username, + password: password, + deviceData: deviceData + }), + contentType: 'application/json', + success: function (res) { + console.log("Success", res); + }, + error: function (err) { + console.error("Error", err); + } + }); + } + + + +} diff --git a/Pages/Index.cshtml.cs b/Pages/Index.cshtml.cs index 97e4b81..efab6ad 100644 --- a/Pages/Index.cshtml.cs +++ b/Pages/Index.cshtml.cs @@ -2,20 +2,37 @@ using DeviceStatusCheckerService.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Runtime.Serialization; +using Scriban; +using Onvif.Core.Client.Device; +using Scriban.Runtime; +using System.Text; +using System.Net; +using System.Xml.Linq; namespace DeviceStatusCheckerService.Pages { public class IndexModel : PageModel { + //FIELDS + + //initializing instances private readonly ILogger _logger; private readonly DeviceManager _deviceManager; + //Contructor to initialize fields for dependency injection public IndexModel(ILogger logger, DeviceManager deviceManager) { _logger = logger; _deviceManager = deviceManager; } + public class JsonHTML + { + public string HTMLContent { get; set; } + } + + //PROPERTIES public List Devices { get @@ -23,5 +40,185 @@ public List Devices return _deviceManager.Devices; } } + + //METHODS + private string RenderScribanTemplate(List selectedDevices, List selectedDynamicKeysArray, List renamedKeysArray) + { + try + { + + //List selectedProperties = selectedDynamicKeysArray; + List selectedProperties = selectedDynamicKeysArray.Select(MemberRenamer).ToList(); + + var _deviceTemplate = Template.Parse(@" + [ + + {{ for model in models }} + { + {{- index = 0 -}} + {{ for selectedproperty in selectedproperties }} + {{if index < arraysize}} + + {{ if selectedproperty == ""streams"" }} + ""Streams"": [ + {{for stream in model.streams}} + { + ""Status"": ""{{ stream.status }}"", + ""Uri"": ""{{ stream.uri }}"" + }, + {{end}} + ] + {{else}} + ""{{renamedkeysarray[index]}}"": ""{{ model[selectedproperty]}}"", + {{end}} + {{index = index + 1}} + {{end}} + {{ end }} + }, + {{ end }} + ] + "); + + var renderedDevice = _deviceTemplate.Render(new + { + selectedproperties = selectedProperties, + selecteddynamickeysarray = selectedDynamicKeysArray, + models = selectedDevices, + renamedkeysarray = renamedKeysArray, + arraysize = selectedDynamicKeysArray.Count(), + }); + return string.Join("", renderedDevice); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error rendering template for device"); + return "Error rendering template for device"; + } + } + + + public JsonResult OnGetExportTemplateDevices(string selectedCheckboxIdsParam, string selectedDynamicKeys, string renamedKeys) + { + + try + { + if (selectedCheckboxIdsParam != null) + { + var selectedDynamicKeysArray = System.Text.Json.JsonSerializer.Deserialize>(selectedDynamicKeys); + if (selectedDynamicKeysArray.Count == 0) + { + return new JsonResult(new { error = "No labels are selected." }) + { + StatusCode = (int)HttpStatusCode.BadRequest + }; + } + + var renamedKeysArray = System.Text.Json.JsonSerializer.Deserialize>(renamedKeys); + byte[] data = Convert.FromBase64String(selectedCheckboxIdsParam); + string selectedUUIDs = System.Text.Encoding.UTF8.GetString(data); + string[] uuidArray = System.Text.Json.JsonSerializer.Deserialize(selectedUUIDs); + + var selectedDevices = _deviceManager.Devices + .Where(device => uuidArray.Contains(device.UUID)) + .ToList(); + + + // Check if selectedDevices is null or empty + if (selectedDevices.Count == 0) + { + return new JsonResult(new { error = "Please select devices." }) + { + StatusCode = (int)HttpStatusCode.BadRequest + }; + } + var selectedDevicesHTML = RenderScribanTemplate(selectedDevices, selectedDynamicKeysArray, renamedKeysArray); + + var jsonHTML = new JsonHTML + { + HTMLContent = selectedDevicesHTML + }; + + return new JsonResult(jsonHTML); + } + else + { + return new JsonResult("No data provided"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error exporting data"); + return new JsonResult("Error exporting data"); + } + + } + + + public JsonResult OnGetExportDevices(string tpl, string selectedCheckboxIdsParam) + { + if (string.IsNullOrEmpty(tpl)) + { + // Handle the error, throw an exception, or return an appropriate response + return new JsonResult(new { error = "Please fill the template." }) + { + StatusCode = (int)HttpStatusCode.BadRequest + }; + } + try + { + byte[] data = Convert.FromBase64String(selectedCheckboxIdsParam); + string selectedUUIDs = System.Text.Encoding.UTF8.GetString(data); + string[] uuidArray = System.Text.Json.JsonSerializer.Deserialize(selectedUUIDs); + var selectedDevices = _deviceManager.Devices + .Where(device => uuidArray.Contains(device.UUID)) + .ToList(); + + if (selectedDevices.Count == 0) + { + return new JsonResult(new { error = "Please select devices." }) + { + StatusCode = (int)HttpStatusCode.BadRequest + }; + } + + //TEMPLATE WORKS + tpl = Encoding.UTF8.GetString(Convert.FromBase64String(tpl)); + var template = Template.Parse(tpl); + var result = template.Render(new { models = selectedDevices }); + return new JsonResult(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error exporting data"); + return new JsonResult("Error exporting data"); + } + } + + + //MemberRenamer function for scriban + private static string MemberRenamer(string name) + { + var builder = new StringBuilder(); + var previousUpper = false; + for (var i = 0; i < name.Length; i++) + { + var c = name[i]; + if (char.IsUpper(c)) + { + if (i > 0 && !previousUpper) + { + builder.Append("_"); + } + builder.Append(char.ToLowerInvariant(c)); + previousUpper = true; + } + else + { + builder.Append(c); + previousUpper = false; + } + } + return builder.ToString(); + } } } \ No newline at end of file