First charts for ping rendering
This commit is contained in:
parent
abb91f7baf
commit
56a412108f
@ -45,9 +45,9 @@ namespace ISPChk.Controllers
|
|||||||
|
|
||||||
// GET: api/Host/5
|
// GET: api/Host/5
|
||||||
[HttpGet("{id}/pings/{start}/{end?}")]
|
[HttpGet("{id}/pings/{start}/{end?}")]
|
||||||
public async Task<ActionResult<Host>> GetPingsByTimeSpan(long id, long start, long end)
|
public async Task<ActionResult<IEnumerable<PingItem>>> GetPingsByTimeSpan(long id, long start, long end)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("called");
|
System.Diagnostics.Debug.WriteLine("called id:" + id + " start:" + start + " end:" + end);
|
||||||
var host = await _context.Hosts.FindAsync(id);
|
var host = await _context.Hosts.FindAsync(id);
|
||||||
|
|
||||||
if (host == null)
|
if (host == null)
|
||||||
@ -55,7 +55,13 @@ namespace ISPChk.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return host;
|
var startTime = DateTimeOffset.FromUnixTimeMilliseconds(start);
|
||||||
|
var endTime = DateTimeOffset.FromUnixTimeMilliseconds(end);
|
||||||
|
|
||||||
|
_context.Entry(host).Collection(h => h.PingItems).Load();
|
||||||
|
var pis = host.PingItems.Where(p => p.Date > startTime).ToList();
|
||||||
|
|
||||||
|
return pis;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT: api/Host/5
|
// PUT: api/Host/5
|
||||||
|
@ -10,6 +10,8 @@ namespace ISPChk.Models
|
|||||||
public long HostId { get; set; }
|
public long HostId { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string HostName { get; set; }
|
public string HostName { get; set; }
|
||||||
|
public bool Ping { get; set; }
|
||||||
|
public bool TCP { get; set; }
|
||||||
|
|
||||||
public List<PingItem> PingItems { get; set; }
|
public List<PingItem> PingItems { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,11 @@ namespace ISPChk.Models
|
|||||||
{
|
{
|
||||||
[Key]
|
[Key]
|
||||||
public long PingId { get; set; }
|
public long PingId { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTimeOffset Date { get; set; }
|
||||||
public float Min { get; set; }
|
public float Min { get; set; }
|
||||||
public float Max { get; set; }
|
public float Max { get; set; }
|
||||||
public float Avg { get; set; }
|
public float Avg { get; set; }
|
||||||
|
public int Failures { get; set; }
|
||||||
public long HostId { get; set; }
|
public long HostId { get; set; }
|
||||||
public Host Host { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,11 +31,10 @@ namespace ISPChk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddHost(Host host)
|
private void StartPing(Host host)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("Host added!");
|
|
||||||
var t = Task.Run(async () => {
|
var t = Task.Run(async () => {
|
||||||
|
|
||||||
Ping pingSender = new Ping();
|
Ping pingSender = new Ping();
|
||||||
|
|
||||||
// Create a buffer of 32 bytes of data to be transmitted.
|
// Create a buffer of 32 bytes of data to be transmitted.
|
||||||
@ -55,11 +54,12 @@ namespace ISPChk
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
long min = timeout+1;
|
long min = timeout + 1;
|
||||||
long max = 0;
|
long max = 0;
|
||||||
long avg = 0;
|
long avg = 0;
|
||||||
int successes = 0;
|
int successes = 0;
|
||||||
for(var i=0;i<5;++i)
|
int failures = 0;
|
||||||
|
for (var i = 0; i < 5; ++i)
|
||||||
{
|
{
|
||||||
// Send the request.
|
// Send the request.
|
||||||
PingReply reply = pingSender.Send(host.HostName, timeout, buffer, options);
|
PingReply reply = pingSender.Send(host.HostName, timeout, buffer, options);
|
||||||
@ -71,33 +71,41 @@ namespace ISPChk
|
|||||||
": bytes=" + reply.Buffer.Length +
|
": bytes=" + reply.Buffer.Length +
|
||||||
" time=" + reply.RoundtripTime + "ms");
|
" time=" + reply.RoundtripTime + "ms");
|
||||||
avg += reply.RoundtripTime;
|
avg += reply.RoundtripTime;
|
||||||
if(reply.RoundtripTime < min)
|
if (reply.RoundtripTime < min)
|
||||||
{
|
{
|
||||||
min = reply.RoundtripTime;
|
min = reply.RoundtripTime;
|
||||||
}
|
}
|
||||||
if(reply.RoundtripTime > max)
|
if (reply.RoundtripTime > max)
|
||||||
{
|
{
|
||||||
max = reply.RoundtripTime;
|
max = reply.RoundtripTime;
|
||||||
}
|
}
|
||||||
//System.Diagnostics.Debug.WriteLine("Time to live: {0}", reply.Options.Ttl);
|
//System.Diagnostics.Debug.WriteLine("Time to live: {0}", reply.Options.Ttl);
|
||||||
//System.Diagnostics.Debug.WriteLine("Don't fragment: {0}", reply.Options.DontFragment);
|
//System.Diagnostics.Debug.WriteLine("Don't fragment: {0}", reply.Options.DontFragment);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine(reply.Status);
|
System.Diagnostics.Debug.WriteLine(reply.Status);
|
||||||
|
failures++;
|
||||||
}
|
}
|
||||||
Thread.Sleep(500);
|
Thread.Sleep(500);
|
||||||
}
|
}
|
||||||
avg = avg / successes;
|
avg = avg / successes;
|
||||||
System.Diagnostics.Debug.WriteLine("min:" + min + " max:" + max + " avg:" + avg);
|
System.Diagnostics.Debug.WriteLine("min:" + min + " max:" + max + " avg:" + avg);
|
||||||
PingItem pi = new PingItem { Date = DateTime.UtcNow, Min = min, Max = max, Avg = avg };
|
PingItem pi = new PingItem { Date = DateTimeOffset.UtcNow, Min = min, Max = max, Avg = avg, Failures = failures };
|
||||||
if(host.PingItems == null)
|
|
||||||
host = _context.Hosts.Include(host => host.PingItems).Where(h => h.HostId == host.HostId).Single();
|
|
||||||
host.PingItems.Add(pi);
|
host.PingItems.Add(pi);
|
||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
Thread.Sleep(5000);
|
Thread.Sleep(5000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddHost(Host host)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("Host added!");
|
||||||
|
if (host.PingItems == null)
|
||||||
|
host = _context.Hosts.Include(host => host.PingItems).Where(h => h.HostId == host.HostId).Single();
|
||||||
|
if(host.Ping)
|
||||||
|
StartPing(host);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"windowsAuthentication": false,
|
"windowsAuthentication": false,
|
||||||
"anonymousAuthentication": true,
|
"anonymousAuthentication": true,
|
||||||
"iisExpress": {
|
"iisExpress": {
|
||||||
"applicationUrl": "http://localhost:65025",
|
"applicationUrl": "http://localhost:4223",
|
||||||
"sslPort": 0
|
"sslPort": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "/",
|
"launchUrl": "",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"dotnetRunMessages": "true",
|
"dotnetRunMessages": "true",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "/",
|
"launchUrl": "/",
|
||||||
"applicationUrl": "http://localhost:5000",
|
"applicationUrl": "http://localhost:4223",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
BIN
ISPChk/ispchk.db
BIN
ISPChk/ispchk.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -10,6 +10,10 @@
|
|||||||
"provider": "jsdelivr",
|
"provider": "jsdelivr",
|
||||||
"library": "bootstrap@4.6.0",
|
"library": "bootstrap@4.6.0",
|
||||||
"destination": "wwwroot/bootstrap/"
|
"destination": "wwwroot/bootstrap/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"library": "Chart.js@2.9.4",
|
||||||
|
"destination": "wwwroot/Chart.js/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
20776
ISPChk/wwwroot/Chart.js/Chart.bundle.js
Normal file
20776
ISPChk/wwwroot/Chart.js/Chart.bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
7
ISPChk/wwwroot/Chart.js/Chart.bundle.min.js
vendored
Normal file
7
ISPChk/wwwroot/Chart.js/Chart.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
47
ISPChk/wwwroot/Chart.js/Chart.css
Normal file
47
ISPChk/wwwroot/Chart.js/Chart.css
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* DOM element rendering detection
|
||||||
|
* https://davidwalsh.name/detect-node-insertion
|
||||||
|
*/
|
||||||
|
@keyframes chartjs-render-animation {
|
||||||
|
from { opacity: 0.99; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartjs-render-monitor {
|
||||||
|
animation: chartjs-render-animation 0.001s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DOM element resizing detection
|
||||||
|
* https://github.com/marcj/css-element-queries
|
||||||
|
*/
|
||||||
|
.chartjs-size-monitor,
|
||||||
|
.chartjs-size-monitor-expand,
|
||||||
|
.chartjs-size-monitor-shrink {
|
||||||
|
position: absolute;
|
||||||
|
direction: ltr;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
visibility: hidden;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartjs-size-monitor-expand > div {
|
||||||
|
position: absolute;
|
||||||
|
width: 1000000px;
|
||||||
|
height: 1000000px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartjs-size-monitor-shrink > div {
|
||||||
|
position: absolute;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
16172
ISPChk/wwwroot/Chart.js/Chart.js
vendored
Normal file
16172
ISPChk/wwwroot/Chart.js/Chart.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
ISPChk/wwwroot/Chart.js/Chart.min.css
vendored
Normal file
1
ISPChk/wwwroot/Chart.js/Chart.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}
|
7
ISPChk/wwwroot/Chart.js/Chart.min.js
vendored
Normal file
7
ISPChk/wwwroot/Chart.js/Chart.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -5,6 +5,7 @@
|
|||||||
<title>ISPChk</title>
|
<title>ISPChk</title>
|
||||||
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<script src="jquery/jquery.js"></script>
|
<script src="jquery/jquery.js"></script>
|
||||||
|
<script src="Chart.js/Chart.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -37,14 +38,38 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<label>Name: <input id="inpNameAdd" /></label><br />
|
<form id="frmHostAdd">
|
||||||
<label>Host: <input id="inpHostNameAdd" /></label><br />
|
<div>
|
||||||
<button id="btnHostAdd">Add Host</button>
|
<label for="inpNameAdd" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="inpNameAdd" aria-describedby="inpNameAddHelp">
|
||||||
|
<div id="inpNameAddHelp" class="form-text">This can be anything.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="inpHostNameAdd" class="form-label">Hostname</label>
|
||||||
|
<input type="text" class="form-control" id="inpHostNameAdd">
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="chkPing">
|
||||||
|
<label class="form-check-label" for="chkPing">Ping</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="chkTCP">
|
||||||
|
<label class="form-check-label" for="chkTCP">TCP</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Add Host</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
Testlel
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
Testlel
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="charts" class="row">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="ispchk.js"></script>
|
||||||
<script src="ispchk.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,6 +1,7 @@
|
|||||||
var hosts = [];
|
var hosts = [];
|
||||||
|
var charts = {};
|
||||||
|
|
||||||
function createHost(host) {
|
async function createHost(host) {
|
||||||
$.postJSON("/api/host", host).then((res, status) => {
|
$.postJSON("/api/host", host).then((res, status) => {
|
||||||
console.log("POSTED" + status, res);
|
console.log("POSTED" + status, res);
|
||||||
if (status === "success") {
|
if (status === "success") {
|
||||||
@ -9,46 +10,139 @@ function createHost(host) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteHost(hostId) {
|
async function deleteHost(hostId) {
|
||||||
$.ajax({
|
var res = await $.ajax({
|
||||||
url: "/api/host/"+hostId,
|
url: "/api/host/" + hostId,
|
||||||
type: "DELETE"
|
type: "DELETE"
|
||||||
}).then(() => {
|
|
||||||
console.log("DELETED");
|
|
||||||
fetchHosts();
|
|
||||||
});
|
});
|
||||||
|
console.log("DELETED");
|
||||||
|
fetchHosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchHosts() {
|
async function fetchHosts() {
|
||||||
var tbody = $("#tblHosts > tbody");
|
var tbody = $("#tblHosts > tbody");
|
||||||
tbody.empty();
|
tbody.empty();
|
||||||
$.getJSON("/api/host").then((res) => {
|
var res = await $.getJSON("/api/host");
|
||||||
console.log("Got hosts: ", res);
|
console.log("Got hosts: ", res);
|
||||||
for (let host of res) {
|
for (let host of res) {
|
||||||
var row = $("<tr>",).appendTo(tbody);
|
var row = $("<tr>",).appendTo(tbody);
|
||||||
$("<td>").text(host.hostId).appendTo(row);
|
$("<td>").text(host.hostId).appendTo(row);
|
||||||
$("<td>").text(host.name).appendTo(row);
|
$("<td>").text(host.name).appendTo(row);
|
||||||
$("<td>").text(host.hostName).appendTo(row);
|
$("<td>").text(host.hostName).appendTo(row);
|
||||||
$("<td>").text("Delete").click(() => {
|
$("<td>").text("Delete").click(() => {
|
||||||
deleteHost(host.hostId);
|
deleteHost(host.hostId);
|
||||||
}).appendTo(row);
|
}).appendTo(row);
|
||||||
}
|
}
|
||||||
});
|
updateGraphs(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#btnDebug').click(function () {
|
async function fetchPings(hostId, start, end) {
|
||||||
|
var res = await $.getJSON("/api/host/" + hostId + "/pings/" + start + "/" + end);
|
||||||
|
console.log("Fetched pings: ", res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function transformChartData(pings) {
|
||||||
|
var res = { date: [], min: [], avg: [], max: [], failures: []};
|
||||||
|
for (let ping of pings) {
|
||||||
|
res.date.push(ping.date);
|
||||||
|
res.min.push(ping.min);
|
||||||
|
res.avg.push(ping.avg);
|
||||||
|
res.max.push(ping.max);
|
||||||
|
res.failures.push(ping.failures);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateGraphs(hosts) {
|
||||||
|
var charts = $("#charts");
|
||||||
|
for (let host of hosts) {
|
||||||
|
console.log("Appending ", host);
|
||||||
|
var before = Date.now() - 300000;
|
||||||
|
var val = await transformChartData(await fetchPings(host.hostId, before, Date.now()));
|
||||||
|
$("<canvas>", { id: "chart_" + host.hostId, width: 200, height: 200 }).appendTo(charts);
|
||||||
|
var ctx = document.getElementById('chart_' + host.hostId).getContext('2d');
|
||||||
|
charts[host.hostId] = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: val.date,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'min',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.3)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
yAxisID: "y-latency",
|
||||||
|
data: val.min
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'avg',
|
||||||
|
backgroundColor: 'rgba(99, 255, 132, 0.3)',
|
||||||
|
borderColor: 'rgb(99, 255, 132)',
|
||||||
|
yAxisID: "y-latency",
|
||||||
|
data: val.avg
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'max',
|
||||||
|
backgroundColor: 'rgba(132, 99, 255, 0.3)',
|
||||||
|
borderColor: 'rgb(132, 99, 255)',
|
||||||
|
yAxisID: "y-latency",
|
||||||
|
data: val.max
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: host.name
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
id: "y-latency",
|
||||||
|
position: "left",
|
||||||
|
ticks: {
|
||||||
|
suggestedMin: 10,
|
||||||
|
suggestedMax: 40
|
||||||
|
}
|
||||||
|
},/*
|
||||||
|
{
|
||||||
|
id: "y-avg",
|
||||||
|
position: "left",
|
||||||
|
ticks: {
|
||||||
|
suggestedMin: 950,
|
||||||
|
suggestedMax: 1050
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "y-max",
|
||||||
|
position: "right",
|
||||||
|
ticks: {
|
||||||
|
suggestedMin: 0,
|
||||||
|
suggestedMax: 100
|
||||||
|
}
|
||||||
|
},*/
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#btnDebug').click(async () => {
|
||||||
console.log("DEBUG");
|
console.log("DEBUG");
|
||||||
$.getJSON("/api/host/" + id + "/pings/" + Date.now + "/" + Date.now).then((res) => {
|
|
||||||
console.log("CALLED: res ", res);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#btnHostAdd').click(function () {
|
$("#frmHostAdd").submit((event) => {
|
||||||
|
event.preventDefault();
|
||||||
var name = $("#inpNameAdd").val();
|
var name = $("#inpNameAdd").val();
|
||||||
var hostname = $("#inpHostNameAdd").val();
|
var hostname = $("#inpHostNameAdd").val();
|
||||||
|
var ping = $('#chkPing').prop('checked')
|
||||||
|
var tcp = $('#chkPing').prop('checked')
|
||||||
var host = {
|
var host = {
|
||||||
name: name,
|
name: name,
|
||||||
hostname: hostname
|
hostname: hostname,
|
||||||
|
ping: ping,
|
||||||
|
tcp: tcp
|
||||||
}
|
}
|
||||||
createHost(host);
|
createHost(host);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user