Ping App
Code
├── Dockerfile
├── api
│ └── v1
│ ├── index.php
│ └── util.php
├── docker-compose.yml
└── public
├── css
│ └── master.css
├── img
│ └── spinner.gif
├── index.html
└── js
└── main.js
version: "3"
services:
web:
container_name: web
image: ifpb/php:7.3-apache-ping
build: .
ports:
- 8080:80
volumes:
- ./:/var/www/html/
FROM php:7.3-apache
RUN apt -y update && apt install -y inetutils-ping
Back-end side
└── api
└── v1
├── index.php
└── util.php
Front-end side
└── public
├── css
│ └── master.css
├── img
│ └── spinner.gif
├── index.html
└── js
└── main.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css" integrity="sha384-Smlep5jCw/wG7hdkwQ/Z5nLIefveQRIY9nfy6xoR1uRYBtpZgI6339F5dgvm/e9B" crossorigin="anonymous">
<link rel="stylesheet" href="css/master.css">
</head>
<body>
<header>
<h1>Host Monitor</h1>
</header>
<main class="container">
<div class="row mb-5 justify-content-center">
<section class="tootlbar">
<input type="text" id="address" placeholder="host" autofocus>
<input type="text" id="count" placeholder="count" size="5">
<button id="pingButton">ping</button>
<img src="img/spinner.gif" alt="spinner" id="spinner" class="invisible">
</section>
</div>
<div class="row">
<div class="col-6">
<div class="card" id="ping-history">
<div class="card-header text-center">
<h2>History</h2>
</div>
<div class="card-body">
<table id="pingTable" class="table table-hover">
<thead>
<tr>
<th>Host</th>
<th>Tx</th>
<th>Rx</th>
<th>Losted</th>
<th>Time (ms)</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<div class="col-6">
<div class="card">
<div class="card-header text-center">
<h2>Stats</h2>
</div>
<div class="card-body">
<h3 id="addressStatus" class="text-center"></h3>
<div class="row">
<div class="col-4">
<div id="stats-info" class="text-center">
<h4>Received</h4>
<span id="transmittedStatus"></span>
<canvas id="stats-info-chart"></canvas>
</div>
</div>
<div class="col-8">
<div id="time-packets">
<h4 class="text-center">Packet Time</h4>
<canvas id="time-packets-chart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js" integrity="sha384-o+RDsa0aLu++PJvFqy8fFScvbHFLtbvScb8AjopnFD+iEQ7wo/CG0xlczd+2O/em" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.0.0/tablesort.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.0.0/src/sorts/tablesort.number.js"></script>
<script src="js/main.js"></script>
</body>
</html>
const addressField = document.querySelector("#address");
const countField = document.querySelector("#count");
const pingButton = document.querySelector("#pingButton");
const spinner = document.querySelector("#spinner");
const pingTable = document.querySelector("#pingTable");
const pingTableContent = document.querySelector("#pingTable tbody");
const addressStatus = document.querySelector("#addressStatus");
const transmittedStatus = document.querySelector("#transmittedStatus");
// Events
pingButton.onclick = () => getPing(addressField.value, countField.value);
document.onkeydown = e => {
if (e.key === "Enter") getPing(addressField.value, countField.value);
};
// Data
let statusValues = {};
function updateStatusValues(ping) {
let address = ping.address;
if (statusValues[address]) {
statusValues[address].transmitted += ping.transmitted;
statusValues[address].losted += ping.losted;
statusValues[address].received += ping.received;
statusValues[address].times.concat(ping.times);
} else {
statusValues[address] = {
transmitted: ping.transmitted,
losted: ping.losted,
received: ping.received,
times: ping.times
};
}
}
// Ping
function getPing(addressValue, countValue) {
spinner.classList.remove("invisible");
let url = `../api/v1/?host=${addressValue}&count=${countValue}`;
fetch(url)
.then(res => res.json())
.then(json => addPing(json));
}
function addPing(pingResponse) {
if (pingResponse.error) {
alert("Erro na obtenção do Ping!");
} else {
const ping = pingParser(pingResponse);
updateStatusValues(ping);
loadPingHistory(ping);
loadStatssInfo(ping.address);
loadTimePackets(ping.times);
}
spinner.classList.add("invisible");
addressField.focus();
addressField.value = "";
countField.value = "";
}
function pingParser(pingResponse) {
const address = pingResponse.host;
const transmitted = parseInt(pingResponse.statistics.transmitted);
const received = parseInt(pingResponse.statistics.received);
const losted = transmitted - received;
const totalTimes = pingResponse.packets
.reduce((t, p) => parseFloat(p.time) + t, 0)
.toFixed(1);
const times = pingResponse.packets.map(p => p.time);
return { address, transmitted, received, losted, totalTimes, times };
}
// Ping History
function loadPingHistory(ping) {
const values = [
ping.address,
ping.transmitted,
ping.received,
ping.losted,
ping.totalTimes
];
pingTableContent.insertAdjacentHTML(
"afterbegin",
`<tr data-address="${ping.address}"><td>${values.join(
"</td><td>"
)}</td></tr>`
);
pingTableContent.firstChild.onclick = () => {
const row = event.target.parentNode;
const address = row.dataset.address;
loadStatssInfo(address);
loadTimePackets(statusValues[address].times);
Array.from(pingTableContent.childNodes).map(r =>
r.classList.remove("selected")
);
row.classList.add("selected");
};
let sort = new Tablesort(pingTable);
}
// Stats Info
let statsInfoChart;
const statsInfoChartCanvas = document.querySelector("#stats-info-chart");
function createTransmittedChart(transmitted) {
const data = {
labels: ["Losted", "Received"],
datasets: [
{
data: transmitted,
backgroundColor: ["#FF6384", "#36A2EB"],
hoverBackgroundColor: ["#FF6384", "#36A2EB"]
}
]
};
const options = {
legend: {
display: false
}
};
const chart = new Chart(statsInfoChartCanvas, {
type: "doughnut",
data: data,
options: options
});
return chart;
}
function loadStatssInfo(address) {
const transmitted = statusValues[address].transmitted;
const losted = statusValues[address].losted;
const received = statusValues[address].received;
const transmittedPercentage = [
(losted / transmitted) * 100,
(received / transmitted) * 100
];
addressStatus.innerHTML = address;
transmittedStatus.innerHTML = `${transmitted} (<span id="lostedStatus">${losted}</span> / <span id="receivedStatus">${received}</span>)`;
if (statsInfoChart) {
statsInfoChart.data.datasets[0].data = transmittedPercentage;
statsInfoChart.update();
} else {
statsInfoChart = createTransmittedChart(transmittedPercentage);
}
}
// Time Packets
let timePacketsChart;
const timePacketsChartCanvas = document.querySelector("#time-packets-chart");
function createTimePacketsChart(times) {
const data = {
labels: Object.keys(times).map(t => +t + 1),
datasets: [{ data: times }]
};
const options = {
legend: {
display: false
}
};
const chart = new Chart(timePacketsChartCanvas, {
type: "line",
data: data,
options: options
});
return chart;
}
function loadTimePackets(times) {
if (timePacketsChart) {
timePacketsChart.data.labels = Object.keys(times).map(t => +t + 1);
timePacketsChart.data.datasets[0].data = times;
timePacketsChart.update();
} else {
timePacketsChart = createTimePacketsChart(times);
}
}
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
header {
padding: 0.2rem 0;
text-align: center;
background: #fafafa;
border-bottom: 1px solid #ccc;
margin-bottom: 3rem;
}
#lostedStatus {
color: #ff6384;
}
#receivedStatus {
color: #36a2eb;
}
tr:hover {
cursor: pointer;
}
tr.selected {
background-color: rgba(0, 0, 0, 0.075);
}