refs #2693. Updated backend
testing/ogcore-api/pipeline/head Build started... Details

develop
Manuel Aranda Rosales 2025-09-23 08:35:12 +02:00
parent 3fdb40e8ae
commit 70dff6a7a5
13 changed files with 457 additions and 70 deletions

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250917113636 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE hardware_type ADD description VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE hardware_type DROP description');
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250917114148 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE hardware_type ADD code VARCHAR(10) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE hardware_type DROP code');
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250918065132 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE client DROP FOREIGN KEY FK_C7440455CFA495C1');
$this->addSql('DROP INDEX IDX_C7440455CFA495C1 ON client');
$this->addSql('ALTER TABLE client DROP hardware_profile_id');
$this->addSql('ALTER TABLE hardware_profile DROP FOREIGN KEY FK_2D9A2460FB84408A');
$this->addSql('DROP INDEX IDX_2D9A2460FB84408A ON hardware_profile');
$this->addSql('ALTER TABLE hardware_profile CHANGE organizational_unit_id client_id INT NOT NULL');
$this->addSql('ALTER TABLE hardware_profile ADD CONSTRAINT FK_2D9A246019EB6921 FOREIGN KEY (client_id) REFERENCES client (id)');
$this->addSql('CREATE INDEX IDX_2D9A246019EB6921 ON hardware_profile (client_id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE hardware_profile DROP FOREIGN KEY FK_2D9A246019EB6921');
$this->addSql('DROP INDEX IDX_2D9A246019EB6921 ON hardware_profile');
$this->addSql('ALTER TABLE hardware_profile CHANGE client_id organizational_unit_id INT NOT NULL');
$this->addSql('ALTER TABLE hardware_profile ADD CONSTRAINT FK_2D9A2460FB84408A FOREIGN KEY (organizational_unit_id) REFERENCES organizational_unit (id)');
$this->addSql('CREATE INDEX IDX_2D9A2460FB84408A ON hardware_profile (organizational_unit_id)');
$this->addSql('ALTER TABLE client ADD hardware_profile_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455CFA495C1 FOREIGN KEY (hardware_profile_id) REFERENCES hardware_profile (id)');
$this->addSql('CREATE INDEX IDX_C7440455CFA495C1 ON client (hardware_profile_id)');
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Command;
use App\Entity\HardwareType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:load-hardware-types',
description: 'Carga los tipos de hardware iniciales en la base de datos'
)]
class LoadHardwareTypesCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $entityManager
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$hardwareTypes = [
['code' => 'boo', 'name' => 'BIOS', 'description' => 'Sistema básico de entrada/salida'],
['code' => 'mod', 'name' => 'Motherboard', 'description' => 'Placa base del sistema'],
['code' => 'boa', 'name' => 'Board', 'description' => 'Placa base o tarjeta madre'],
['code' => 'cpu', 'name' => 'CPU', 'description' => 'Procesador central'],
['code' => 'mem', 'name' => 'Memory', 'description' => 'Memoria RAM'],
['code' => 'ide', 'name' => 'IDE Controller', 'description' => 'Controlador IDE'],
['code' => 'vga', 'name' => 'Graphics Card', 'description' => 'Tarjeta gráfica'],
['code' => 'dis', 'name' => 'Disk', 'description' => 'Disco de almacenamiento'],
['code' => 'usb', 'name' => 'USB Controller', 'description' => 'Controlador USB'],
['code' => 'cdr', 'name' => 'CD/DVD', 'description' => 'Unidad de CD/DVD'],
['code' => 'net', 'name' => 'Network Card', 'description' => 'Tarjeta de red'],
];
$repository = $this->entityManager->getRepository(HardwareType::class);
$created = 0;
$updated = 0;
foreach ($hardwareTypes as $typeData) {
// Buscar por nombre primero
$hardwareType = $repository->findOneBy(['name' => $typeData['name']]);
if (!$hardwareType) {
$hardwareType = new HardwareType();
$hardwareType->setName($typeData['name']);
$created++;
$io->writeln(sprintf('Creando tipo: %s (%s)', $typeData['name'], $typeData['code']));
} else {
$updated++;
$io->writeln(sprintf('Actualizando tipo: %s (%s)', $typeData['name'], $typeData['code']));
}
$hardwareType->setDescription($typeData['description']);
$hardwareType->setCode($typeData['code']);
$this->entityManager->persist($hardwareType);
}
$this->entityManager->flush();
$io->success(sprintf(
'Tipos de hardware cargados exitosamente. Creados: %d, Actualizados: %d',
$created,
$updated
));
return Command::SUCCESS;
}
}

View File

@ -3,6 +3,9 @@
namespace App\Controller\OgAgent;
use App\Entity\Client;
use App\Entity\Hardware;
use App\Entity\HardwareProfile;
use App\Entity\HardwareType;
use App\Model\CommandTypes;
use App\Model\TraceStatus;
use Symfony\Component\HttpFoundation\JsonResponse;
@ -42,16 +45,221 @@ class HardwareInventoryAction extends AbstractOgAgentController
throw new BadRequestHttpException('Error performing hardware inventory: '.$response['error']);
}
$this->logger->info('Login client', ['client' => $client->getId()]);
$responseData = $response;
if (isset($responseData['res']) && $responseData['res'] === 2) {
throw new BadRequestHttpException('Error en el inventario de hardware: operación fallida');
}
$jobId = $response['job_id'];
$this->logger->info('Hardware inventory for client', ['client' => $client->getId()]);
$inputData = [
'client' => $client->getIp(),
];
$this->createService->__invoke($client, CommandTypes::HARDWARE_INVENTORY, TraceStatus::IN_PROGRESS, $jobId, $inputData);
$this->createService->__invoke($client, CommandTypes::HARDWARE_INVENTORY, TraceStatus::SUCCESS, null, $inputData);
return new JsonResponse(data: [], status: Response::HTTP_OK);
if (isset($responseData['res']) && $responseData['res'] === 1) {
$this->processHardwareInventory($client, $responseData);
return new JsonResponse(data: $responseData, status: Response::HTTP_OK);
}
return new JsonResponse(data: $response, status: Response::HTTP_OK);
}
private function processHardwareInventory(Client $client, array $responseData): void
{
if (!isset($responseData['hrd'])) {
$this->logger->warning('No hay datos de hardware en la respuesta', ['client' => $client->getId()]);
return;
}
$this->logger->info('Iniciando procesamiento de inventario de hardware', ['client' => $client->getId()]);
$hardwareData = base64_decode($responseData['hrd']);
if ($hardwareData === false) {
$this->logger->error('Error decodificando datos de hardware base64', ['client' => $client->getId()]);
return;
}
$this->logger->debug('Datos de hardware decodificados', [
'client' => $client->getId(),
'data_length' => strlen($hardwareData)
]);
$hardwareLines = explode("\n", $hardwareData);
$this->logger->info('Líneas de hardware encontradas', [
'client' => $client->getId(),
'total_lines' => count($hardwareLines)
]);
$hardwareRepository = $this->entityManager->getRepository(Hardware::class);
$hardwareTypeRepository = $this->entityManager->getRepository(HardwareType::class);
$hardwareItems = [];
$processedLines = 0;
$createdHardware = 0;
foreach ($hardwareLines as $line) {
$line = trim($line);
if (empty($line)) {
continue;
}
$parts = explode('=', $line, 2);
if (count($parts) !== 2) {
$this->logger->debug('Línea de hardware ignorada (formato incorrecto)', [
'client' => $client->getId(),
'line' => $line
]);
continue;
}
$typeCode = trim($parts[0]);
$description = trim($parts[1]);
if (empty($typeCode) || empty($description)) {
$this->logger->debug('Línea de hardware ignorada (datos vacíos)', [
'client' => $client->getId(),
'line' => $line
]);
continue;
}
$hardwareType = $this->getOrCreateHardwareType($typeCode, $hardwareTypeRepository);
if ($hardwareType->getId() === null) {
$this->logger->debug('Nuevo tipo de hardware creado', [
'client' => $client->getId(),
'type_name' => $hardwareType->getName(),
'type_code' => $typeCode
]);
}
$hardware = $hardwareRepository->findOneBy([
'name' => $description,
'type' => $hardwareType
]);
if (!$hardware) {
$hardware = new Hardware();
$hardware->setName($description);
$hardware->setDescription($description);
$hardware->setType($hardwareType);
$this->entityManager->persist($hardware);
$createdHardware++;
$this->logger->debug('Nuevo hardware creado', [
'client' => $client->getId(),
'hardware_name' => $description,
'hardware_type' => $hardwareType->getName()
]);
}
$hardwareItems[] = $hardware;
$processedLines++;
}
$this->logger->info('Procesamiento de líneas de hardware completado', [
'client' => $client->getId()
]);
$hardwareProfile = $client->getHardwareProfile();
$isNewProfile = false;
if (!$hardwareProfile) {
$hardwareProfile = new HardwareProfile();
$hardwareProfile->setDescription('Perfil de hardware generado automáticamente para ' . $client->getIp());
$hardwareProfile->setClient($client);
$this->entityManager->persist($hardwareProfile);
$isNewProfile = true;
$this->logger->info('Nuevo perfil de hardware creado', [
'client' => $client->getId(),
'client_ip' => $client->getIp()
]);
$client->setHardwareProfile($hardwareProfile);
} else {
$this->logger->info('Actualizando perfil de hardware existente', [
'client' => $client->getId(),
'profile_id' => $hardwareProfile->getId()
]);
}
foreach ($hardwareItems as $hardware) {
$hardwareProfile->addHardwareCollection($hardware);
}
$this->logger->debug('Nuevo hardware añadido al perfil', [
'client' => $client->getId(),
'added_hardware_count' => count($hardwareItems)
]);
$client->setHardwareProfile($hardwareProfile);
$this->entityManager->persist($client);
$this->entityManager->flush();
$this->logger->info('Inventario de hardware guardado exitosamente', [
'client' => $client->getId(),
'profile_id' => $hardwareProfile->getId(),
'is_new_profile' => $isNewProfile,
'total_hardware_items' => count($hardwareItems)
]);
}
private function getOrCreateHardwareType(string $typeCode, $hardwareTypeRepository): HardwareType
{
$hardwareTypeMapping = [
'boo' => 'BIOS',
'mod' => 'Motherboard',
'boa' => 'Board',
'cpu' => 'CPU',
'mem' => 'Memory',
'ide' => 'IDE Controller',
'vga' => 'Graphics Card',
'dis' => 'Disk',
'usb' => 'USB Controller',
'cdr' => 'CD/DVD',
'net' => 'Network Card'
];
$hardwareType = $hardwareTypeRepository->findOneBy(['code' => $typeCode]);
if (!$hardwareType) {
$typeName = $hardwareTypeMapping[$typeCode] ?? strtoupper($typeCode);
$hardwareType = $hardwareTypeRepository->findOneBy(['name' => $typeName]);
}
if (!$hardwareType) {
$typeName = $hardwareTypeMapping[$typeCode] ?? strtoupper($typeCode);
$hardwareType = new HardwareType();
$hardwareType->setName($typeName);
$hardwareType->setCode($typeCode);
if (isset($hardwareTypeMapping[$typeCode])) {
$descriptions = [
'boo' => 'Sistema básico de entrada/salida',
'mod' => 'Placa base del sistema',
'boa' => 'Placa base o tarjeta madre',
'cpu' => 'Procesador central',
'mem' => 'Memoria RAM',
'ide' => 'Controlador IDE',
'vga' => 'Tarjeta gráfica',
'dis' => 'Disco de almacenamiento',
'usb' => 'Controlador USB',
'cdr' => 'Unidad de CD/DVD',
'net' => 'Tarjeta de red'
];
$hardwareType->setDescription($descriptions[$typeCode] ?? null);
}
$this->entityManager->persist($hardwareType);
}
return $hardwareType;
}
}

View File

@ -3,9 +3,9 @@
namespace App\Dto\Input;
use ApiPlatform\Metadata\ApiProperty;
use App\Dto\Output\OrganizationalUnitOutput;
use App\Entity\HardwareProfile;
use Symfony\Component\Serializer\Annotation\Groups;
use App\Dto\Output\ClientOutput;
final class HardwareProfileInput
{
@ -18,8 +18,8 @@ final class HardwareProfileInput
public ?string $comments = null;
#[Groups(['hardware-profile:write'])]
#[ApiProperty(description: 'The organizational unit of the hardware profile')]
public ?OrganizationalUnitOutput $organizationalUnit = null;
#[ApiProperty(description: 'The client of the hardware profile', readableLink: true)]
public ?ClientOutput $client = null;
public function __construct(?HardwareProfile $hardwareProfile = null)
{
@ -29,9 +29,7 @@ final class HardwareProfileInput
$this->description = $hardwareProfile->getDescription();
$this->comments = $hardwareProfile->getComments();
if($hardwareProfile->getOrganizationalUnit()) {
$this->organizationalUnit = new OrganizationalUnitOutput($hardwareProfile->getOrganizationalUnit());
}
$this->client = new ClientOutput($hardwareProfile->getClient());
}
public function createOrUpdateEntity(?HardwareProfile $hardwareProfile = null): HardwareProfile
@ -42,9 +40,7 @@ final class HardwareProfileInput
$hardwareProfile->setDescription($this->description);
$hardwareProfile->setComments($this->comments);
if ($this->organizationalUnit) {
$hardwareProfile->setOrganizationalUnit($this->organizationalUnit->getEntity());
}
$hardwareProfile->setClient($this->client->getEntity());
return $hardwareProfile;
}

View File

@ -13,16 +13,16 @@ final class ClientOutput extends AbstractOutput
{
CONST string TYPE = 'client';
#[Groups(['client:read', 'organizational-unit:read', 'pxe-template:read', 'trace:read', 'subnet:read'])]
#[Groups(['client:read', 'organizational-unit:read', 'pxe-template:read', 'trace:read', 'subnet:read', 'hardware-profile:read'])]
public string $name;
#[Groups(['client:read', 'organizational-unit:read'])]
public string $type = self::TYPE;
#[Groups(['client:read', 'organizational-unit:read', 'pxe-template:read', 'trace:read', 'subnet:read'])]
#[Groups(['client:read', 'organizational-unit:read', 'pxe-template:read', 'trace:read', 'subnet:read', 'hardware-profile:read'])]
public ?string $ip = '';
#[Groups(['client:read', 'organizational-unit:read', 'pxe-template:read', 'trace:read', 'subnet:read'])]
#[Groups(['client:read', 'organizational-unit:read', 'pxe-template:read', 'trace:read', 'subnet:read', 'hardware-profile:read'])]
public ?string $mac = '';
#[Groups(['client:read'])]
@ -47,10 +47,6 @@ final class ClientOutput extends AbstractOutput
#[Groups(['client:read'])]
public ?MenuOutput $menu = null;
#[Groups(['client:read'])]
#[ApiProperty(readableLink: true )]
public ?HardwareProfileOutput $hardwareProfile = null;
#[Groups(['client:read'])]
#[ApiProperty(readableLink: true )]
public ?ImageRepositoryOutput $repository = null;
@ -122,8 +118,6 @@ final class ClientOutput extends AbstractOutput
$this->pxeTemplate = $template ? new PxeTemplateOutput($template) : null;
$this->hardwareProfile = $client->getHardwareProfile() ? new HardwareProfileOutput($client->getHardwareProfile()) : null;
if ($client->getSubnet()){
$this->subnet = $client->getSubnet()?->getIpAddress().'/'
. $this->convertMaskToCIDR($client->getSubnet() ? $client->getSubnet()->getNetmask() : $client->getOrganizationalUnit()->getNetworkSettings()->getNetmask());

View File

@ -11,14 +11,14 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'HardwareProfile')]
final class HardwareProfileOutput extends AbstractOutput
{
#[Groups(['hardware-profile:read', 'client:read', 'organizational-unit:read'])]
#[Groups(['hardware-profile:read', 'client:read'])]
public ?string $description = '';
#[Groups(['hardware-profile:read'])]
public ?string $comments = '';
#[Groups(['hardware-profile:read'])]
public ?OrganizationalUnitOutput $organizationalUnit = null;
public ?ClientOutput $client = null;
#[Groups(['hardware-profile:read'])]
public ?array $hardwareCollection = [];
@ -35,8 +35,8 @@ final class HardwareProfileOutput extends AbstractOutput
$this->description = $hardwareProfile->getDescription();
$this->comments = $hardwareProfile->getComments();
if($hardwareProfile->getOrganizationalUnit()) {
$this->organizationalUnit = new OrganizationalUnitOutput($hardwareProfile->getOrganizationalUnit());
if($hardwareProfile->getClient()) {
$this->client = new ClientOutput($hardwareProfile->getClient());
}
$this->hardwareCollection = $hardwareProfile->getHardwareCollection()->map(

View File

@ -12,10 +12,22 @@ final class HardwareTypeOutput extends AbstractOutput
#[Groups(['hardware-type:read', 'hardware:read'])]
public string $name;
#[Groups(['hardware-type:read', 'hardware:read'])]
public ?string $description = null;
#[Groups(['hardware-type:read', 'hardware:read'])]
public ?string $code = null;
#[Groups(['hardware-type:read', 'hardware:read'])]
public \DateTime $createdAt;
public function __construct(HardwareType $hardwareType)
{
parent::__construct($hardwareType);
$this->name = $hardwareType->getName();
$this->description = $hardwareType->getDescription();
$this->code = $hardwareType->getCode();
$this->createdAt = $hardwareType->getCreatedAt();
}
}

View File

@ -54,7 +54,7 @@ class Client extends AbstractEntity
#[ORM\JoinColumn( onDelete: 'SET NULL')]
private ?Menu $menu = null;
#[ORM\ManyToOne]
#[ORM\OneToOne(mappedBy: 'client', cascade: ['persist', 'remove'])]
private ?HardwareProfile $hardwareProfile = null;
#[ORM\Column(nullable: true)]

View File

@ -16,9 +16,9 @@ class HardwareProfile extends AbstractEntity
#[ORM\Column(length: 255, nullable: true)]
private ?string $comments = null;
#[ORM\ManyToOne(targetEntity: OrganizationalUnit::class)]
#[ORM\ManyToOne(targetEntity: Client::class, inversedBy: 'hardwareProfile')]
#[ORM\JoinColumn(nullable: false)]
private ?OrganizationalUnit $organizationalUnit = null;
private ?Client $client = null;
/**
* @var Collection<int, Hardware>
@ -26,18 +26,11 @@ class HardwareProfile extends AbstractEntity
#[ORM\ManyToMany(targetEntity: Hardware::class, inversedBy: 'hardwareProfiles')]
private Collection $hardwareCollection;
/**
* @var Collection<int, Client>
*/
#[ORM\OneToMany(mappedBy: 'hardwareProfile', targetEntity: Client::class)]
private Collection $clients;
public function __construct()
{
parent::__construct();
$this->hardwareCollection = new ArrayCollection();
$this->clients = new ArrayCollection();
}
public function getId(): ?int
@ -69,14 +62,14 @@ class HardwareProfile extends AbstractEntity
return $this;
}
public function getOrganizationalUnit(): ?OrganizationalUnit
public function getClient(): ?Client
{
return $this->organizationalUnit;
return $this->client;
}
public function setOrganizationalUnit(?OrganizationalUnit $organizationalUnit): static
public function setClient(?Client $client): static
{
$this->organizationalUnit = $organizationalUnit;
$this->client = $client;
return $this;
}
@ -104,34 +97,4 @@ class HardwareProfile extends AbstractEntity
return $this;
}
/**
* @return Collection<int, Client>
*/
public function getClients(): Collection
{
return $this->clients;
}
public function addClient(Client $client): static
{
if (!$this->clients->contains($client)) {
$this->clients->add($client);
$client->setHardwareProfile($this);
}
return $this;
}
public function removeClient(Client $client): static
{
if ($this->clients->removeElement($client)) {
// set the owning side to null (unless already changed)
if ($client->getHardwareProfile() === $this) {
$client->setHardwareProfile(null);
}
}
return $this;
}
}

View File

@ -9,4 +9,34 @@ use Doctrine\ORM\Mapping as ORM;
class HardwareType extends AbstractEntity
{
use NameableTrait;
#[ORM\Column(length: 255, nullable: true)]
private ?string $description = null;
#[ORM\Column(length: 10, nullable: true)]
private ?string $code = null;
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): static
{
$this->description = $description;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(?string $code): static
{
$this->code = $code;
return $this;
}
}