From 14745e9bf790ac0ef42c0c45c91f653f640949de Mon Sep 17 00:00:00 2001 From: Andreas Venizelou Date: Mon, 19 Aug 2024 16:53:08 +0300 Subject: [PATCH] Issues --- Protest/Front/backup.js | 1 - Protest/Front/dhcpdiscover.js | 4 +- Protest/Front/gandalf.js | 2 +- Protest/Front/issues.js | 160 ++++++++++++++++++++++--- Protest/Front/keepalive.js | 4 +- Protest/Front/list.js | 2 +- Protest/Front/ping.js | 4 +- Protest/Front/portscan.js | 4 +- Protest/Front/reverseproxy.js | 4 +- Protest/Front/sitecheck.js | 4 +- Protest/Front/ssh.js | 4 +- Protest/Front/telnet.js | 4 +- Protest/Front/traceroute.js | 4 +- Protest/Tools/LiveStats.cs | 13 +- Protest/Workers/Issues.cs | 218 +++++++++++++++++++++++++--------- 15 files changed, 327 insertions(+), 105 deletions(-) diff --git a/Protest/Front/backup.js b/Protest/Front/backup.js index e715075a..8bb6f062 100644 --- a/Protest/Front/backup.js +++ b/Protest/Front/backup.js @@ -195,5 +195,4 @@ class Backup extends List { element.style.backgroundColor = "var(--clr-select)"; }; } - } \ No newline at end of file diff --git a/Protest/Front/dhcpdiscover.js b/Protest/Front/dhcpdiscover.js index 1863dfe5..eed023d1 100644 --- a/Protest/Front/dhcpdiscover.js +++ b/Protest/Front/dhcpdiscover.js @@ -230,9 +230,7 @@ class DhcpDiscover extends Window { this.hexRecord = []; this.result.textContent = ""; - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); this.ws = new WebSocket((KEEP.isSecure ? "wss://" : "ws://") + server + "/ws/dhcp"); diff --git a/Protest/Front/gandalf.js b/Protest/Front/gandalf.js index ef0ce2f7..2505efc2 100644 --- a/Protest/Front/gandalf.js +++ b/Protest/Front/gandalf.js @@ -210,7 +210,7 @@ class Gandalf extends Window { else if (this.thresholdRange.value < 128) strength = "Strong"; else strength = "Overkill"; - thresholdValueLabel.textContent = `${this.thresholdRange.value}-bits (${strength} or bellow)`; + thresholdValueLabel.textContent = `${this.thresholdRange.value}-bits (${strength} or below)`; if (this.entropy) totalValueLabel.textContent = this.entropy.reduce((sum, entry)=> { diff --git a/Protest/Front/issues.js b/Protest/Front/issues.js index 81dead7f..a53f46f9 100644 --- a/Protest/Front/issues.js +++ b/Protest/Front/issues.js @@ -1,12 +1,48 @@ class Issues extends List { + + static SEVERITY_TEXT = { + 1 : "Info", + 2 : "Warning", + 3 : "Error", + 4 : "Critical", + }; + + static SEVERITY_ICON = { + 1 : "url(mono/info.svg)", + 2 : "url(mono/warning.svg)", + 3 : "url(mono/error.svg)", + 4 : "url(mono/critical.svg)", + }; + + static SEVERITY_COLOR = { + 1 : "var(--clr-dark)", + 2 : "var(--clr-warning)", + 3 : "var(--clr-error)", + 4 : "var(--clr-critical)", + }; + constructor() { super(); this.AddCssDependencies("list.css"); - const columns = ["host", "category", "issue", "last update"]; + const columns = ["issue", "target", "category", "severity", "source"]; this.SetupColumns(columns); + this.columnsElements[0].style.width = "40%"; + + this.columnsElements[1].style.left = "40%"; + this.columnsElements[1].style.width = "15%"; + + this.columnsElements[2].style.left = "55%"; + this.columnsElements[2].style.width = "15%"; + + this.columnsElements[3].style.left = "70%"; + this.columnsElements[3].style.width = "15%"; + + this.columnsElements[4].style.left = "85%"; + this.columnsElements[4].style.width = "15%"; + this.columnsOptions.style.display = "none"; this.SetTitle("Issues"); @@ -33,10 +69,19 @@ class Issues extends List { super.UpdateAuthorization(); } + Close() { //overrides + super.Close(); + + if (this.ws != null) { + try { + this.ws.close(); + } + catch (ex) {}; + } + } + Connect() { - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); if (this.ws != null) { @@ -48,26 +93,42 @@ class Issues extends List { this.ws = new WebSocket((KEEP.isSecure ? "wss://" : "ws://") + server + "/ws/issues"); - this.ws.onopen = ()=> { - if (this.args.interval) { - this.ws.send(`interval=${this.args.interval}`); - } + this.ws.onopen = ()=> {}; - if (this.args.select) { - this.UpdateSelected(); + this.ws.onmessage = event=> { + const json = JSON.parse(event.data); + for (let i=0; i { - + this.ws.onclose = ()=> { + this.ws = null; }; - this.ws.onclose = ()=> { + this.ws.onerror = error=> {}; + } + AddIssue(issue) { + const key = Date.now() + Math.random() * 1000; + + const element = document.createElement("div"); + element.id = key; + element.className = "list-element"; + this.list.appendChild(element); + + this.link.data[key] = { + key: key, + severity: {v: issue.severity}, + issue : {v: issue.issue}, + target : {v: issue.target}, + category: {v: issue.category}, + source : {v: issue.source}, + isUser : {v: issue.isUser}, }; - this.ws.onerror = error=> {}; + this.InflateElement(element, this.link.data[key]); + this.link.length++; } ScanDialog(entry=null, isRunning=false) { @@ -87,8 +148,77 @@ class Issues extends List { innerBox.style.alignItems = "center"; okButton.onclick = async ()=> { + try { + const response = await fetch("issues/start"); + if (response.status !== 200) LOADER.HttpErrorHandler(response.status); + + const json = await response.json(); + if (json.error) throw(json.error); + + if (this.ws === null) { + this.Connect(); + } + } + catch (ex) { + this.ConfirmBox(ex, true, "mono/error.svg") + } dialog.Close(); }; } + + InflateElement(element, entry) { //overrides + const icon = document.createElement("div"); + icon.className = "list-element-icon"; + icon.style.maskSize = "24px 24px"; + icon.style.maskPosition = "center"; + icon.style.maskRepeat = "no-repeat"; + icon.style.maskImage = Issues.SEVERITY_ICON[entry.severity.v] ?? "url(mono/critical.svg)"; + icon.style.backgroundColor = Issues.SEVERITY_COLOR[entry.severity.v] ?? "var(--clr-dark)"; + icon.style.filter = "brightness(0.8)"; + element.appendChild(icon); + + for (let i = 0; i < this.columnsElements.length; i++) { + if (!(this.columnsElements[i].textContent in entry)) continue; + const propertyName = this.columnsElements[i].textContent; + + let value; + + if (propertyName === "severity") { + value = Issues.SEVERITY_TEXT[entry[this.columnsElements[i].textContent].v]; + icon.style.left = this.columnsElements[i].style.left; + } + else { + value = entry[this.columnsElements[i].textContent].v + } + + if (value.length === 0) continue; + + const newAttr = document.createElement("div"); + newAttr.textContent = value; + element.appendChild(newAttr); + + if (propertyName === "severity") { + newAttr.style.left = `calc(28px + ${this.columnsElements[i].style.left})`; + newAttr.style.width = `calc(${this.columnsElements[i].style.width} - 28px)`; + } + else { + newAttr.style.left = this.columnsElements[i].style.left; + newAttr.style.width = this.columnsElements[i].style.width; + } + } + + element.onclick = ()=> { + if (this.selected) this.selected.style.backgroundColor = ""; + + this.args.select = entry.key; + + this.selected = element; + element.style.backgroundColor = "var(--clr-select)"; + }; + + element.ondblclick = ()=> { + + }; + } } \ No newline at end of file diff --git a/Protest/Front/keepalive.js b/Protest/Front/keepalive.js index aaaba50e..e382b7b1 100644 --- a/Protest/Front/keepalive.js +++ b/Protest/Front/keepalive.js @@ -11,9 +11,7 @@ const KEEP = { sessionTtlMapping: { 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:14, 9:21, 10:28, 11:60, 12:90 }, Initialize: ()=> { - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.endsWith("/")) server = server.substring(0, server.indexOf("/")); KEEP.socket = new WebSocket((KEEP.isSecure ? "wss://" : "ws://") + server + "/ws/keepalive"); diff --git a/Protest/Front/list.js b/Protest/Front/list.js index 68c76325..6e435c40 100644 --- a/Protest/Front/list.js +++ b/Protest/Front/list.js @@ -593,7 +593,7 @@ class List extends Window { for (let i = 0; i < this.columnsElements.length; i++) { if (!(this.columnsElements[i].textContent in entry)) continue; - let value = entry[this.columnsElements[i].textContent].v; + const value = entry[this.columnsElements[i].textContent].v; if (value.length === 0) continue; const newAttr = document.createElement("div"); diff --git a/Protest/Front/ping.js b/Protest/Front/ping.js index 577908a4..9eba1b0b 100644 --- a/Protest/Front/ping.js +++ b/Protest/Front/ping.js @@ -774,9 +774,7 @@ class Ping extends Console { Connect() { if (this.args.status !== "play") return; - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); if (this.ws != null) { diff --git a/Protest/Front/portscan.js b/Protest/Front/portscan.js index 2f511d74..de3480e9 100644 --- a/Protest/Front/portscan.js +++ b/Protest/Front/portscan.js @@ -481,9 +481,7 @@ class PortScan extends Console { } Connect() { - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); if (this.ws != null) { diff --git a/Protest/Front/reverseproxy.js b/Protest/Front/reverseproxy.js index a33f2fe5..abf71042 100644 --- a/Protest/Front/reverseproxy.js +++ b/Protest/Front/reverseproxy.js @@ -239,9 +239,7 @@ class ReverseProxy extends List { } Connect() { - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); if (this.ws != null) { diff --git a/Protest/Front/sitecheck.js b/Protest/Front/sitecheck.js index 720bf956..476ff591 100644 --- a/Protest/Front/sitecheck.js +++ b/Protest/Front/sitecheck.js @@ -80,9 +80,7 @@ class SiteCheck extends Window { } Check() { - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); this.ws = new WebSocket((KEEP.isSecure ? "wss://" : "ws://") + server + "/ws/sitecheck"); diff --git a/Protest/Front/ssh.js b/Protest/Front/ssh.js index ddb3728c..58d5c857 100644 --- a/Protest/Front/ssh.js +++ b/Protest/Front/ssh.js @@ -129,9 +129,7 @@ class Ssh extends Terminal { this.statusBox.textContent = "Connecting..."; this.content.appendChild(this.statusBox); - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); if (this.ws != null) { diff --git a/Protest/Front/telnet.js b/Protest/Front/telnet.js index 648b8bc9..191f0813 100644 --- a/Protest/Front/telnet.js +++ b/Protest/Front/telnet.js @@ -73,9 +73,7 @@ class Telnet extends Terminal { this.statusBox.textContent = "Connecting..."; this.content.appendChild(this.statusBox); - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); if (this.ws != null) { diff --git a/Protest/Front/traceroute.js b/Protest/Front/traceroute.js index 91967293..63ce7755 100644 --- a/Protest/Front/traceroute.js +++ b/Protest/Front/traceroute.js @@ -211,9 +211,7 @@ class TraceRoute extends Console { } Connect() { - let server = window.location.href; - server = server.replace("https://", ""); - server = server.replace("http://", ""); + let server = window.location.href.replace("https://", "").replace("http://", ""); if (server.indexOf("/") > 0) server = server.substring(0, server.indexOf("/")); if (this.ws != null) { diff --git a/Protest/Tools/LiveStats.cs b/Protest/Tools/LiveStats.cs index 7f0b1547..62191859 100644 --- a/Protest/Tools/LiveStats.cs +++ b/Protest/Tools/LiveStats.cs @@ -89,7 +89,7 @@ public static async void UserStats(HttpListenerContext ctx) { catch { } } - if (Issues.CheckPasswordStrength(entry, out Issues.Issue? weakPsIssue)) { + if (Issues.CheckPasswordStrength(entry, true, out Issues.Issue? weakPsIssue)) { WsWriteText(ws, weakPsIssue?.ToJsonBytes(), mutex); } } @@ -133,14 +133,11 @@ public static async void DeviceStats(HttpListenerContext ctx) { return; } - string[] pingArray = Array.Empty(); - string firstAlive = null; - PingReply firstReply = null; - entry.attributes.TryGetValue("ip", out Database.Attribute _ip); entry.attributes.TryGetValue("hostname", out Database.Attribute _hostname); entry.attributes.TryGetValue("operating system", out Database.Attribute _os); + string[] pingArray = Array.Empty(); if (_ip?.value?.Length > 0) { pingArray = _ip.value.Split(';').Select(o => o.Trim()).ToArray(); } @@ -150,6 +147,8 @@ public static async void DeviceStats(HttpListenerContext ctx) { object mutex = new object(); + string firstAlive = null; + PingReply firstReply = null; if (pingArray.Length > 0) { List pingTasks = new List(); @@ -295,7 +294,7 @@ public static async void DeviceStats(HttpListenerContext ctx) { } } - if (Issues.CheckPasswordStrength(entry, out Issues.Issue? weakPsIssue)) { + if (Issues.CheckPasswordStrength(entry, false, out Issues.Issue? weakPsIssue)) { WsWriteText(ws, weakPsIssue?.ToJsonBytes(), mutex); } } @@ -340,7 +339,7 @@ private static void WmiQuery(WebSocket ws, object mutex, string firstAlive, ref WsWriteText(ws, $"{{\"drive\":\"{caption}\",\"total\":{nSize},\"used\":{nSize - nFree},\"path\":\"{Data.EscapeJsonText($"\\\\{firstAlive}\\{caption.Replace(":", String.Empty)}$")}\",\"source\":\"WMI\"}}", mutex); - if (Issues.CheckDiskCapacity(percent, caption, out Issues.Issue? diskIssue)) { + if (Issues.CheckDiskCapacity(firstAlive, percent, caption, out Issues.Issue? diskIssue)) { WsWriteText(ws, diskIssue?.ToJsonBytes(), mutex); } } diff --git a/Protest/Workers/Issues.cs b/Protest/Workers/Issues.cs index f087bba0..ffdd1052 100644 --- a/Protest/Workers/Issues.cs +++ b/Protest/Workers/Issues.cs @@ -1,37 +1,55 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.Metrics; using System.Net; +using System.Net.NetworkInformation; using System.Net.WebSockets; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Lextm.SharpSnmpLib; - +using Lextm.SharpSnmpLib.Security; using Protest.Http; using Protest.Tools; using static Protest.Tools.DebitNotes; +using static Protest.Workers.Issues; namespace Protest.Workers; internal static class Issues { private const int WEAK_PASSWORD_ENTROPY_THRESHOLD = 28; - public enum IssueLevel { info, warning, error, critical } + public enum SeverityLevel { + info = 1, + warning = 2, + error = 3, + critical = 4 + } public struct Issue { - public IssueLevel level; + public SeverityLevel severity; public string message; + public string target; + public string category; public string source; + //public string file; + public bool isUser; + public long timestamp; } + private static TaskWrapper task; + private static ConcurrentBag issues = new ConcurrentBag(); + public static byte[] ToJsonBytes(this Issue issue) => JsonSerializer.SerializeToUtf8Bytes(new Dictionary { - { issue.level.ToString(), issue.message }, - { "source", issue.source }, + { issue.severity.ToString(), issue.message }, + { "target", issue.target }, + { "category", issue.category}, + { "source", issue.source }, + //{ "file", issue.file}, }); - public static TaskWrapper task; - public static byte[] List() { //TODO: return null; @@ -42,8 +60,7 @@ public static byte[] Start(string origin) { Thread thread = new Thread(() => Scan()); - task = new TaskWrapper("Issues") - { + task = new TaskWrapper("Issues") { thread = thread, origin = origin, TotalSteps = 0, @@ -98,30 +115,60 @@ public static async void WebSocketHandler(HttpListenerContext ctx) { return; } + int lastIssuesCount = 0; + long lastTimestamp = -1; + try { - while (ws.State == WebSocketState.Open) { + while (ws.State == WebSocketState.Open && task is not null) { if (!Auth.IsAuthenticatedAndAuthorized(ctx, "/ws/issues")) { ctx.Response.Close(); return; } - await WsWriteText(ws, "{\"test\":\"test\"}"u8.ToArray()); - await Task.Delay(10_000); + await Task.Delay(5_000); + + if (lastIssuesCount == issues.Count) { + continue; + } + + lastIssuesCount = issues.Count; + + IEnumerable filtered = issues.Where(o => o.timestamp > lastTimestamp); + + if (filtered.Any()) { + + Console.WriteLine(filtered.Count()); + + byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(filtered.Select(o => new { + severity = o.severity, + issue = o.message, + target = o.target, + category = o.category, + source = o.source, + isUser = o.isUser, + })); + + await WsWriteText(ws, bytes); + + lastTimestamp = filtered.Max(o => o.timestamp); + } } - } - catch { - } - if (ws?.State == WebSocketState.Open) { - try { - await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, CancellationToken.None); + } + catch { } + finally { + if (ws?.State == WebSocketState.Open) { + try { + await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); + } + catch { } } - catch { } } } private static void Scan() { ScanUsers(); + Thread.Sleep(1000); ScanDevices(); } @@ -129,31 +176,74 @@ private static void ScanUsers() { foreach (KeyValuePair user in DatabaseInstances.users.dictionary) { user.Value.attributes.TryGetValue("type", out Database.Attribute typeAttribute); + if (CheckPasswordStrength(user.Value, true, out Issue? issue) && issue.HasValue) { + issues.Add(issue.Value); + } } } private static void ScanDevices() { foreach (KeyValuePair device in DatabaseInstances.devices.dictionary) { - device.Value.attributes.TryGetValue("type", out Database.Attribute typeAttribute); - device.Value.attributes.TryGetValue("ip", out Database.Attribute ipAttribute); + ScanDevice(device); + } + } - if (Data.PRINTER_TYPES.Contains(typeAttribute.value)) { + public static void ScanDevice(KeyValuePair device) { + device.Value.attributes.TryGetValue("type", out Database.Attribute typeAttribute); + device.Value.attributes.TryGetValue("ip", out Database.Attribute ipAttribute); + device.Value.attributes.TryGetValue("hostname", out Database.Attribute hostnameAttribute); + device.Value.attributes.TryGetValue("operating system", out Database.Attribute osAttribute); - } - else if (Data.SWITCH_TYPES.Contains(typeAttribute.value)) { + if (CheckPasswordStrength(device.Value, false, out Issue? issue) && issue.HasValue) { + issues.Add(issue.Value); + } + + if (osAttribute?.value.Contains("windows", StringComparison.OrdinalIgnoreCase) == true) { + + } + else if (Data.PRINTER_TYPES.Contains(typeAttribute?.value)) { + + } + else if (Data.SWITCH_TYPES.Contains(typeAttribute?.value)) { - } } } - public static bool CheckPasswordStrength(Database.Entry entry, out Issue? issue) { + public static bool CheckPasswordStrength(Database.Entry entry, bool isUser, out Issue? issue) { if (entry.attributes.TryGetValue("password", out Database.Attribute password)) { string value = password.value; if (value.Length > 0 && PasswordStrength.Entropy(value) < WEAK_PASSWORD_ENTROPY_THRESHOLD) { - issue = new Issues.Issue { - level = Issues.IssueLevel.critical, - message = "Weak password", - source = "Internal check" + string target; + if (isUser) { + if (entry.attributes.TryGetValue("username", out Database.Attribute usernameAttr)) { + target = usernameAttr.value; + } + else if (entry.attributes.TryGetValue("username", out Database.Attribute emailAttr)) { + target = emailAttr.value; + } + else { + target = entry.filename; + } + } else { + if (entry.attributes.TryGetValue("ip", out Database.Attribute ipAttr)) { + target = ipAttr.value; + } + else if (entry.attributes.TryGetValue("hostname", out Database.Attribute hostnameAttr)) { + target = hostnameAttr.value; + } + else { + target = entry.filename; + } + } + + issue = new Issue { + severity = Issues.SeverityLevel.critical, + target = target, + message = "Weak password", + category = "Password", + source = "Internal check", + isUser = isUser, + timestamp = DateTime.UtcNow.Ticks }; return true; } @@ -163,30 +253,44 @@ public static bool CheckPasswordStrength(Database.Entry entry, out Issue? issue) return false; } - public static bool CheckDiskCapacity(double percent, string diskCaption, out Issue? issue) { + public static bool CheckDiskCapacity(string target, double percent, string diskCaption, out Issue? issue) { + string message = $"Free space is {percent}% on disk {Data.EscapeJsonText(diskCaption)}"; + if (percent <= 1) { - issue = new Issues.Issue { - level = IssueLevel.critical, - message = $"{percent}% free space on disk {Data.EscapeJsonText(diskCaption)}", - source = "WMI" + issue = new Issue { + severity = SeverityLevel.critical, + target = target, + message = message, + category = "Disk drive", + source = "WMI", + isUser = false, + timestamp = DateTime.UtcNow.Ticks, }; return true; } - + if (percent <= 5) { - issue = new Issues.Issue { - level = IssueLevel.error, - message = $"{percent}% free space on disk {Data.EscapeJsonText(diskCaption)}", - source = "WMI" + issue = new Issue { + severity = SeverityLevel.error, + target = target, + message = message, + category = "Disk drive", + source = "WMI", + isUser = false, + timestamp = DateTime.UtcNow.Ticks, }; return true; } - + if (percent < 15) { - issue = new Issues.Issue { - level = IssueLevel.warning, - message = $"{percent}% free space on disk {Data.EscapeJsonText(diskCaption)}", - source = "WMI" + issue = new Issue { + severity = SeverityLevel.warning, + target = target, + message = message, + category = "Disk drive", + source = "WMI", + isUser = false, + timestamp = DateTime.UtcNow.Ticks, }; return true; } @@ -224,17 +328,25 @@ public static bool CheckPrinterComponent(IPAddress ipAddress, SnmpProfiles.Profi int used = 100 * current / max; if (used < 5) { - arrays.Add(new Issues.Issue { - level = IssueLevel.error, - message = $"{used}% {componentNameArray[i][1]}", - source = "SNMP" + arrays.Add(new Issue { + severity = SeverityLevel.error, + message = $"{used}% {componentNameArray[i][1]}", + target = ipAddress.ToString(), + category = "Printer component", + source = "SNMP", + isUser = false, + timestamp = DateTime.UtcNow.Ticks }); } else if (used < 15) { - arrays.Add(new Issues.Issue { - level = IssueLevel.warning, - message = $"{used}% {componentNameArray[i][1]}", - source = "SNMP" + arrays.Add(new Issue { + severity = SeverityLevel.warning, + message = $"{used}% {componentNameArray[i][1]}", + target = ipAddress.ToString(), + category = "Printer component", + source = "SNMP", + isUser = false, + timestamp = DateTime.UtcNow.Ticks }); } }