Итерация 3: инвентарь устройств
- Миграция devices (name, type, ip, mac, hostname, vendor, os, location, importance, status) - Device model + DeviceRepository с полным CRUD - DeviceService для бизнес-логики - MergeSuggestionService для предложений объединения (MAC→90%, hostname→60%) - DeviceController: список, создание, редактирование, карточка, удаление, создание из discovered_host - Шаблоны: devices/index, devices/form, devices/show - Dashboard с реальными счётчиками (устройства, новые находки) - Кнопка «Создать устройство» в Discovery для новых хостов - DeviceRepository + DeviceService + DeviceController в DI
This commit is contained in:
parent
177e44f015
commit
15772bc17e
|
|
@ -9,10 +9,24 @@ use Psr\Http\Message\ServerRequestInterface;
|
|||
|
||||
class DashboardController
|
||||
{
|
||||
private \Domovoy\Repositories\DeviceRepository $deviceRepository;
|
||||
private \Domovoy\Repositories\ScanJobRepository $scanJobRepository;
|
||||
|
||||
public function __construct(
|
||||
\Domovoy\Repositories\DeviceRepository $deviceRepository,
|
||||
\Domovoy\Repositories\ScanJobRepository $scanJobRepository
|
||||
) {
|
||||
$this->deviceRepository = $deviceRepository;
|
||||
$this->scanJobRepository = $scanJobRepository;
|
||||
}
|
||||
|
||||
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
ob_start();
|
||||
$username = $_SESSION['username'] ?? 'User';
|
||||
$deviceCount = $this->deviceRepository->getCount();
|
||||
$newDiscoveries = $this->deviceRepository->getNewDiscoveriesCount();
|
||||
$recentScans = $this->scanJobRepository->findRecent(5);
|
||||
require dirname(__DIR__, 2) . '/templates/dashboard/index.php';
|
||||
$body = ob_get_clean();
|
||||
$response->getBody()->write($body);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Controllers;
|
||||
|
||||
use Domovoy\Models\Device;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class DeviceController
|
||||
{
|
||||
private \Domovoy\Services\Inventory\DeviceService $deviceService;
|
||||
private \Domovoy\Repositories\DiscoveredHostRepository $discoveredHostRepository;
|
||||
|
||||
public function __construct(
|
||||
\Domovoy\Services\Inventory\DeviceService $deviceService,
|
||||
\Domovoy\Repositories\DiscoveredHostRepository $discoveredHostRepository
|
||||
) {
|
||||
$this->deviceService = $deviceService;
|
||||
$this->discoveredHostRepository = $discoveredHostRepository;
|
||||
}
|
||||
|
||||
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
ob_start();
|
||||
$username = $_SESSION['username'] ?? 'User';
|
||||
$devices = $this->deviceService->getAllDevices();
|
||||
require dirname(__DIR__, 2) . '/templates/devices/index.php';
|
||||
$body = ob_get_clean();
|
||||
$response->getBody()->write($body);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function createForm(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
ob_start();
|
||||
$username = $_SESSION['username'] ?? 'User';
|
||||
$device = new Device();
|
||||
$types = Device::$types;
|
||||
$importances = Device::$importances;
|
||||
require dirname(__DIR__, 2) . '/templates/devices/form.php';
|
||||
$body = ob_get_clean();
|
||||
$response->getBody()->write($body);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function create(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$device = new Device();
|
||||
$device->name = trim($data['name'] ?? '');
|
||||
$device->type = $data['type'] ?? 'unknown';
|
||||
$device->description = trim($data['description'] ?? '') ?: null;
|
||||
$device->primaryIp = trim($data['primary_ip'] ?? '') ?: null;
|
||||
$device->macAddress = trim($data['mac_address'] ?? '') ?: null;
|
||||
$device->hostname = trim($data['hostname'] ?? '') ?: null;
|
||||
$device->vendor = trim($data['vendor'] ?? '') ?: null;
|
||||
$device->osName = trim($data['os_name'] ?? '') ?: null;
|
||||
$device->osVersion = trim($data['os_version'] ?? '') ?: null;
|
||||
$device->location = trim($data['location'] ?? '') ?: null;
|
||||
$device->importance = $data['importance'] ?? 'normal';
|
||||
|
||||
$this->deviceService->updateDevice($device);
|
||||
|
||||
return $response
|
||||
->withHeader('Location', '/devices')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
public function show(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
|
||||
{
|
||||
$id = (int)$args['id'];
|
||||
$device = $this->deviceService->getDevice($id);
|
||||
|
||||
if ($device === null) {
|
||||
return $response->withStatus(404)->write('Device not found');
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$username = $_SESSION['username'] ?? 'User';
|
||||
$types = Device::$types;
|
||||
$importances = Device::$importances;
|
||||
require dirname(__DIR__, 2) . '/templates/devices/show.php';
|
||||
$body = ob_get_clean();
|
||||
$response->getBody()->write($body);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function editForm(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
|
||||
{
|
||||
$id = (int)$args['id'];
|
||||
$device = $this->deviceService->getDevice($id);
|
||||
|
||||
if ($device === null) {
|
||||
return $response->withStatus(404)->write('Device not found');
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$username = $_SESSION['username'] ?? 'User';
|
||||
$types = Device::$types;
|
||||
$importances = Device::$importances;
|
||||
require dirname(__DIR__, 2) . '/templates/devices/form.php';
|
||||
$body = ob_get_clean();
|
||||
$response->getBody()->write($body);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function update(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
|
||||
{
|
||||
$id = (int)$args['id'];
|
||||
$device = $this->deviceService->getDevice($id);
|
||||
|
||||
if ($device === null) {
|
||||
return $response->withStatus(404)->write('Device not found');
|
||||
}
|
||||
|
||||
$data = $request->getParsedBody();
|
||||
$device->name = trim($data['name'] ?? '');
|
||||
$device->type = $data['type'] ?? 'unknown';
|
||||
$device->description = trim($data['description'] ?? '') ?: null;
|
||||
$device->primaryIp = trim($data['primary_ip'] ?? '') ?: null;
|
||||
$device->macAddress = trim($data['mac_address'] ?? '') ?: null;
|
||||
$device->hostname = trim($data['hostname'] ?? '') ?: null;
|
||||
$device->vendor = trim($data['vendor'] ?? '') ?: null;
|
||||
$device->osName = trim($data['os_name'] ?? '') ?: null;
|
||||
$device->osVersion = trim($data['os_version'] ?? '') ?: null;
|
||||
$device->location = trim($data['location'] ?? '') ?: null;
|
||||
$device->importance = $data['importance'] ?? 'normal';
|
||||
$device->status = $data['status'] ?? 'active';
|
||||
|
||||
$this->deviceService->updateDevice($device);
|
||||
|
||||
return $response
|
||||
->withHeader('Location', '/devices/' . $device->id)
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
public function delete(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
|
||||
{
|
||||
$id = (int)$args['id'];
|
||||
$this->deviceService->deleteDevice($id);
|
||||
|
||||
return $response
|
||||
->withHeader('Location', '/devices')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
public function createFromHost(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
$hostId = $data['host_id'] ?? null;
|
||||
$name = trim($data['name'] ?? '');
|
||||
|
||||
if ($hostId === null || $name === '') {
|
||||
return $response->withStatus(400)->write('Host ID and name required');
|
||||
}
|
||||
|
||||
$host = $this->discoveredHostRepository->findById((int)$hostId);
|
||||
if ($host === null) {
|
||||
return $response->withStatus(404)->write('Host not found');
|
||||
}
|
||||
|
||||
$device = $this->deviceService->createFromDiscoveredHost(
|
||||
$name,
|
||||
$host->ipAddress,
|
||||
$host->macAddress,
|
||||
$host->hostname,
|
||||
$host->vendor
|
||||
);
|
||||
|
||||
// Mark host as accepted
|
||||
$host->status = 'accepted';
|
||||
$host->matchedDeviceId = (string)$device->id;
|
||||
$this->discoveredHostRepository->save($host);
|
||||
|
||||
return $response
|
||||
->withHeader('Location', '/devices/' . $device->id)
|
||||
->withStatus(302);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Models;
|
||||
|
||||
class Device
|
||||
{
|
||||
public ?int $id = null;
|
||||
public string $name = '';
|
||||
public string $type = 'unknown';
|
||||
public ?string $description = null;
|
||||
public ?string $primaryIp = null;
|
||||
public ?string $macAddress = null;
|
||||
public ?string $hostname = null;
|
||||
public ?string $vendor = null;
|
||||
public ?string $osName = null;
|
||||
public ?string $osVersion = null;
|
||||
public ?string $location = null;
|
||||
public string $importance = 'normal';
|
||||
public string $status = 'active';
|
||||
public ?\DateTimeImmutable $createdAt = null;
|
||||
public ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
$obj = new self();
|
||||
$obj->id = (int)$data['id'];
|
||||
$obj->name = $data['name'];
|
||||
$obj->type = $data['type'];
|
||||
$obj->description = $data['description'] ?? null;
|
||||
$obj->primaryIp = $data['primary_ip'] ?? null;
|
||||
$obj->macAddress = $data['mac_address'] ?? null;
|
||||
$obj->hostname = $data['hostname'] ?? null;
|
||||
$obj->vendor = $data['vendor'] ?? null;
|
||||
$obj->osName = $data['os_name'] ?? null;
|
||||
$obj->osVersion = $data['os_version'] ?? null;
|
||||
$obj->location = $data['location'] ?? null;
|
||||
$obj->importance = $data['importance'];
|
||||
$obj->status = $data['status'];
|
||||
$obj->createdAt = new \DateTimeImmutable($data['created_at']);
|
||||
$obj->updatedAt = new \DateTimeImmutable($data['updated_at']);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static array $types = [
|
||||
'server', 'router', 'nas', 'desktop', 'laptop',
|
||||
'phone', 'printer', 'iot', 'vm', 'container_host', 'unknown',
|
||||
];
|
||||
|
||||
public static array $importances = ['critical', 'high', 'normal', 'low'];
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Repositories;
|
||||
|
||||
use Domovoy\Models\Device;
|
||||
use PDO;
|
||||
|
||||
class DeviceRepository
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById(int $id): ?Device
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM devices WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ? Device::fromArray($row) : null;
|
||||
}
|
||||
|
||||
public function findAll(string $sortBy = 'name'): array
|
||||
{
|
||||
$allowed = ['name', 'type', 'status', 'primary_ip', 'created_at'];
|
||||
$sort = in_array($sortBy, $allowed, true) ? $sortBy : 'name';
|
||||
$stmt = $this->pdo->query("SELECT * FROM devices ORDER BY {$sort} ASC");
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = Device::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findByMac(string $macAddress): ?Device
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM devices WHERE mac_address = :mac');
|
||||
$stmt->execute(['mac' => strtolower($macAddress)]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ? Device::fromArray($row) : null;
|
||||
}
|
||||
|
||||
public function findByName(string $name): ?Device
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM devices WHERE name = :name');
|
||||
$stmt->execute(['name' => $name]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ? Device::fromArray($row) : null;
|
||||
}
|
||||
|
||||
public function getCount(): int
|
||||
{
|
||||
return (int)$this->pdo->query('SELECT COUNT(*) FROM devices')->fetchColumn();
|
||||
}
|
||||
|
||||
public function getNewDiscoveriesCount(): int
|
||||
{
|
||||
$stmt = $this->pdo->query("SELECT COUNT(*) FROM discovered_hosts WHERE status = 'new'");
|
||||
return (int)$stmt->fetchColumn();
|
||||
}
|
||||
|
||||
public function save(Device $device): void
|
||||
{
|
||||
$now = (new \DateTimeImmutable())->format('Y-m-d H:i:s');
|
||||
if ($device->id === null) {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO devices (name, type, description, primary_ip, mac_address, hostname,
|
||||
vendor, os_name, os_version, location, importance, status, created_at, updated_at)
|
||||
VALUES (:name, :type, :description, :primary_ip, :mac_address, :hostname,
|
||||
:vendor, :os_name, :os_version, :location, :importance, :status, :created_at, :updated_at)'
|
||||
);
|
||||
$stmt->execute([
|
||||
'name' => $device->name,
|
||||
'type' => $device->type,
|
||||
'description' => $device->description,
|
||||
'primary_ip' => $device->primaryIp,
|
||||
'mac_address' => $device->macAddress !== null ? strtolower($device->macAddress) : null,
|
||||
'hostname' => $device->hostname,
|
||||
'vendor' => $device->vendor,
|
||||
'os_name' => $device->osName,
|
||||
'os_version' => $device->osVersion,
|
||||
'location' => $device->location,
|
||||
'importance' => $device->importance,
|
||||
'status' => $device->status,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$device->id = (int)$this->pdo->lastInsertId();
|
||||
} else {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE devices SET name = :name, type = :type, description = :description,
|
||||
primary_ip = :primary_ip, mac_address = :mac_address, hostname = :hostname,
|
||||
vendor = :vendor, os_name = :os_name, os_version = :os_version,
|
||||
location = :location, importance = :importance, status = :status,
|
||||
updated_at = :updated_at WHERE id = :id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $device->id,
|
||||
'name' => $device->name,
|
||||
'type' => $device->type,
|
||||
'description' => $device->description,
|
||||
'primary_ip' => $device->primaryIp,
|
||||
'mac_address' => $device->macAddress !== null ? strtolower($device->macAddress) : null,
|
||||
'hostname' => $device->hostname,
|
||||
'vendor' => $device->vendor,
|
||||
'os_name' => $device->osName,
|
||||
'os_version' => $device->osVersion,
|
||||
'location' => $device->location,
|
||||
'importance' => $device->importance,
|
||||
'status' => $device->status,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM devices WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Services\Inventory;
|
||||
|
||||
use Domovoy\Models\Device;
|
||||
use Domovoy\Repositories\DeviceRepository;
|
||||
|
||||
class DeviceService
|
||||
{
|
||||
private DeviceRepository $deviceRepository;
|
||||
|
||||
public function __construct(DeviceRepository $deviceRepository)
|
||||
{
|
||||
$this->deviceRepository = $deviceRepository;
|
||||
}
|
||||
|
||||
public function createFromDiscoveredHost(
|
||||
string $name,
|
||||
string $ipAddress,
|
||||
?string $macAddress = null,
|
||||
?string $hostname = null,
|
||||
?string $vendor = null
|
||||
): Device {
|
||||
$device = new Device();
|
||||
$device->name = $name;
|
||||
$device->primaryIp = $ipAddress;
|
||||
$device->macAddress = $macAddress;
|
||||
$device->hostname = $hostname;
|
||||
$device->vendor = $vendor;
|
||||
$device->type = 'unknown';
|
||||
$device->status = 'active';
|
||||
$device->importance = 'normal';
|
||||
|
||||
$this->deviceRepository->save($device);
|
||||
return $device;
|
||||
}
|
||||
|
||||
public function getAllDevices(): array
|
||||
{
|
||||
return $this->deviceRepository->findAll();
|
||||
}
|
||||
|
||||
public function getDevice(int $id): ?Device
|
||||
{
|
||||
return $this->deviceRepository->findById($id);
|
||||
}
|
||||
|
||||
public function updateDevice(Device $device): void
|
||||
{
|
||||
$this->deviceRepository->save($device);
|
||||
}
|
||||
|
||||
public function deleteDevice(int $id): void
|
||||
{
|
||||
$this->deviceRepository->delete($id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Services\Inventory;
|
||||
|
||||
use Domovoy\Models\DiscoveredHost;
|
||||
use Domovoy\Repositories\DeviceRepository;
|
||||
|
||||
class MergeSuggestionService
|
||||
{
|
||||
private DeviceRepository $deviceRepository;
|
||||
|
||||
public function __construct(DeviceRepository $deviceRepository)
|
||||
{
|
||||
$this->deviceRepository = $deviceRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find merge suggestions for a discovered host.
|
||||
* Returns array of ['device' => Device, 'confidence' => int, 'reason' => string]
|
||||
*/
|
||||
public function findSuggestions(DiscoveredHost $host): array
|
||||
{
|
||||
$suggestions = [];
|
||||
|
||||
// High confidence: MAC address match
|
||||
if ($host->macAddress !== null) {
|
||||
$device = $this->deviceRepository->findByMac($host->macAddress);
|
||||
if ($device !== null) {
|
||||
$suggestions[] = [
|
||||
'device' => $device,
|
||||
'confidence' => 90,
|
||||
'reason' => 'MAC адрес совпадает',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Medium confidence: hostname match
|
||||
if ($host->hostname !== null) {
|
||||
$device = $this->deviceRepository->findByName($host->hostname);
|
||||
if ($device !== null) {
|
||||
$suggestions[] = [
|
||||
'device' => $device,
|
||||
'confidence' => 60,
|
||||
'reason' => 'Hostname совпадает',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Low confidence: IP match
|
||||
if ($host->primaryIp !== null) {
|
||||
// Would need findByIp in repository — skip for now
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class CreateDevices extends AbstractMigration
|
||||
{
|
||||
public function change(): void
|
||||
{
|
||||
$table = $this->table('devices');
|
||||
$table
|
||||
->addColumn('name', 'string', ['limit' => 255, 'null' => false])
|
||||
->addColumn('type', 'string', ['limit' => 30, 'null' => false, 'default' => 'unknown'])
|
||||
->addColumn('description', 'text', ['null' => true])
|
||||
->addColumn('primary_ip', 'string', ['limit' => 45, 'null' => true])
|
||||
->addColumn('mac_address', 'string', ['limit' => 17, 'null' => true])
|
||||
->addColumn('hostname', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('vendor', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('os_name', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('os_version', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('location', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('importance', 'string', ['limit' => 20, 'null' => false, 'default' => 'normal'])
|
||||
->addColumn('status', 'string', ['limit' => 20, 'null' => false, 'default' => 'active'])
|
||||
->addColumn('created_at', 'datetime', ['null' => false, 'default' => 'CURRENT_TIMESTAMP'])
|
||||
->addColumn('updated_at', 'datetime', ['null' => false, 'default' => 'CURRENT_TIMESTAMP'])
|
||||
->addIndex(['primary_ip'])
|
||||
->addIndex(['mac_address'], ['unique' => true])
|
||||
->addIndex(['type'])
|
||||
->addIndex(['status'])
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -114,8 +114,11 @@ $containerBuilder->addDefinitions([
|
|||
\Domovoy\Controllers\SetupController::class => function ($c) {
|
||||
return new \Domovoy\Controllers\SetupController($c->get(\Domovoy\Services\AuthService::class));
|
||||
},
|
||||
\Domovoy\Controllers\DashboardController::class => function () {
|
||||
return new \Domovoy\Controllers\DashboardController();
|
||||
\Domovoy\Controllers\DashboardController::class => function ($c) {
|
||||
return new \Domovoy\Controllers\DashboardController(
|
||||
$c->get(\Domovoy\Repositories\DeviceRepository::class),
|
||||
$c->get(\Domovoy\Repositories\ScanJobRepository::class)
|
||||
);
|
||||
},
|
||||
\Domovoy\Controllers\DiscoveryController::class => function ($c) {
|
||||
return new \Domovoy\Controllers\DiscoveryController(
|
||||
|
|
@ -129,6 +132,22 @@ $containerBuilder->addDefinitions([
|
|||
$c->get(\Domovoy\Repositories\NetworkRangeRepository::class)
|
||||
);
|
||||
},
|
||||
// Inventory
|
||||
\Domovoy\Repositories\DeviceRepository::class => function ($c) {
|
||||
return new \Domovoy\Repositories\DeviceRepository($c->get(PDO::class));
|
||||
},
|
||||
\Domovoy\Services\Inventory\DeviceService::class => function ($c) {
|
||||
return new \Domovoy\Services\Inventory\DeviceService($c->get(\Domovoy\Repositories\DeviceRepository::class));
|
||||
},
|
||||
\Domovoy\Services\Inventory\MergeSuggestionService::class => function ($c) {
|
||||
return new \Domovoy\Services\Inventory\MergeSuggestionService($c->get(\Domovoy\Repositories\DeviceRepository::class));
|
||||
},
|
||||
\Domovoy\Controllers\DeviceController::class => function ($c) {
|
||||
return new \Domovoy\Controllers\DeviceController(
|
||||
$c->get(\Domovoy\Services\Inventory\DeviceService::class),
|
||||
$c->get(\Domovoy\Repositories\DiscoveredHostRepository::class)
|
||||
);
|
||||
},
|
||||
]);
|
||||
$container = $containerBuilder->build();
|
||||
|
||||
|
|
@ -161,6 +180,16 @@ $app->group('', function (\Slim\Routing\RouteCollectorProxy $group) {
|
|||
$group->post('/discovery/ranges/create', [\Domovoy\Controllers\NetworkRangeController::class, 'create'])->setName('discovery.ranges.create');
|
||||
$group->post('/discovery/ranges/toggle', [\Domovoy\Controllers\NetworkRangeController::class, 'toggle'])->setName('discovery.ranges.toggle');
|
||||
$group->post('/discovery/ranges/delete', [\Domovoy\Controllers\NetworkRangeController::class, 'delete'])->setName('discovery.ranges.delete');
|
||||
|
||||
// Devices CRUD
|
||||
$group->get('/devices', [\Domovoy\Controllers\DeviceController::class, 'index'])->setName('devices');
|
||||
$group->get('/devices/create', [\Domovoy\Controllers\DeviceController::class, 'createForm'])->setName('devices.create');
|
||||
$group->post('/devices/create', [\Domovoy\Controllers\DeviceController::class, 'create'])->setName('devices.create.post');
|
||||
$group->get('/devices/{id}', [\Domovoy\Controllers\DeviceController::class, 'show'])->setName('devices.show');
|
||||
$group->get('/devices/{id}/edit', [\Domovoy\Controllers\DeviceController::class, 'editForm'])->setName('devices.edit');
|
||||
$group->post('/devices/{id}/update', [\Domovoy\Controllers\DeviceController::class, 'update'])->setName('devices.update');
|
||||
$group->post('/devices/{id}/delete', [\Domovoy\Controllers\DeviceController::class, 'delete'])->setName('devices.delete');
|
||||
$group->post('/devices/from-host', [\Domovoy\Controllers\DeviceController::class, 'createFromHost'])->setName('devices.from_host');
|
||||
});
|
||||
|
||||
$app->run();
|
||||
|
|
|
|||
|
|
@ -6,15 +6,7 @@
|
|||
<div class="card text-white bg-primary">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Устройства</h5>
|
||||
<p class="card-text display-6">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-success">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Сервисы</h5>
|
||||
<p class="card-text display-6">0</p>
|
||||
<p class="card-text display-6"><?= $deviceCount ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -22,6 +14,14 @@
|
|||
<div class="card text-white bg-info">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Новые находки</h5>
|
||||
<p class="card-text display-6"><?= $newDiscoveries ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-success">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Сервисы</h5>
|
||||
<p class="card-text display-6">0</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -39,33 +39,45 @@
|
|||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
Последние найденные хосты
|
||||
</div>
|
||||
<div class="card-header">Последние сканирования</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Нет данных. Запустите сканирование сети.</p>
|
||||
<?php if (empty($recentScans)): ?>
|
||||
<p class="text-muted">Нет запусков сканирования.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-sm">
|
||||
<thead><tr><th>Тип</th><th>Статус</th><th>Когда</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($recentScans as $scan): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($scan->type) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$badge = match ($scan->status) {
|
||||
'pending' => 'bg-warning', 'running' => 'bg-info',
|
||||
'done' => 'bg-success', 'failed' => 'bg-danger',
|
||||
default => 'bg-secondary',
|
||||
};
|
||||
?>
|
||||
<span class="badge <?= $badge ?>"><?= htmlspecialchars($scan->status) ?></span>
|
||||
</td>
|
||||
<td><?= $scan->createdAt->format('m-d H:i') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
Последние события
|
||||
</div>
|
||||
<div class="card-header">Быстрые действия</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Нет событий.</p>
|
||||
<a href="/discovery" class="btn btn-outline-primary mb-2">Сканирование сети</a>
|
||||
<a href="/devices/create" class="btn btn-outline-secondary mb-2">Добавить устройство</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Последний скан сети
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Сканирование ещё не запускалось.</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php $content = ob_get_clean(); ?>
|
||||
<?php require dirname(__DIR__) . '/layout.php'; ?>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
<?php ob_start(); ?>
|
||||
<h2><?= $device->id === null ? 'Добавить устройство' : 'Редактировать: ' . htmlspecialchars($device->name) ?></h2>
|
||||
|
||||
<form method="POST" action="<?= $device->id === null ? '/devices/create' : '/devices/' . $device->id . '/update' ?>" class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Название *</label>
|
||||
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($device->name) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Тип</label>
|
||||
<select name="type" class="form-select">
|
||||
<?php foreach ($types as $t): ?>
|
||||
<option value="<?= $t ?>" <?= $device->type === $t ? 'selected' : '' ?>><?= $t ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Важность</label>
|
||||
<select name="importance" class="form-select">
|
||||
<?php foreach ($importances as $imp): ?>
|
||||
<option value="<?= $imp ?>" <?= $device->importance === $imp ? 'selected' : '' ?>><?= $imp ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Primary IP</label>
|
||||
<input type="text" name="primary_ip" class="form-control" value="<?= htmlspecialchars($device->primaryIp ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">MAC адрес</label>
|
||||
<input type="text" name="mac_address" class="form-control" value="<?= htmlspecialchars($device->macAddress ?? '') ?>" placeholder="aa:bb:cc:dd:ee:ff">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Hostname</label>
|
||||
<input type="text" name="hostname" class="form-control" value="<?= htmlspecialchars($device->hostname ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Vendor</label>
|
||||
<input type="text" name="vendor" class="form-control" value="<?= htmlspecialchars($device->vendor ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">OS Name</label>
|
||||
<input type="text" name="os_name" class="form-control" value="<?= htmlspecialchars($device->osName ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">OS Version</label>
|
||||
<input type="text" name="os_version" class="form-control" value="<?= htmlspecialchars($device->osVersion ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Location</label>
|
||||
<input type="text" name="location" class="form-control" value="<?= htmlspecialchars($device->location ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Статус</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="active" <?= $device->status === 'active' ? 'selected' : '' ?>>active</option>
|
||||
<option value="inactive" <?= $device->status === 'inactive' ? 'selected' : '' ?>>inactive</option>
|
||||
<option value="maintenance" <?= $device->status === 'maintenance' ? 'selected' : '' ?>>maintenance</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Описание</label>
|
||||
<textarea name="description" class="form-control" rows="2"><?= htmlspecialchars($device->description ?? '') ?></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||||
<a href="/devices" class="btn btn-secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
<?php $content = ob_get_clean(); ?>
|
||||
<?php require dirname(__DIR__) . '/layout.php'; ?>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php ob_start(); ?>
|
||||
<h2>Устройства</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
<a href="/devices/create" class="btn btn-primary">Добавить устройство</a>
|
||||
</div>
|
||||
|
||||
<?php if (empty($devices)): ?>
|
||||
<div class="alert alert-info">Нет устройств. Добавьте первое устройство вручную или создайте из найденного хоста.</div>
|
||||
<?php else: ?>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Тип</th>
|
||||
<th>IP</th>
|
||||
<th>MAC</th>
|
||||
<th>Vendor</th>
|
||||
<th>Важность</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($devices as $device): ?>
|
||||
<tr>
|
||||
<td><a href="/devices/<?= $device->id ?>"><?= htmlspecialchars($device->name) ?></a></td>
|
||||
<td><?= htmlspecialchars($device->type) ?></td>
|
||||
<td><?= htmlspecialchars($device->primaryIp ?? '-') ?></td>
|
||||
<td><?= htmlspecialchars($device->macAddress ?? '-') ?></td>
|
||||
<td><?= htmlspecialchars($device->vendor ?? '-') ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$badge = match ($device->importance) {
|
||||
'critical' => 'bg-danger',
|
||||
'high' => 'bg-warning',
|
||||
'low' => 'bg-secondary',
|
||||
default => 'bg-light text-dark',
|
||||
};
|
||||
?>
|
||||
<span class="badge <?= $badge ?>"><?= htmlspecialchars($device->importance) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?= $device->status === 'active' ? 'bg-success' : 'bg-secondary' ?>">
|
||||
<?= htmlspecialchars($device->status) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/devices/<?= $device->id ?>/edit" class="btn btn-sm btn-outline-primary">Изменить</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
<?php $content = ob_get_clean(); ?>
|
||||
<?php require dirname(__DIR__) . '/layout.php'; ?>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php ob_start(); ?>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2><?= htmlspecialchars($device->name) ?></h2>
|
||||
<div>
|
||||
<a href="/devices/<?= $device->id ?>/edit" class="btn btn-sm btn-outline-primary">Изменить</a>
|
||||
<form method="POST" action="/devices/<?= $device->id ?>/delete" class="d-inline" onsubmit="return confirm('Удалить устройство?')">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<table class="table table-borderless">
|
||||
<tr><th>Тип</th><td><?= htmlspecialchars($device->type) ?></td></tr>
|
||||
<tr><th>IP</th><td><?= htmlspecialchars($device->primaryIp ?? '-') ?></td></tr>
|
||||
<tr><th>MAC</th><td><?= htmlspecialchars($device->macAddress ?? '-') ?></td></tr>
|
||||
<tr><th>Hostname</th><td><?= htmlspecialchars($device->hostname ?? '-') ?></td></tr>
|
||||
<tr><th>Vendor</th><td><?= htmlspecialchars($device->vendor ?? '-') ?></td></tr>
|
||||
<tr><th>OS</th><td><?= htmlspecialchars(($device->osName ?? '') . ' ' . ($device->osVersion ?? '')) ?></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<table class="table table-borderless">
|
||||
<tr><th>Важность</th><td><?= htmlspecialchars($device->importance) ?></td></tr>
|
||||
<tr><th>Статус</th><td><?= htmlspecialchars($device->status) ?></td></tr>
|
||||
<tr><th>Location</th><td><?= htmlspecialchars($device->location ?? '-') ?></td></tr>
|
||||
<tr><th>Описание</th><td><?= htmlspecialchars($device->description ?? '-') ?></td></tr>
|
||||
<tr><th>Создано</th><td><?= $device->createdAt->format('Y-m-d H:i') ?></td></tr>
|
||||
<tr><th>Обновлено</th><td><?= $device->updatedAt->format('Y-m-d H:i') ?></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php $content = ob_get_clean(); ?>
|
||||
<?php require dirname(__DIR__) . '/layout.php'; ?>
|
||||
|
|
@ -144,6 +144,11 @@
|
|||
<td><?= empty($host->openPorts) ? '-' : implode(', ', $host->openPorts) ?></td>
|
||||
<td><?= $host->confidence ?>%</td>
|
||||
<td>
|
||||
<form method="POST" action="/devices/from-host" class="d-inline">
|
||||
<input type="hidden" name="host_id" value="<?= $host->id ?>">
|
||||
<input type="hidden" name="name" value="<?= htmlspecialchars($host->hostname ?: $host->ipAddress) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">Создать устройство</button>
|
||||
</form>
|
||||
<form method="POST" action="/discovery/hosts/ignore" class="d-inline">
|
||||
<input type="hidden" name="host_id" value="<?= $host->id ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">Игнорировать</button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue