Этот коммит содержится в:
Igor V Belousov 2019-11-12 20:23:32 +03:00
родитель 5e4ad2b0eb
Коммит 04d7efa232
15 изменённых файлов: 511 добавлений и 37 удалений

5
README.md Обычный файл
Просмотреть файл

@ -0,0 +1,5 @@
# Test work
1. `docker-compose build`
2. `docker-compose run --rm app php src/baseInit.php`
3. `docker-compose up`

Просмотреть файл

@ -5,3 +5,5 @@ RUN set -ex \
&& docker-php-ext-install pdo pdo_mysql gd zip
WORKDIR /app
RUN chmod u+s /bin/ping

Просмотреть файл

@ -216,4 +216,177 @@ a.btn, a.btn:hover, a.btn:focus {
select {
height: 1.5rem;
padding-left: 0 !important;
}
}
.servers-tree * {
list-style-type: none;
}
.servers-tree {
margin: 0;
padding: 0;
}
.servers-tree-group {
cursor: pointer;
user-select: none;
}
.servers-tree-group::before {
content: " ";
display: inline-block;
margin-right: 6px;
border-top: .375rem solid transparent;
border-bottom: .375rem solid transparent;
border-left: .375rem solid black;
}
.servers-tree-group--down::before {
transform: rotate(90deg);
}
.servers-tree-group__list {
display: none;
}
.servers-tree-group__list--active {
display: block;
}
.modal {
display: none;
}
.modal--active {
display: block;
}
.modal__bg {
height: 100%;
width: 100%;
background: rgba(0, 0, 0, .3);
position: fixed;
top: 0;
left: 0;
z-index: 1;
}
.modal__window {
width: 300px;
position: fixed;
top: calc(50% - 96px);
left: calc(50vw - 150px);
height: 190px;
z-index: 2;
}
.modal-window--spiner::before {
content: "";
display: block;
color: black;
font-size: .25rem;
margin: 0;
width: 1em;
height: 1em;
border-radius: 50%;
position: absolute;
animation: wloadingkf 1.3s infinite linear;
transform: translate3d(0, -50%, 0);
font-family: monospace;
left: 50%;
top: 50%;
z-index: -1;
}
.modal-window--spiner .modal-window__body {
display: none;
}
.modal-window {
border: 1px solid #666;
width: 100%;
position: relative;
height: 100%;
background: #eee;
z-index: 2;
border-radius: .325rem;
box-shadow: 0 .125rem .25rem #666;
}
.modal-window__title {
text-align: center;
}
.modal-window__close {
position: absolute;
right: 0;
top: .125rem;
width: 1rem;
height: 1rem;
opacity: 0.3;
cursor: pointer;
}
.modal-window__close:hover {
opacity: 1;
}
.modal-window__close:before,
.modal-window__close:after {
position: absolute;
left: .25rem;
content: ' ';
height: 1.0625rem;
width: .125rem;
background-color: #333;
}
.modal-window__close:before {
transform: rotate(45deg);
}
.modal-window__close:after {
transform: rotate(-45deg);
}
@keyframes wloadingkf {
0%,
100% {
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
}
12.5% {
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
25% {
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
}
37.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
50% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
62.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
}
75% {
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
}
87.5% {
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
.modal-window-body {
padding: 1.5rem 0;
}
.modal-window-body__label {
font-weight: 700;
padding: 0 .5rem;
font-size: 85%;
min-width: 7.5rem;
display: inline-block;
}

Просмотреть файл

@ -22,11 +22,55 @@ d.querySelectorAll(".js-server-btn-delete").forEach(v => {
deleteEvent(v, "You're sure to delete this server?", "/servers/delete")
});
d.querySelector(".js-server-form-on-submit").addEventListener("submit", e=>{
let check_ip = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
let ip = e.target.ip;
if (!check_ip.exec(ip.value)){
e.preventDefault()
ip.focus()
}
let serverFormOnSubmit;
serverFormOnSubmit = d.querySelector(".js-server-form-on-submit");
if (serverFormOnSubmit) {
serverFormOnSubmit.addEventListener("submit", e => {
let check_ip = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
let ip = e.target.ip;
if (!check_ip.exec(ip.value)) {
e.preventDefault();
ip.focus()
}
});
}
d.querySelectorAll(".js-servers-tree-group").forEach(v => {
v.addEventListener("click", e => {
let parent = e.target.parentElement.querySelector(".js-servers-tree-group-list");
if (parent) {
parent.classList.toggle("servers-tree-group__list--active");
}
e.target.classList.toggle("servers-tree-group--down");
})
});
d.querySelectorAll(".js-modal-close").forEach(v => {
v.addEventListener("click", () => {
d.querySelector(".modal").classList.remove("modal--active")
})
});
d.querySelectorAll(".js-ping").forEach(v => {
v.addEventListener("click", e => {
d.querySelector(".modal").classList.add("modal--active");
let mwClass = d.querySelector(".modal-window").classList;
mwClass.add("modal-window--spiner");
let xhr = new XMLHttpRequest();
xhr.open("POST", "/ping/" + e.target.dataset.id);
xhr.send();
xhr.onload = () => {
let resp = JSON.parse(xhr.response);
if (resp.status === 'ok') {
['name', 'ip', 'lost', 'transmitted', 'received'].map(i => {
d.querySelector('.js-ping-result-' + i).innerHTML = resp[i]
})
}
mwClass.remove("modal-window--spiner");
};
})
});

Просмотреть файл

@ -4,6 +4,7 @@ namespace MyApp\Controller;
use MyApp\Core\Controller;
use MyApp\View\DefaultView as View;
use MyApp\Service\DefaultService;
class DefaultController extends Controller
{
@ -12,6 +13,6 @@ class DefaultController extends Controller
*/
public function index()
{
$this->htmlResponse((new View())->index());
$this->htmlResponse((new View())->index((new DefaultService())->getTree()));
}
}

44
src/controllers/PingController.php Обычный файл
Просмотреть файл

@ -0,0 +1,44 @@
<?php
namespace MyApp\Controller;
use MyApp\Core\Controller;
use MyApp\Model\ServersModel;
use MyApp\Service\PingService;
use MyApp\View\PingView as View;
class PingController extends Controller
{
/**
* @RouteRegExp = "|^/ping/history/(?<id>\d+)$|"
* @HttpMethod = "GET"
*
* @param $id
*/
public function history($id)
{
if ($model = (new PingService())->getTableData((int) $id)) {
$servers = ServersModel::find([(int) $id]);
$server_name = $servers ? $servers[0]->getName() : '';
$this->htmlResponse((new View())->history($model, $server_name));
} else {
$this->redirect('/');
}
}
/**
* @RouteRegExp = "|^/ping/(?<server_id>\d+)$|"
* @HttpMethod = "POST"
*
* @param $server_id
*/
public function ping($server_id)
{
$out = (new PingService())->ping($server_id);
if ($out) {
$this->jsonResponse((object) array_merge(['status' => 'ok'], $out));
} else {
$this->jsonResponse((object) ['status' => 'error']);
}
}
}

Просмотреть файл

@ -65,9 +65,9 @@ class TallyLogsModel extends Model
}
/**
* @return int
* @return string
*/
public function getTime(): int
public function getTime(): string
{
return $this->time;
}
@ -79,7 +79,7 @@ class TallyLogsModel extends Model
*/
public function setTime(DateTime $time): self
{
$this->time = $time->getTimestamp();
$this->time = $time->format('Y-m-d H:i:s');
return $this;
}
@ -143,4 +143,16 @@ class TallyLogsModel extends Model
return $this;
}
/**
* @param ServersModel $server
*
* @return TallyLogsModel
*/
public function setServer(ServersModel $server): self
{
$this->server = $server->getId();
return $this;
}
}

61
src/services/DefaultService.php Обычный файл
Просмотреть файл

@ -0,0 +1,61 @@
<?php
namespace MyApp\Service;
class DefaultService
{
private function getGroupsArray(
?array $models,
array $servers_arr,
$parent_id = null
) {
$out = [];
if ($models) {
foreach ($models as $item) {
/** @var \MyApp\Model\GroupsModel $item */
if (
$item->getParent() === $parent_id) {
$out_item = [
'name' => $item->getName(),
'type' => 'group',
'items' => $this->getGroupsArray($models, $servers_arr,
$item->getId()),
];
if (array_key_exists($item->getId(), $servers_arr)) {
$out_item['items'] = array_merge($out_item['items'],
$servers_arr[$item->getId()]);
}
$out[] = $out_item;
}
}
}
return $out;
}
public function getTree()
{
$groups = (new GroupsService())->getTableData();
$servers = (new ServersService())->getTableData();
$servers_arr = [];
if ($servers) {
foreach ($servers as $server) {
$servers_arr[$server->getGroup()][] = [
'name' => $server->getName(),
'type' => 'server',
'id' => $server->getId(),
];
}
}
$out = [];
$out = $this->getGroupsArray($groups, $servers_arr);
if (array_key_exists(null, $servers_arr)) {
$out = array_merge($out, $servers_arr[null]);
}
return $out;
}
}

Просмотреть файл

@ -35,14 +35,17 @@ class GroupsService
public function sortForSelect($models, $parent_id = null, $level = 1)
{
$out = [];
foreach ($models as $item) {
/** GroupsModel $item */
if (
$item->getParent() === $parent_id) {
$item->{'select-level'} = str_repeat('-', $level);
$out[] = $item;
$out = array_merge($out,
$this->sortForSelect($models, $item->getId(), $level + 1));
if ($models) {
foreach ($models as $item) {
/** GroupsModel $item */
if (
$item->getParent() === $parent_id) {
$item->{'select-level'} = str_repeat('-', $level);
$out[] = $item;
$out = array_merge($out,
$this->sortForSelect($models, $item->getId(),
$level + 1));
}
}
}

Просмотреть файл

@ -9,26 +9,39 @@ use DateTime;
class PingService
{
public function ping($server_id): ? TallyLogsModel
public function getTableData(int $server_id): ?array
{
$server = ServersModel::find([$server_id],null,null,[],'','1');
$models = TallyLogsModel::find(null, null, '{server} = :server_id',
['server_id' => $server_id], 'id DESC');
if (!empty($server)){
return $models;
}
public function ping($server_id): ?array
{
$server = ServersModel::find([$server_id], null, null, [], '', '1');
if (!empty($server)) {
$ip = $server[0]->getIp();
exec("ping -c 10 $ip", $output, $status);
foreach ($output as $line) {
if (preg_match('/(?<transmitted>\d+)[\w\ ]*,\ (?<received>\d+)[\w\ ]*, (?<lost>\d+)%/',
$line, $m)) {
$log = new TallyLogsModel();
$log->setTime(new DateTime())
->setLost($m['lost'])
->setTransmitted($m['transmitted'])
->setReceived($m['received']);
$log = (new TallyLogsModel())->setTime(new DateTime())
->setLost((int) $m['lost'])
->setTransmitted((int) $m['transmitted'])
->setReceived((int) $m['received'])
->setServer($server[0]);
Model::save([$log]);
return $log;
return [
'name' => $server[0]->getName(),
'ip' => $server[0]->getIp(),
'lost' => $m['lost'],
'transmitted' => $m['transmitted'],
'received' => $m['received'],
];
}
}
}

Просмотреть файл

@ -18,12 +18,15 @@ class ServersService
{
$models = ServersModel::find(null, null, '1 = 1');
$records = [];
foreach ($models as $model) {
/** @var ServersModel $model */
if ($group_id = $model->getGroup()) {
$records[] = $group_id;
if ($models) {
foreach ($models as $model) {
/** @var ServersModel $model */
if ($group_id = $model->getGroup()) {
$records[] = $group_id;
}
}
}
if (count($records)) {
if ($groups = GroupsModel::find($records)) {
$groups_names = [];

Просмотреть файл

@ -1,5 +1,70 @@
<?php
require __DIR__ . '/../shared/head.php'; ?>
require __DIR__.'/../shared/head.php'; ?>
<?php if (count($this->tree)): ?>
<ul class="servers-tree">
<?php
(new class() {
public function __invoke($tree)
{
foreach ($tree as $item) {
echo '<li>';
if ('group' === $item['type']) {
?>
<span
class="servers-tree__group servers-tree-group js-servers-tree-group"><?php echo $item['name']; ?></span>
<?php
if (count($item['items'])) {
?>
<ul class="servers-tree-group__list js-servers-tree-group-list">
<?php $this($item['items']); ?>
</ul>
<?php
}
} else {
?>
<?php echo $item['name']; ?>
<div class="btn d-inline-block js-ping"
data-id="<?= $item['id']; ?>">ping
</div>
<a href="/ping/history/<?= $item['id']; ?>">ping history</a>
<?php
}
echo '</li>';
}
}
})($this->tree);
?>
</ul>
<?php endif; ?>
<div class="modal">
<div class="modal__bg js-modal-close">
</div>
<div class="modal__window">
<div class="modal-window">
<div class="modal-window__title">
Ping
</div>
<div class="modal-window__close js-modal-close"></div>
<div class="modal-window__body modal-window-body js-modal-body">
<div><span class="modal-window-body__label">Server:</span> <span
class="modal-window-body__result js-ping-result-name"></span>
</div>
<div><span class="modal-window-body__label">IP Address:</span> <span
class="modal-window-body__result js-ping-result-ip"></span>
</div>
<div><span class="modal-window-body__label">Transmitted:</span> <span
class="modal-window-body__result js-ping-result-transmitted"></span>
</div>
<div><span class="modal-window-body__label">Received:</span> <span
class="modal-window-body__result js-ping-result-received"></span>
</div>
<div><span class="modal-window-body__label">Lost %:</span> <span
class="modal-window-body__result js-ping-result-lost"></span>
</div>
</div>
</div>
</div>
</div>
<?php
require __DIR__ . '/../shared/footer.php';
require __DIR__.'/../shared/footer.php';

31
src/templates/ping/index.tpl.php Обычный файл
Просмотреть файл

@ -0,0 +1,31 @@
<?php
require __DIR__.'/../shared/head.php'; ?>
<?php if ($this->table): ?>
<table>
<tr>
<th>Time</th>
<th>Transmitted</th>
<th>Received</th>
<th>Lost</th>
</tr>
<?php foreach ($this->table as $item): ?>
<tr>
<td>
<?= $item->getTime(); ?>
</td>
<td>
<?= $item->getTransmitted(); ?>
</td>
<td>
<?= $item->getReceived(); ?>
</td>
<td>
<?= $item->getLost(); ?>%
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<?php
require __DIR__.'/../shared/footer.php';

Просмотреть файл

@ -6,8 +6,10 @@ use MyApp\Core\View;
class DefaultView extends View
{
public function index(): string
public function index($tree): string
{
$this->{'tree'} = $tree;
return $this->setTitle('Home')->render('default/index');
}
}

15
src/views/PingView.php Обычный файл
Просмотреть файл

@ -0,0 +1,15 @@
<?php
namespace MyApp\View;
use MyApp\Core\View;
class PingView extends View
{
public function history($table, $server_name)
{
$this->{'table'} = $table;
return $this->setTitle('History of ping to '.trim($server_name))->render('ping/index');
}
}