diff --git a/CHANGELOG.md b/CHANGELOG.md index cc21655..327bc49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,19 @@ # Changelog +## [0.11.0] - 2025-04-11 +### Added +- Se ha añadido funcionalidad para renombrar imagenes en ogRepository. Nuevo sistema de versionado. +- Se ha añadido la integracion con el ogAgent para poder ejecutar scripts. +- Se ha añadido el poder añadir descripcion a una imagen. +- Se han añadido 2 nuevos campos en la gestion de los repositorios: usuario y puerto ssh. +- Se ha añadido funcionalidad para poder gestionar el estado de un equipo de manera automatica. En caso de no haber conexion con el cliente, la web sera notificada en un tiempo maximo de 5 min. + +### Improved +- Se han modificado los logs para que puedan "salir" por syslog ademas de por fichero. + +### Fixed +- Se ha corregido el bug que hacia que cuando habia demasiados clientes, no se mostraran en pantalla debido a un error de memoria. +--- + ## [0.10.1] - 2025-03-25 ### Improved - Se ha modificado el script de creación de usuarios, añadiendole la opcion del tipo de visionalizacion por defecto de la vista "grupos". diff --git a/config/api_platform/Client.yaml b/config/api_platform/Client.yaml index a4c48f5..ad274d0 100644 --- a/config/api_platform/Client.yaml +++ b/config/api_platform/Client.yaml @@ -50,6 +50,13 @@ resources: uriTemplate: /clients/server/{uuid}/get-pxe controller: App\Controller\OgBoot\PxeBootFile\GetAction + login_client: + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\MultipleClientsInput + uriTemplate: /clients/server/login-client + controller: App\Controller\OgAgent\LoginAction + reboot_client: class: ApiPlatform\Metadata\Post method: POST diff --git a/config/api_platform/Command.yaml b/config/api_platform/Command.yaml index 425259b..c89428b 100644 --- a/config/api_platform/Command.yaml +++ b/config/api_platform/Command.yaml @@ -7,6 +7,8 @@ resources: groups: ['default', 'command:read'] denormalizationContext: groups: ['command:write'] + order: + id: 'DESC' operations: ApiPlatform\Metadata\GetCollection: provider: App\State\Provider\CommandProvider @@ -28,9 +30,8 @@ resources: class: ApiPlatform\Metadata\Post method: POST input: App\Dto\Input\CommandExecuteInput - uriTemplate: /commands/{uuid}/execute - controller: App\Controller\CommandExecuteAction - + uriTemplate: /commands/run-script + controller: App\Controller\OgAgent\RunScriptAction properties: App\Entity\Command: id: diff --git a/config/api_platform/ImageImageRepository.yaml b/config/api_platform/ImageImageRepository.yaml index 279d72a..b2d3d73 100644 --- a/config/api_platform/ImageImageRepository.yaml +++ b/config/api_platform/ImageImageRepository.yaml @@ -64,6 +64,14 @@ resources: uriTemplate: /image-image-repositories/{uuid}/convert-image-to-virtual controller: App\Controller\OgRepository\Image\ConvertImageToVirtualAction + rename_image_ogrepository: + shortName: OgRepository Server + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\RenameImageInput + uriTemplate: /image-image-repositories/{uuid}/rename-image + controller: App\Controller\OgRepository\Image\RenameAction + trash_delete_image_ogrepository: shortName: OgRepository Server description: Delete Image in OgRepository diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index d0608d0..59cb0fb 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -15,6 +15,12 @@ when@dev: type: console process_psr_3_messages: false channels: ["!event", "!doctrine", "!console"] + syslog: + type: syslog + ident: "ogcore" + level: info + formatter: App\Formatter\CustomLineFormatter + channels: ["!event"] when@test: monolog: @@ -42,6 +48,12 @@ when@prod: type: console process_psr_3_messages: false channels: ["!event", "!doctrine"] + syslog: + type: syslog + ident: "ogcore" + level: info + formatter: App\Formatter\CustomLineFormatter + channels: ["!event"] deprecation: type: stream channels: [deprecation] diff --git a/config/services/api_platform.yaml b/config/services/api_platform.yaml index eabd61c..6f91d56 100644 --- a/config/services/api_platform.yaml +++ b/config/services/api_platform.yaml @@ -42,7 +42,7 @@ services: api_platform.filter.command.boolean: parent: 'api_platform.doctrine.orm.boolean_filter' - arguments: [ { 'enabled': ~ } ] + arguments: [ { 'enabled': ~, 'readOnly': ~ } ] tags: [ 'api_platform.filter' ] api_platform.filter.hardware.order: diff --git a/migrations/Version20250325075647.php b/migrations/Version20250325075647.php new file mode 100644 index 0000000..6048e92 --- /dev/null +++ b/migrations/Version20250325075647.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE client ADD firmware_type 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 client DROP firmware_type'); + } +} diff --git a/migrations/Version20250326061450.php b/migrations/Version20250326061450.php new file mode 100644 index 0000000..2eebbef --- /dev/null +++ b/migrations/Version20250326061450.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE command ADD parameters TINYINT(1) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE command DROP parameters'); + } +} diff --git a/migrations/Version20250331144522.php b/migrations/Version20250331144522.php new file mode 100644 index 0000000..b5b2f71 --- /dev/null +++ b/migrations/Version20250331144522.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE image_image_repository ADD datasize 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 image_image_repository DROP datasize'); + } +} diff --git a/migrations/Version20250402060324.php b/migrations/Version20250402060324.php new file mode 100644 index 0000000..f3b99b7 --- /dev/null +++ b/migrations/Version20250402060324.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE image ADD version INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE image DROP version'); + } +} diff --git a/migrations/Version20250402081107.php b/migrations/Version20250402081107.php new file mode 100644 index 0000000..e03a6f1 --- /dev/null +++ b/migrations/Version20250402081107.php @@ -0,0 +1,31 @@ +addSql('DROP INDEX UNIQ_IDENTIFIER_IMAGE_REPOSITORY ON image_image_repository'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_IMAGE_REPOSITORY ON image_image_repository (image_id, repository_id)'); + } +} diff --git a/migrations/Version20250402094550.php b/migrations/Version20250402094550.php new file mode 100644 index 0000000..fb6d618 --- /dev/null +++ b/migrations/Version20250402094550.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE image_image_repository ADD version INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE image_image_repository DROP version'); + } +} diff --git a/migrations/Version20250407063100.php b/migrations/Version20250407063100.php new file mode 100644 index 0000000..2181e1c --- /dev/null +++ b/migrations/Version20250407063100.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE image_image_repository 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 image_image_repository DROP description'); + } +} diff --git a/migrations/Version20250407154425.php b/migrations/Version20250407154425.php new file mode 100644 index 0000000..c472a1d --- /dev/null +++ b/migrations/Version20250407154425.php @@ -0,0 +1,33 @@ +addSql('CREATE INDEX IDX_STATUS ON client (status)'); + $this->addSql('CREATE INDEX IDX_UPDATED_AT ON client (updated_at)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX IDX_STATUS ON client'); + $this->addSql('DROP INDEX IDX_UPDATED_AT ON client'); + } +} diff --git a/migrations/Version20250407154620.php b/migrations/Version20250407154620.php new file mode 100644 index 0000000..5a2b12d --- /dev/null +++ b/migrations/Version20250407154620.php @@ -0,0 +1,31 @@ +addSql('CREATE INDEX IDX_STATUS_UPDATED_AT ON client (status, updated_at)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX IDX_STATUS_UPDATED_AT ON client'); + } +} diff --git a/migrations/Version20250408140101.php b/migrations/Version20250408140101.php new file mode 100644 index 0000000..f84d86c --- /dev/null +++ b/migrations/Version20250408140101.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE image_repository ADD user VARCHAR(255) DEFAULT NULL, ADD ssh_port 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 image_repository DROP user, DROP ssh_port'); + } +} diff --git a/migrations/Version20250409093554.php b/migrations/Version20250409093554.php new file mode 100644 index 0000000..4993a74 --- /dev/null +++ b/migrations/Version20250409093554.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE image_image_repository ADD name VARCHAR(255) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE image_image_repository DROP name'); + } +} diff --git a/src/Command/CheckClientAvailability.php b/src/Command/CheckClientAvailability.php new file mode 100644 index 0000000..bf7419e --- /dev/null +++ b/src/Command/CheckClientAvailability.php @@ -0,0 +1,98 @@ +modify(' - '.self::THRESHOLD_MINUTES . ' minutes'); + + $startQueryTime = microtime(true); + + $query = $this->entityManager->createQuery( + 'UPDATE App\Entity\Client c + SET c.status = :status + WHERE c.status = :currentStatus AND c.updatedAt < :threshold' + ); + $query->setParameter('status', ClientStatus::DISCONNECTED); + $query->setParameter('currentStatus', ClientStatus::OG_LIVE); + $query->setParameter('threshold', $threshold); + $updatedCount = $query->execute(); + + $queryTime = microtime(true) - $startQueryTime; + + $startMercureTime = microtime(true); + + $clients = $this->entityManager->createQueryBuilder() + ->select('c') + ->from(Client::class, 'c') + ->where('c.status = :status') + ->andWhere('c.updatedAt < :threshold') + ->setParameter('status', ClientStatus::DISCONNECTED) + ->setParameter('threshold', $threshold) + ->getQuery() + ->getResult(); + + $this->dispatchMercureEvent($clients); + + $mercureTime = microtime(true) - $startMercureTime; + + $io->success("Updated $updatedCount clients to DISCONNECTED status."); + $io->note("Query time: " . round($queryTime, 3) . "s"); + $io->note("Mercure dispatch time: " . round($mercureTime, 3) . "s"); + + return Command::SUCCESS; + } + + private function dispatchMercureEvent(array $clients, int $chunkSize = 10000): void + { + $chunks = array_chunk($clients, $chunkSize); + + foreach ($chunks as $chunk) { + $data = []; + + foreach ($chunk as $client) { + $data[] = [ + '@id' => '/clients/' . $client->getUuid(), + 'status' => $client->getStatus(), + ]; + } + + $update = new Update( + 'clients', + json_encode($data) + ); + + $this->hub->publish($update); + } + } + +} diff --git a/src/Command/TestCommand.php b/src/Command/TestCommand.php deleted file mode 100644 index fa8306b..0000000 --- a/src/Command/TestCommand.php +++ /dev/null @@ -1,44 +0,0 @@ -entityManager->getRepository(Trace::class)->find(7236); - - $trace->setStatus(TraceStatus::SUCCESS); - $trace->setProgress(1000); - - $this->entityManager->persist($trace); - $this->entityManager->flush(); - - return Command::SUCCESS; - } -} diff --git a/src/Controller/DeployImageAction.php b/src/Controller/DeployImageAction.php index ae59bc3..4912d29 100644 --- a/src/Controller/DeployImageAction.php +++ b/src/Controller/DeployImageAction.php @@ -87,7 +87,7 @@ class DeployImageAction extends AbstractController ]; try { - $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity(), $this->httpClient); + $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity()); } catch (\Exception $e) { continue; } diff --git a/src/Controller/OgAgent/CreateImageAction.php b/src/Controller/OgAgent/CreateImageAction.php index 55c87a7..eb89c5c 100644 --- a/src/Controller/OgAgent/CreateImageAction.php +++ b/src/Controller/OgAgent/CreateImageAction.php @@ -8,6 +8,7 @@ use App\Entity\Client; use App\Entity\Command; use App\Entity\Image; use App\Entity\ImageImageRepository; +use App\Entity\ImageRepository; use App\Entity\Trace; use App\Model\ClientStatus; use App\Model\CommandTypes; @@ -56,10 +57,14 @@ class CreateImageAction extends AbstractController $repository = $image->getClient()->getRepository(); + $latestImageRepo = $this->entityManager->getRepository(ImageImageRepository::class)->findLatestVersionByImageAndRepository($image, $repository); + $imageImageRepository = new ImageImageRepository(); + $imageImageRepository->setName($image->getName().'_v'.($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1)); $imageImageRepository->setImage($image); $imageImageRepository->setRepository($repository); $imageImageRepository->setStatus(ImageStatus::IN_PROGRESS); + $imageImageRepository->setVersion($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1); $this->entityManager->persist($imageImageRepository); @@ -68,7 +73,7 @@ class CreateImageAction extends AbstractController 'par' => (string) $partitionInfo['numPartition'], 'cpt' => null, 'idi' => $imageImageRepository->getUuid(), - 'nci' => $image->getName(), + 'nci' => $image->getName().'_v'.$imageImageRepository->getVersion(), 'ipr' => $repository->getIp(), 'nfn' => 'CrearImagen', 'ids' => '0' @@ -90,7 +95,7 @@ class CreateImageAction extends AbstractController try { $this->logger->info('Creating image', ['image' => $image->getId()]); - $response = $this->httpClient->request('POST', 'https://'.$image->getClient()->getIp().':8000/CloningEngine/CrearImagen', [ + $response = $this->httpClient->request('POST', 'https://'.$image->getClient()->getIp().':8000/opengnsys/CrearImagen', [ 'verify_peer' => false, 'verify_host' => false, 'headers' => [ diff --git a/src/Controller/OgAgent/DeployImageAction.php b/src/Controller/OgAgent/DeployImageAction.php index eaacd9b..6bf3c8d 100644 --- a/src/Controller/OgAgent/DeployImageAction.php +++ b/src/Controller/OgAgent/DeployImageAction.php @@ -92,7 +92,7 @@ class DeployImageAction extends AbstractController ]; try { - $response = $this->httpClient->request('POST', 'https://'.$client->getIp().':8000/CloningEngine/RestaurarImagen', [ + $response = $this->httpClient->request('POST', 'https://'.$client->getIp().':8000/opengnsys/RestaurarImagen', [ 'verify_peer' => false, 'verify_host' => false, 'headers' => [ diff --git a/src/Controller/OgAgent/LoginAction.php b/src/Controller/OgAgent/LoginAction.php new file mode 100644 index 0000000..42fd7e1 --- /dev/null +++ b/src/Controller/OgAgent/LoginAction.php @@ -0,0 +1,93 @@ +clients as $clientEntity) { + /** @var Client $client */ + $client = $clientEntity->getEntity(); + + + if (!$client->getIp()) { + throw new ValidatorException('IP is required'); + } + + if ($client->getStatus() !== ClientStatus::OG_LIVE) { + throw new ValidatorException('Client is not in OG_LIVE status'); + } + + $data = [ + 'nfn' => 'IniciarSesion', + 'dsk' => '1', + 'par' => '1', + 'ids' => '0' + ]; + + try { + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/ogAdmClient/IniciarSesion', [ + 'verify_peer' => false, + 'verify_host' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'json' => $data, + ]); + $this->logger->info('Login client', ['client' => $client->getId()]); + + } catch (TransportExceptionInterface $e) { + $this->logger->error('Login rebooting client', ['client' => $client->getId(), 'error' => $e->getMessage()]); + return new JsonResponse( + data: ['error' => $e->getMessage()], + status: Response::HTTP_INTERNAL_SERVER_ERROR + ); + } + + $jobId = json_decode($response->getContent(), true)['job_id']; + + $client->setStatus(ClientStatus::INITIALIZING); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->createService->__invoke($client, CommandTypes::REBOOT, TraceStatus::SUCCESS, $jobId, []); + } + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } +} diff --git a/src/Controller/OgAgent/PartitionAssistantAction.php b/src/Controller/OgAgent/PartitionAssistantAction.php index 7e8021d..5dfe433 100644 --- a/src/Controller/OgAgent/PartitionAssistantAction.php +++ b/src/Controller/OgAgent/PartitionAssistantAction.php @@ -48,6 +48,7 @@ class PartitionAssistantAction extends AbstractController } foreach ($input->clients as $clientInput) { + /** @var Client $client */ $client = $clientInput->getEntity(); $disks = []; @@ -72,13 +73,10 @@ class PartitionAssistantAction extends AbstractController ]; } - if ($partition->filesystem === 'CACHE') { - $disks[$diskNumber]['diskData'] = [ - 'dis' => (string) $diskNumber, - 'che' => "0", - 'tch' => (string) ($partition->size * 1024), - ]; - } + $disks[$diskNumber]['diskData'] = [ + 'dis' => (string) $diskNumber, + 'tch' => (string) ($partition->size * 1024), + ]; $disks[$diskNumber]['partitionData'][] = [ 'par' => (string) $partition->partitionNumber, @@ -104,7 +102,7 @@ class PartitionAssistantAction extends AbstractController ]; try { - $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/CloningEngine/Configurar', [ + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/opengnsys/Configurar', [ 'verify_peer' => false, 'verify_host' => false, 'headers' => [ @@ -130,5 +128,4 @@ class PartitionAssistantAction extends AbstractController return new JsonResponse(data: [], status: Response::HTTP_OK); } - } diff --git a/src/Controller/OgAgent/PowerOffAction.php b/src/Controller/OgAgent/PowerOffAction.php index 39f2e5b..8cde198 100644 --- a/src/Controller/OgAgent/PowerOffAction.php +++ b/src/Controller/OgAgent/PowerOffAction.php @@ -53,13 +53,15 @@ class PowerOffAction extends AbstractController continue; } + $endpoint = $client->getStatus() === ClientStatus::OG_LIVE ? 'opengnsys/Apagar' : 'opengnsys/poweroff'; + $data = [ 'nfn' => 'Apagar', 'ids' => '0' ]; try { - $response = $this->httpClient->request('POST', 'https://'.$client->getIp().':8000/ogAdmClient/Apagar', [ + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/'.$endpoint, [ 'verify_peer' => false, 'verify_host' => false, 'headers' => [ diff --git a/src/Controller/OgAgent/RebootAction.php b/src/Controller/OgAgent/RebootAction.php index 72d9a43..0be2163 100644 --- a/src/Controller/OgAgent/RebootAction.php +++ b/src/Controller/OgAgent/RebootAction.php @@ -41,6 +41,7 @@ class RebootAction extends AbstractController public function __invoke(MultipleClientsInput $input): JsonResponse { foreach ($input->clients as $clientEntity) { + /** @var Client $client */ $client = $clientEntity->getEntity(); @@ -48,13 +49,15 @@ class RebootAction extends AbstractController throw new ValidatorException('IP is required'); } + $endpoint = $client->getStatus() === ClientStatus::OG_LIVE ? 'opengnsys/Reiniciar' : '/opengnsys/reboot'; + $data = [ 'nfn' => 'Reiniciar', 'ids' => '0' ]; try { - $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/ogAdmClient/Reiniciar', [ + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/'.$endpoint, [ 'verify_peer' => false, 'verify_host' => false, 'headers' => [ @@ -62,6 +65,7 @@ class RebootAction extends AbstractController ], 'json' => $data, ]); + $this->logger->info('Rebooting client', ['client' => $client->getId()]); } catch (TransportExceptionInterface $e) { diff --git a/src/Controller/OgAgent/RunScriptAction.php b/src/Controller/OgAgent/RunScriptAction.php new file mode 100644 index 0000000..0282f3a --- /dev/null +++ b/src/Controller/OgAgent/RunScriptAction.php @@ -0,0 +1,81 @@ +clients as $clientEntity) { + $client = $clientEntity->getEntity(); + + if (!$client->getIp()) { + throw new ValidatorException('IP is required'); + } + + $data = [ + 'nfn' => 'EjecutarScript', + 'scp' => base64_encode($input->script), + 'ids' => '0' + ]; + + try { + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/opengnsys/EjecutarScript', [ + 'verify_peer' => false, + 'verify_host' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'json' => $data, + ]); + $this->logger->info('Rebooting client', ['client' => $client->getId()]); + + } catch (TransportExceptionInterface $e) { + $this->logger->error('Error rebooting client', ['client' => $client->getId(), 'error' => $e->getMessage()]); + return new JsonResponse( + data: ['error' => $e->getMessage()], + status: Response::HTTP_INTERNAL_SERVER_ERROR + ); + } + + $jobId = json_decode($response->getContent(), true)['job_id']; + + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $inputData = [ + 'script' => $input->script, + ]; + + $this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::SUCCESS, $jobId, $inputData); + } + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Controller/OgAgent/StatusAction.php b/src/Controller/OgAgent/StatusAction.php index 77b5699..dde6331 100644 --- a/src/Controller/OgAgent/StatusAction.php +++ b/src/Controller/OgAgent/StatusAction.php @@ -70,15 +70,19 @@ class StatusAction extends AbstractController { $this->logger->info('Checking client status', ['client' => $client->getId()]); + $params = [ + 'full-config' => false, + ]; + try { - $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/ogAdmClient/status', [ + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/opengnsys/status', [ 'verify_peer' => false, 'verify_host' => false, - 'timeout' => 30, + 'timeout' => 10, 'headers' => [ 'Content-Type' => 'application/json', ], - 'json' => [], + 'json' => $params, ]); $statusCode = $response->getStatusCode(); $client->setStatus($statusCode === Response::HTTP_OK ? ClientStatus::OG_LIVE : ClientStatus::OFF); diff --git a/src/Controller/OgAgent/Webhook/AgentSessionController.php b/src/Controller/OgAgent/Webhook/AgentSessionController.php index 67d6a6e..6723ea8 100644 --- a/src/Controller/OgAgent/Webhook/AgentSessionController.php +++ b/src/Controller/OgAgent/Webhook/AgentSessionController.php @@ -7,6 +7,7 @@ namespace App\Controller\OgAgent\Webhook; use App\Entity\Client; use App\Model\ClientStatus; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -18,7 +19,8 @@ use Symfony\Component\Routing\Annotation\Route; class AgentSessionController extends AbstractController { public function __construct( - protected readonly EntityManagerInterface $entityManager + protected readonly EntityManagerInterface $entityManager, + protected readonly LoggerInterface $logger ) { } @@ -58,6 +60,14 @@ class AgentSessionController extends AbstractController $this->entityManager->persist($client); $this->entityManager->flush(); + $this->logger->info('Client started', [ + 'mac' => $data['mac'], + 'ip' => $data['ip'], + 'ostype' => $data['ostype'], + 'osversion' => $data['osversion'], + 'agent_version' => $data['agent_version'], + ]); + return new JsonResponse([], Response::HTTP_OK); } @@ -82,6 +92,13 @@ class AgentSessionController extends AbstractController $this->entityManager->persist($client); $this->entityManager->flush(); + $this->logger->info('Client stopped', [ + 'mac' => $data['mac'], + 'ip' => $data['ip'], + 'ostype' => $data['ostype'], + 'osversion' => $data['osversion'], + ]); + return new JsonResponse([], Response::HTTP_OK); } @@ -117,6 +134,18 @@ class AgentSessionController extends AbstractController return new JsonResponse(['message' => 'Invalid status'], Response::HTTP_BAD_REQUEST); } + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->logger->info('Client logged in', [ + 'ip' => $data['ip'], + 'user' => $data['user'], + 'language' => $data['language'], + 'session' => $data['session'], + 'ostype' => $data['ostype'], + 'osversion' => $data['osversion'], + ]); + return new JsonResponse([], Response::HTTP_OK); } @@ -152,6 +181,15 @@ class AgentSessionController extends AbstractController return new JsonResponse(['message' => 'Invalid status'], Response::HTTP_BAD_REQUEST); } + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->logger->info('Client logged out', [ + 'ip' => $data['ip'], + 'user' => $data['user'], + 'ostype' => $data['ostype'], + ]); + return new JsonResponse([], Response::HTTP_OK); } } diff --git a/src/Controller/OgAgent/Webhook/ClientsController.php b/src/Controller/OgAgent/Webhook/StatusController.php similarity index 91% rename from src/Controller/OgAgent/Webhook/ClientsController.php rename to src/Controller/OgAgent/Webhook/StatusController.php index b5970e3..6871f1a 100644 --- a/src/Controller/OgAgent/Webhook/ClientsController.php +++ b/src/Controller/OgAgent/Webhook/StatusController.php @@ -3,6 +3,7 @@ namespace App\Controller\OgAgent\Webhook; use App\Controller\OgRepository\Image\CreateAuxFilesAction; +use App\Entity\Client; use App\Entity\Image; use App\Entity\ImageImageRepository; use App\Entity\OperativeSystem; @@ -32,7 +33,7 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; #[AsController] -class ClientsController extends AbstractController +class StatusController extends AbstractController { const string CREATE_IMAGE = 'RESPUESTA_CrearImagen'; const string RESTORE_IMAGE = 'RESPUESTA_RestaurarImagen'; @@ -59,20 +60,28 @@ class ClientsController extends AbstractController public function index(Request $request): JsonResponse { $data = $request->toArray(); - $requiredFields = ['job_id']; - - foreach ($requiredFields as $field) { - if (!isset($data[$field])) { - return new JsonResponse(['message' => "Missing parameter: $field"], Response::HTTP_BAD_REQUEST); - } - } $this->logger->info('Webhook data received', $data); + // Esta parte del codigo nos indica si el cliente se encuentra activo + if (isset($data['iph']) && isset($data['timestamp'])) { + $client = $this->entityManager->getRepository(Client::class)->findOneBy(['ip' => $data['iph']]); + if (!$client) { + $this->logger->error('Client not found', $data); + return new JsonResponse(['message' => 'Client not found'], Response::HTTP_NOT_FOUND); + } + + $updateAt = (new \DateTime())->setTimestamp((int)$data['timestamp']); + + $client->setUpdatedAt($updateAt); + $this->entityManager->persist($client); + $this->entityManager->flush(); + } + if (isset($data['progress'])){ $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); if ($trace){ - $trace->setProgress($data['progress'] * 1000); + $trace->setProgress($data['progress'] * 100); $this->entityManager->persist($trace); $this->entityManager->flush(); } diff --git a/src/Controller/OgBoot/PxeBootFile/PostAction.php b/src/Controller/OgBoot/PxeBootFile/PostAction.php index b2245b8..75a30c3 100644 --- a/src/Controller/OgBoot/PxeBootFile/PostAction.php +++ b/src/Controller/OgBoot/PxeBootFile/PostAction.php @@ -40,7 +40,7 @@ class PostAction extends AbstractOgBootController 'json' => [ 'template_name' => $pxeTemplate->getName(), 'mac' => strtolower($client->getMac()), - 'lang' => 'es_ES.UTF_8', + 'lang' => 'es_ES.UTF-8', 'ip' => $client->getIp(), 'server_ip' => $this->ogBootApiUrl, 'router' => $client->getOrganizationalUnit()->getNetworkSettings()->getRouter(), diff --git a/src/Controller/OgRepository/Image/BackupImageAction.php b/src/Controller/OgRepository/Image/BackupImageAction.php index 8f8662d..51e76b4 100644 --- a/src/Controller/OgRepository/Image/BackupImageAction.php +++ b/src/Controller/OgRepository/Image/BackupImageAction.php @@ -32,6 +32,7 @@ class BackupImageAction extends AbstractOgRepositoryController public function __invoke(BackupImageInput $input, ImageImageRepository $imageImageRepository): JsonResponse { $image = $imageImageRepository->getImage(); + $repository = $imageImageRepository->getRepository(); if (!$image->getName()) { throw new ValidatorException('Name is required'); @@ -41,8 +42,9 @@ class BackupImageAction extends AbstractOgRepositoryController 'json' => [ 'ID_img' => $imageImageRepository->getImageFullsum(), 'repo_ip' => $input->repoIp, + 'ssh_port' => $repository->getSshPort() ?? '22', 'remote_path' => $input->remotePath, - 'user' => 'opengnsys' + 'user' => $repository->getUser() ?? 'opengnsys', ] ]; diff --git a/src/Controller/OgRepository/Image/ConvertAction.php b/src/Controller/OgRepository/Image/ConvertAction.php index a41589a..ff3a4bd 100644 --- a/src/Controller/OgRepository/Image/ConvertAction.php +++ b/src/Controller/OgRepository/Image/ConvertAction.php @@ -53,12 +53,13 @@ class ConvertAction extends AbstractOgRepositoryController } $imageImageRepositoryEntity = new ImageImageRepository(); + $imageImageRepositoryEntity->setName($imageEntity->getName().'_v'.$imageImageRepositoryEntity->getVersion() + 1); $imageImageRepositoryEntity->setStatus(ImageStatus::PENDING); $imageImageRepositoryEntity->setImage($imageEntity); $imageImageRepositoryEntity->setRepository($repository); + $imageImageRepositoryEntity->setVersion(1); $this->entityManager->persist($imageImageRepositoryEntity); - $this->entityManager->flush(); $this->logger->info('Converting image', ['image' => $image]); @@ -74,6 +75,7 @@ class ConvertAction extends AbstractOgRepositoryController if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR ) { throw new ValidatorException('Error converting image'); } + $this->entityManager->flush(); $inputData = [ 'imageName' => $image, diff --git a/src/Controller/OgRepository/Image/CreateAuxFilesAction.php b/src/Controller/OgRepository/Image/CreateAuxFilesAction.php index d5ebd1f..aaf0bdb 100644 --- a/src/Controller/OgRepository/Image/CreateAuxFilesAction.php +++ b/src/Controller/OgRepository/Image/CreateAuxFilesAction.php @@ -39,18 +39,18 @@ class CreateAuxFilesAction extends AbstractOgRepositoryController $params = [ 'json' => [ - 'image' => $image->getName().'.img' + 'image' => $data->getName().'.img' ] ]; - $this->logger->info('Creating aux files', ['image' => $image->getName()]); + $this->logger->info('Creating aux files', ['image' => $data->getName()]); $repository = $data->getRepository(); $content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/images/torrentsum', $params); $inputData = [ - 'imageName' => $image->getName(), + 'imageName' => $data->getName(), 'imageImageRepositoryUuid' => $data->getUuid(), ]; diff --git a/src/Controller/OgRepository/Image/DeletePermanentAction.php b/src/Controller/OgRepository/Image/DeletePermanentAction.php index 7b14d90..892936a 100644 --- a/src/Controller/OgRepository/Image/DeletePermanentAction.php +++ b/src/Controller/OgRepository/Image/DeletePermanentAction.php @@ -5,6 +5,7 @@ namespace App\Controller\OgRepository\Image; use App\Controller\OgRepository\AbstractOgRepositoryController; use App\Entity\Image; use App\Entity\ImageImageRepository; +use App\Entity\ImageRepository; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; @@ -30,6 +31,7 @@ class DeletePermanentAction extends AbstractOgRepositoryController throw new ValidatorException('Fullsum is required'); } + /** @var ImageRepository $image */ $image = $data->getImage(); $this->logger->info('Deleting image', ['image' => $image->getName()]); @@ -43,6 +45,13 @@ class DeletePermanentAction extends AbstractOgRepositoryController $this->entityManager->remove($data); $this->entityManager->flush(); + $imageImageCollection = $image->getImageImageRepositories(); + + if ($imageImageCollection->isEmpty()) { + $this->entityManager->remove($image); + $this->entityManager->flush(); + } + return new JsonResponse(data: $content, status: Response::HTTP_OK); } } \ No newline at end of file diff --git a/src/Controller/OgRepository/Image/DeleteTrashAction.php b/src/Controller/OgRepository/Image/DeleteTrashAction.php index 8ff17d7..31c245e 100644 --- a/src/Controller/OgRepository/Image/DeleteTrashAction.php +++ b/src/Controller/OgRepository/Image/DeleteTrashAction.php @@ -45,6 +45,13 @@ class DeleteTrashAction extends AbstractOgRepositoryController $this->entityManager->persist($image); $this->entityManager->flush(); + $imageImageCollection = $image->getImageImageRepositories(); + + if ($imageImageCollection->isEmpty()) { + $this->entityManager->remove($image); + $this->entityManager->flush(); + } + return new JsonResponse(data: $content, status: Response::HTTP_OK); } } \ No newline at end of file diff --git a/src/Controller/OgRepository/Image/DeployImageAction.php b/src/Controller/OgRepository/Image/DeployImageAction.php index 600163f..8b8eb8c 100644 --- a/src/Controller/OgRepository/Image/DeployImageAction.php +++ b/src/Controller/OgRepository/Image/DeployImageAction.php @@ -30,7 +30,7 @@ class DeployImageAction extends AbstractOgRepositoryController * @throws ClientExceptionInterface * @throws TransportExceptionInterface */ - public function __invoke(DeployImageInput $input, ImageImageRepository $data, Client $client, HttpClientInterface $httpClient): JsonResponse + public function __invoke(DeployImageInput $input, ImageImageRepository $data, Client $client): JsonResponse { $params = [ 'json' => [ @@ -45,9 +45,9 @@ class DeployImageAction extends AbstractOgRepositoryController ]; $type = match ($input->method) { - 'udpcast', 'udpcast_direct' => DeployMethodTypes::MULTICAST_UDPCAST, + 'udpcast', 'udpcast-direct' => DeployMethodTypes::MULTICAST_UDPCAST, 'p2p' => DeployMethodTypes::TORRENT, - default => DeployMethodTypes::MULTICAST_UFTP, + default => null, }; $repository = $client->getRepository(); diff --git a/src/Controller/OgRepository/Image/ImportAction.php b/src/Controller/OgRepository/Image/ImportAction.php index cb28a67..4e1af2f 100644 --- a/src/Controller/OgRepository/Image/ImportAction.php +++ b/src/Controller/OgRepository/Image/ImportAction.php @@ -51,9 +51,11 @@ class ImportAction extends AbstractOgRepositoryController } $imageImageRepositoryEntity = new ImageImageRepository(); + $imageImageRepositoryEntity->setName($imageEntity->getName().'_v'.$imageImageRepositoryEntity->getVersion() + 1); $imageImageRepositoryEntity->setStatus(ImageStatus::AUX_FILES_PENDING); $imageImageRepositoryEntity->setImage($imageEntity); $imageImageRepositoryEntity->setRepository($repository); + $imageImageRepositoryEntity->setVersion(1); $this->entityManager->persist($imageImageRepositoryEntity); $this->entityManager->flush(); @@ -74,7 +76,7 @@ class ImportAction extends AbstractOgRepositoryController $inputData = [ 'imageName' => $image, - 'imageUuid' => $imageImageRepositoryEntity->getUuid(), + 'imageImageRepositoryUuid' => $imageImageRepositoryEntity->getUuid(), ]; $this->createService->__invoke(null, CommandTypes::CREATE_IMAGE_AUX_FILE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData); diff --git a/src/Controller/OgRepository/Image/RenameAction.php b/src/Controller/OgRepository/Image/RenameAction.php new file mode 100644 index 0000000..6e41e16 --- /dev/null +++ b/src/Controller/OgRepository/Image/RenameAction.php @@ -0,0 +1,125 @@ +getImage(); + + if ($image->isGlobal()) { + $repositories = $image->getImageImageRepositories(); + + if ($repositories->count() === 0) { + return $this->jsonError('Image is not in any repository'); + } + + if (!$this->isAvailableInAllRepositories($repositories)) { + $this->logger->info('Image is not available in all repositories', ['image' => $image->getName()]); + return $this->jsonError('Image is not available in all repositories'); + } + + $repoWithImage = $this->entityManager + ->getRepository(ImageImageRepository::class) + ->findBy(['image' => $image, 'repository' => $imageImageRepository->getRepository()]); + } else { + $repoWithImage = [$imageImageRepository]; + } + + $hasError = false; + + foreach ($repoWithImage as $repository) { + $content = $this->renameImageInRepository($repository, $input->newName); + + if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + $hasError = true; + } + + $repository->setName($input->newName); + $this->entityManager->persist($repository); + } + + $this->entityManager->flush(); + + if ($hasError) { + return new JsonResponse(['error' => 'Error renaming image'], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + return new JsonResponse([], Response::HTTP_OK); + } + + /** + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + * @throws ServerExceptionInterface + */ + private function isAvailableInAllRepositories($repositories): bool + { + foreach ($repositories as $repository) { + try { + $this->createRequest('GET', 'http://' . $repository->getRepository()->getIp() . ':8006/ogrepository/v1/status'); + } catch (TransportExceptionInterface $e) { + return false; + } + } + return true; + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + private function renameImageInRepository(ImageImageRepository $repository, string $newName): array + { + $params = [ + 'json' => [ + 'ID_img' => $repository->getImageFullsum(), + 'image_new_name' => $newName, + ] + ]; + + return $this->createRequest( + 'PUT', + 'http://' . $repository->getRepository()->getIp() . ':8006/ogrepository/v1/images/rename', + $params + ); + } + + private function jsonError(string $message): JsonResponse + { + return new JsonResponse( + ['error' => $message, 'code' => Response::HTTP_INTERNAL_SERVER_ERROR], + Response::HTTP_BAD_REQUEST + ); + } + +} \ No newline at end of file diff --git a/src/Controller/OgRepository/Image/TransferAction.php b/src/Controller/OgRepository/Image/TransferAction.php index c0513bb..b0707d4 100644 --- a/src/Controller/OgRepository/Image/TransferAction.php +++ b/src/Controller/OgRepository/Image/TransferAction.php @@ -47,7 +47,8 @@ class TransferAction extends AbstractOgRepositoryController 'json' => [ 'image' => $image->getName().'.img', 'repo_ip' => $imageImageRepository->getRepository()->getIp(), - 'user' => 'opengnsys', + 'user' => $repository->getUser(), + 'ssh_port' => $repository->getSshPort() ] ]; diff --git a/src/Controller/OgRepository/Image/TransferGlobalAction.php b/src/Controller/OgRepository/Image/TransferGlobalAction.php index c69ff48..0e4b31f 100644 --- a/src/Controller/OgRepository/Image/TransferGlobalAction.php +++ b/src/Controller/OgRepository/Image/TransferGlobalAction.php @@ -48,7 +48,8 @@ class TransferGlobalAction extends AbstractOgRepositoryController 'json' => [ 'image' => $image->getName().'.img', 'repo_ip' => $imageImageRepository->getRepository()->getIp(), - 'user' => 'opengnsys', + 'user' => $repository->getUser(), + 'ssh_port' => $repository->getSshPort() ] ]; diff --git a/src/Controller/OgRepository/SyncAction.php b/src/Controller/OgRepository/SyncAction.php index f4fd765..11e623c 100644 --- a/src/Controller/OgRepository/SyncAction.php +++ b/src/Controller/OgRepository/SyncAction.php @@ -56,13 +56,16 @@ class SyncAction extends AbstractOgRepositoryController if (!$imageImageRepositoryEntity) { $imageImageRepositoryEntity = new ImageImageRepository(); - $imageImageRepositoryEntity->setImageFullsum($image['fullsum']); - $imageImageRepositoryEntity->setStatus(ImageStatus::SUCCESS); - $imageImageRepositoryEntity->setImage($imageEntity); - $imageImageRepositoryEntity->setRepository($input); - - $this->entityManager->persist($imageImageRepositoryEntity); } + + $imageImageRepositoryEntity->setImageFullsum($image['fullsum']); + $imageImageRepositoryEntity->setDatasize($image['datasize']); + $imageImageRepositoryEntity->setStatus(ImageStatus::SUCCESS); + $imageImageRepositoryEntity->setImage($imageEntity); + $imageImageRepositoryEntity->setRepository($input); + + $this->entityManager->persist($imageImageRepositoryEntity); + } foreach ($existingImages as $existingImage) { diff --git a/src/Controller/OgRepository/Webhook/ResponseController.php b/src/Controller/OgRepository/Webhook/ResponseController.php index b4900f7..3dee082 100644 --- a/src/Controller/OgRepository/Webhook/ResponseController.php +++ b/src/Controller/OgRepository/Webhook/ResponseController.php @@ -51,10 +51,17 @@ class ResponseController extends AbstractOgRepositoryController $imageImageRepository = $this->getImageImageRepository($trace); if (!$imageImageRepository) return $this->jsonResponseError('Image not found', Response::HTTP_NOT_FOUND, $trace); + $imageImageRepository->setStatus(ImageStatus::SUCCESS); + $this->entityManager->persist($imageImageRepository); + + if (isset($data['success']) && $data['success'] !== true) { + $this->updateTraceStatus($trace, TraceStatus::FAILED, $data['output'] ?? 'Action failed'); + return new JsonResponse(['message' => 'Success'], Response::HTTP_OK); + } + if ($setFullsum) { $imageImageRepository->setImageFullsum($data['image_id']); } - $imageImageRepository->setStatus(ImageStatus::SUCCESS); $this->entityManager->persist($imageImageRepository); $this->updateTraceStatus($trace, TraceStatus::SUCCESS); @@ -83,6 +90,7 @@ class ResponseController extends AbstractOgRepositoryController } $newImageRepo = new ImageImageRepository(); + $newImageRepo->setName($image->getName().'_v'.($originImageImageRepository->getVersion() + 1)); $newImageRepo->setImage($image); $newImageRepo->setRepository($repository); $newImageRepo->setStatus(ImageStatus::SUCCESS); diff --git a/src/Dto/Input/CommandExecuteInput.php b/src/Dto/Input/CommandExecuteInput.php index 4630a47..9d903f0 100644 --- a/src/Dto/Input/CommandExecuteInput.php +++ b/src/Dto/Input/CommandExecuteInput.php @@ -14,4 +14,9 @@ final class CommandExecuteInput #[Assert\NotNull] #[Groups(['command:write'])] public array $clients = []; + + + #[Assert\NotNull] + #[Groups(['command:write'])] + public string $script = ''; } \ No newline at end of file diff --git a/src/Dto/Input/CommandInput.php b/src/Dto/Input/CommandInput.php index 10697d8..d381efc 100644 --- a/src/Dto/Input/CommandInput.php +++ b/src/Dto/Input/CommandInput.php @@ -40,6 +40,13 @@ final class CommandInput )] public ?bool $enabled = true; + #[Groups(['command:write'])] + #[ApiProperty( + description: 'Tiene parámetros?', + example: 'true', + )] + public ?bool $parameters = true; + #[Groups(['command:write'])] #[ApiProperty( description: 'Los comentarios del comando', @@ -57,6 +64,7 @@ final class CommandInput $this->script = $command->getScript(); $this->enabled = $command->isEnabled(); $this->readOnly = $command->isReadOnly(); + $this->parameters = $command->isParameters(); $this->comments = $command->getComments(); } @@ -70,6 +78,7 @@ final class CommandInput $command->setScript($this->script); $command->setEnabled($this->enabled); $command->setReadOnly($this->readOnly); + $command->setParameters($this->parameters); $command->setComments($this->comments); return $command; diff --git a/src/Dto/Input/ImageImageRepositoryInput.php b/src/Dto/Input/ImageImageRepositoryInput.php index 9cd95ca..b595bb2 100644 --- a/src/Dto/Input/ImageImageRepositoryInput.php +++ b/src/Dto/Input/ImageImageRepositoryInput.php @@ -12,13 +12,8 @@ use Symfony\Component\Validator\Constraints as Assert; final class ImageImageRepositoryInput { - #[Assert\NotNull] #[Groups(['image-image-repository:write'])] - public ?ImageRepositoryOutput $imageRepository = null; - - #[Assert\NotNull] - #[Groups(['image-image-repository:write'])] - public ?string $status = ''; + public ?string $description = ''; public function __construct(?ImageImageRepository $imageImageRepository = null) { @@ -26,8 +21,7 @@ final class ImageImageRepositoryInput return; } - $this->imageRepository = new ImageRepositoryOutput($imageImageRepository->getRepository()); - $this->status = $imageImageRepository->getStatus(); + $this->description = $imageImageRepository->getDescription(); } public function createOrUpdateEntity(?ImageImageRepository $imageImageRepository = null): ImageImageRepository @@ -36,8 +30,7 @@ final class ImageImageRepositoryInput $imageImageRepository = new ImageImageRepository(); } - $imageImageRepository->setRepository($this->imageRepository); - $imageImageRepository->setStatus($this->status); + $imageImageRepository->setDescription($this->description); return $imageImageRepository; } diff --git a/src/Dto/Input/ImageInput.php b/src/Dto/Input/ImageInput.php index fc79464..00dd01d 100644 --- a/src/Dto/Input/ImageInput.php +++ b/src/Dto/Input/ImageInput.php @@ -5,6 +5,7 @@ namespace App\Dto\Input; use ApiPlatform\Metadata\ApiProperty; use App\Dto\Output\ClientOutput; use App\Dto\Output\ImageImageRepositoryOutput; +use App\Dto\Output\ImageOutput; use App\Dto\Output\ImageRepositoryOutput; use App\Dto\Output\OrganizationalUnitOutput; use App\Dto\Output\PartitionOutput; @@ -37,6 +38,10 @@ final class ImageInput #[ApiProperty(description: 'The type of the image', example: "Server")] public ?string $source = 'input'; + #[Groups(['image:write'])] + #[ApiProperty(description: 'The optional selected image')] + public ?ImageOutput $selectedImage = null; + #[Groups(['image:write'])] #[ApiProperty(description: 'The software profile of the image')] public ?SoftwareProfileOutput $softwareProfile = null; @@ -60,6 +65,10 @@ final class ImageInput #[ApiProperty(description: 'The parent of the image')] public ?self $parent = null; + #[Groups(['image:write'])] + #[ApiProperty(description: 'The parent of the image')] + public ?int $version = null; + #[Groups(['image:write'])] #[ApiProperty(description: 'The remote pc of the image')] public ?bool $remotePc = false; @@ -79,6 +88,7 @@ final class ImageInput $this->comments = $image->getComments(); $this->remotePc = $image->isRemotePc(); $this->isGlobal = $image->isGlobal(); + $this->version = $image->getVersion(); if ($image->getSoftwareProfile()) { $this->softwareProfile = new SoftwareProfileOutput($image->getSoftwareProfile()); diff --git a/src/Dto/Input/ImageRepositoryInput.php b/src/Dto/Input/ImageRepositoryInput.php index 96e20b7..56623e1 100644 --- a/src/Dto/Input/ImageRepositoryInput.php +++ b/src/Dto/Input/ImageRepositoryInput.php @@ -27,11 +27,19 @@ final class ImageRepositoryInput #[ApiProperty(description: 'The IP of the repository', example: "")] public ?string $ip = null; - #[Groups(['repository:write'])] #[ApiProperty(description: 'The comments of the repository', example: "Repository 1 comments")] public ?string $comments = null; + #[Assert\NotBlank] + #[Groups(['repository:write'])] + #[ApiProperty(description: 'The user of the repository', example: "Repository user")] + public ?string $user = 'opengnsys'; + + #[Groups(['repository:write'])] + #[ApiProperty(description: 'The sshPort of the repository', example: "Repository ssh port")] + public ?string $sshPort = '22'; + public function __construct(?ImageRepository $repository = null) { @@ -42,6 +50,8 @@ final class ImageRepositoryInput $this->name = $repository->getName(); $this->ip = $repository->getIp(); $this->comments = $repository->getComments(); + $this->user = $repository->getUser(); + $this->sshPort = $repository->getSshPort(); } public function createOrUpdateEntity(?ImageRepository $repository = null): ImageRepository @@ -53,6 +63,8 @@ final class ImageRepositoryInput $repository->setName($this->name); $repository->setIp($this->ip); $repository->setComments($this->comments); + $repository->setUser($this->user); + $repository->setSshPort($this->sshPort); return $repository; } diff --git a/src/Dto/Input/RenameImageInput.php b/src/Dto/Input/RenameImageInput.php new file mode 100644 index 0000000..7fa128d --- /dev/null +++ b/src/Dto/Input/RenameImageInput.php @@ -0,0 +1,13 @@ +ip = $client->getIp(); $this->netiface = $client->getNetiface(); $this->netDriver = $client->getNetDriver(); + $this->firmwareType = $client->getFirmwareType(); if ($client->getOrganizationalUnit()) { $this->organizationalUnit = new OrganizationalUnitOutput($client->getOrganizationalUnit()); diff --git a/src/Dto/Output/CommandOutput.php b/src/Dto/Output/CommandOutput.php index 394a5d1..0622b70 100644 --- a/src/Dto/Output/CommandOutput.php +++ b/src/Dto/Output/CommandOutput.php @@ -21,6 +21,9 @@ final class CommandOutput extends AbstractOutput #[Groups(['command:read'])] public ?bool $enabled = true; + #[Groups(['command:read'])] + public ?bool $parameters = true; + #[Groups(['command:read'])] public ?string $comments = ''; @@ -37,6 +40,7 @@ final class CommandOutput extends AbstractOutput $this->name = $command->getName(); $this->script = $command->getScript(); $this->readOnly = $command->isReadOnly(); + $this->parameters = $command->isParameters(); $this->enabled = $command->isEnabled(); $this->comments = $command->getComments(); $this->createdAt = $command->getCreatedAt(); diff --git a/src/Dto/Output/ImageImageRepositoryOutput.php b/src/Dto/Output/ImageImageRepositoryOutput.php index a7b3830..9d4c4a0 100644 --- a/src/Dto/Output/ImageImageRepositoryOutput.php +++ b/src/Dto/Output/ImageImageRepositoryOutput.php @@ -18,9 +18,21 @@ class ImageImageRepositoryOutput extends AbstractOutput #[Groups(['image-image-repository:read', 'image:read'])] public string $status; + #[Groups(['image-image-repository:read', 'image:read'])] + public string $name; + #[Groups(['image-image-repository:read', 'image:read'])] public ?string $imageFullsum = null; + #[Groups(['image-image-repository:read', 'image:read'])] + public ?string $datasize = null; + + #[Groups(['image-image-repository:read', 'image:read'])] + public ?string $description = null; + + #[Groups(['image:read', 'image-image-repository:read'])] + public ?int $version = null; + #[Groups(['image-image-repository:read', 'image:read'])] public \DateTime $createdAt; @@ -41,8 +53,12 @@ class ImageImageRepositoryOutput extends AbstractOutput $this->imageRepository = new ImageRepositoryOutput($imageImageRepository->getRepository()); } + $this->name = $imageImageRepository->getName(); + $this->version = $imageImageRepository->getVersion(); $this->status = $imageImageRepository->getStatus(); $this->imageFullsum = $imageImageRepository->getImageFullsum(); + $this->datasize = $imageImageRepository->getDatasize(); + $this->description = $imageImageRepository->getDescription(); $this->createdAt = $imageImageRepository->getCreatedAt(); $this->createdBy = $imageImageRepository->getCreatedBy(); } diff --git a/src/Dto/Output/ImageOutput.php b/src/Dto/Output/ImageOutput.php index d1f4aa1..5daead3 100644 --- a/src/Dto/Output/ImageOutput.php +++ b/src/Dto/Output/ImageOutput.php @@ -39,6 +39,9 @@ final class ImageOutput extends AbstractOutput #[Groups(['image:read'])] public ?array $partitionInfo = null; + #[Groups(['image:read', 'image-image-repository:read'])] + public ?int $version = null; + #[Groups(['image:read'])] public \DateTime $createdAt; @@ -58,6 +61,7 @@ final class ImageOutput extends AbstractOutput fn(ImageImageRepository $image) => new ImageImageRepositoryOutput($image) )->toArray(); + $this->version = $image->getVersion(); $this->partitionInfo = json_decode($image->getPartitionInfo(), true); $this->remotePc = $image->isRemotePc(); $this->isGlobal = $image->isGlobal(); diff --git a/src/Dto/Output/ImageRepositoryOutput.php b/src/Dto/Output/ImageRepositoryOutput.php index daf72e3..44445b6 100644 --- a/src/Dto/Output/ImageRepositoryOutput.php +++ b/src/Dto/Output/ImageRepositoryOutput.php @@ -18,6 +18,12 @@ class ImageRepositoryOutput extends AbstractOutput #[Groups(['repository:read'])] public ?string $comments = ''; + #[Groups(['repository:read'])] + public ?string $sshPort = ''; + + #[Groups(['repository:read'])] + public ?string $user = ''; + #[Groups(['repository:read'])] public \DateTime $createdAt; @@ -31,6 +37,8 @@ class ImageRepositoryOutput extends AbstractOutput $this->name = $imageRepository->getName(); $this->ip = $imageRepository->getIp(); $this->comments = $imageRepository->getComments(); + $this->sshPort = $imageRepository->getSshPort(); + $this->user = $imageRepository->getUser(); $this->createdAt = $imageRepository->getCreatedAt(); $this->createdBy = $imageRepository->getCreatedBy(); } diff --git a/src/Dto/Output/OrganizationalUnitOutput.php b/src/Dto/Output/OrganizationalUnitOutput.php index 7adb715..d5e0b69 100644 --- a/src/Dto/Output/OrganizationalUnitOutput.php +++ b/src/Dto/Output/OrganizationalUnitOutput.php @@ -48,9 +48,6 @@ final class OrganizationalUnitOutput extends AbstractOutput #[Groups(['organizational-unit:read'])] public array $children = []; - #[Groups(['organizational-unit:read'])] - public array $clients = []; - #[Groups(['organizational-unit:read', "client:read"])] #[ApiProperty(readableLink: true)] public ?RemoteCalendarOutput $remoteCalendar = null; @@ -96,12 +93,6 @@ final class OrganizationalUnitOutput extends AbstractOutput )->toArray(); } - if (isset($context['groups']) && in_array('organizational-unit:read', $context['groups'])) { - $this->clients = $organizationalUnit->getClients()->map( - fn(Client $client) => new ClientOutput($client) - )->toArray(); - } - $this->excludeParentChanges = $organizationalUnit->isExcludeParentChanges(); $this->path = $organizationalUnit->getPath(); $this->createdAt = $organizationalUnit->getCreatedAt(); diff --git a/src/Dto/Output/SubnetOutput.php b/src/Dto/Output/SubnetOutput.php index 0a9b540..672e6d1 100644 --- a/src/Dto/Output/SubnetOutput.php +++ b/src/Dto/Output/SubnetOutput.php @@ -33,9 +33,6 @@ final class SubnetOutput extends AbstractOutput #[Groups(['subnet:read'])] public ?string $dns = null; - #[Groups(['subnet:read'])] - public array $clients; - #[Groups(['subnet:read'])] public ?bool $synchronized = false; @@ -61,11 +58,6 @@ final class SubnetOutput extends AbstractOutput $this->bootFileName = $subnet->getBootFileName(); $this->synchronized = $subnet->isSynchronized(); $this->serverId = $subnet->getServerId(); - - $this->clients = $subnet->getClients()->map( - fn(Client $client) => new ClientOutput($client) - )->toArray(); - $this->createdAt = $subnet->getCreatedAt(); $this->createdBy = $subnet->getCreatedBy(); } diff --git a/src/Dto/Output/TraceOutput.php b/src/Dto/Output/TraceOutput.php index 14ea96b..94bd188 100644 --- a/src/Dto/Output/TraceOutput.php +++ b/src/Dto/Output/TraceOutput.php @@ -59,7 +59,7 @@ final class TraceOutput extends AbstractOutput $this->output = $trace->getOutput(); $this->input = $trace->getInput(); $this->finishedAt = $trace->getFinishedAt(); - $this->progress = $trace->getProgress() / 100; + $this->progress = $trace->getProgress(); $this->createdAt = $trace->getCreatedAt(); $this->createdBy = $trace->getCreatedBy(); } diff --git a/src/Entity/Client.php b/src/Entity/Client.php index 6f210ba..a3930ca 100644 --- a/src/Entity/Client.php +++ b/src/Entity/Client.php @@ -14,6 +14,9 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_MAC', fields: ['mac'])] #[UniqueEntity(fields: ['ip'], message: 'This IP address is already in use.')] #[UniqueEntity(fields: ['mac'], message: 'This MAC address is already in use.')] +#[ORM\Index(fields: ['status'], name: 'IDX_STATUS')] +#[ORM\Index(fields: ['updatedAt'], name: 'IDX_UPDATED_AT')] +#[ORM\Index(fields: ['status', 'updatedAt'], name: 'IDX_STATUS_UPDATED_AT')] class Client extends AbstractEntity { use NameableTrait; @@ -82,6 +85,9 @@ class Client extends AbstractEntity #[ORM\Column(nullable: true)] private ?bool $pxeSync = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $firmwareType = null; + public function __construct() { parent::__construct(); @@ -335,4 +341,16 @@ class Client extends AbstractEntity return $this; } + + public function getFirmwareType(): ?string + { + return $this->firmwareType; + } + + public function setFirmwareType(?string $firmwareType): static + { + $this->firmwareType = $firmwareType; + + return $this; + } } diff --git a/src/Entity/Command.php b/src/Entity/Command.php index f43aa30..0fea5cf 100644 --- a/src/Entity/Command.php +++ b/src/Entity/Command.php @@ -35,6 +35,9 @@ class Command extends AbstractEntity #[ORM\ManyToMany(targetEntity: CommandTask::class, mappedBy: 'commands')] private Collection $commandTasks; + #[ORM\Column(nullable: true)] + private ?bool $parameters = null; + public function __construct() { parent::__construct(); @@ -131,4 +134,16 @@ class Command extends AbstractEntity return $this; } + + public function isParameters(): ?bool + { + return $this->parameters; + } + + public function setParameters(?bool $parameters): static + { + $this->parameters = $parameters; + + return $this; + } } diff --git a/src/Entity/Image.php b/src/Entity/Image.php index e9607d7..727c212 100644 --- a/src/Entity/Image.php +++ b/src/Entity/Image.php @@ -46,6 +46,9 @@ class Image extends AbstractEntity #[ORM\OneToMany(mappedBy: 'image', targetEntity: ImageImageRepository::class, cascade: ['persist'], orphanRemoval: true)] private Collection $imageImageRepositories; + #[ORM\Column(nullable: true)] + private ?int $version = null; + public function __construct() { parent::__construct(); @@ -191,4 +194,16 @@ class Image extends AbstractEntity return false; } + + public function getVersion(): ?int + { + return $this->version; + } + + public function setVersion(?int $version): static + { + $this->version = $version; + + return $this; + } } diff --git a/src/Entity/ImageImageRepository.php b/src/Entity/ImageImageRepository.php index 169df79..6de9f49 100644 --- a/src/Entity/ImageImageRepository.php +++ b/src/Entity/ImageImageRepository.php @@ -7,8 +7,6 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; #[ORM\Entity(repositoryClass: ImageImageRepositoryRepository::class)] -#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_IMAGE_REPOSITORY', columns: ['image_id', 'repository_id'])] -#[UniqueEntity(fields: ['image', 'repository'], message: 'This image is already associated with this repository')] class ImageImageRepository extends AbstractEntity { #[ORM\ManyToOne(targetEntity: Image::class, cascade: ['persist'], inversedBy: 'imageImageRepositories')] @@ -28,6 +26,18 @@ class ImageImageRepository extends AbstractEntity #[ORM\Column(length: 255, nullable: true)] private ?string $imageFullsum = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $datasize = null; + + #[ORM\Column(nullable: true)] + private ?int $version = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $description = null; + + #[ORM\Column(length: 255)] + private ?string $name = null; + public function getImage(): ?Image { return $this->image; @@ -87,4 +97,52 @@ class ImageImageRepository extends AbstractEntity return $this; } + + public function getDatasize(): ?string + { + return $this->datasize; + } + + public function setDatasize(?string $datasize): static + { + $this->datasize = $datasize; + + return $this; + } + + public function getVersion(): ?int + { + return $this->version; + } + + public function setVersion(?int $version): static + { + $this->version = $version; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): static + { + $this->description = $description; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } } diff --git a/src/Entity/ImageRepository.php b/src/Entity/ImageRepository.php index 1dc49fe..7588190 100644 --- a/src/Entity/ImageRepository.php +++ b/src/Entity/ImageRepository.php @@ -10,6 +10,8 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: ImageRepositoryRepository::class)] class ImageRepository extends AbstractEntity { + const string DEFAULT_USER = 'opengnsys'; + use NameableTrait; #[ORM\Column(length: 255)] @@ -24,6 +26,12 @@ class ImageRepository extends AbstractEntity #[ORM\OneToMany(mappedBy: 'repository', targetEntity: ImageImageRepository::class)] private Collection $imageImageRepositories; + #[ORM\Column(length: 255, nullable: true)] + private ?string $user = self::DEFAULT_USER; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $sshPort = null; + public function __construct() { parent::__construct(); @@ -83,4 +91,28 @@ class ImageRepository extends AbstractEntity return $this; } + + public function getUser(): ?string + { + return $this->user; + } + + public function setUser(?string $user): static + { + $this->user = $user; + + return $this; + } + + public function getSshPort(): ?string + { + return $this->sshPort; + } + + public function setSshPort(?string $sshPort): static + { + $this->sshPort = $sshPort; + + return $this; + } } diff --git a/src/EventListener/ClientStatusNotifier.php b/src/EventListener/ClientStatusNotifier.php index 8ec0ff9..b4ab843 100644 --- a/src/EventListener/ClientStatusNotifier.php +++ b/src/EventListener/ClientStatusNotifier.php @@ -25,6 +25,14 @@ class ClientStatusNotifier public function postUpdate(Client $client, PostUpdateEventArgs $event): void { + $em = $event->getObjectManager(); + $uow = $em->getUnitOfWork(); + $changeSet = $uow->getEntityChangeSet($client); + + if (!array_key_exists('status', $changeSet)) { + return; + } + try { $this->notifyClientStatusChange($client); } catch (\Exception $e) { @@ -38,9 +46,14 @@ class ClientStatusNotifier private function notifyClientStatusChange(Client $client): void { + $data[] = [ + '@id' => '/clients/' . $client->getUuid(), + 'status' => $client->getStatus(), + ]; + $update = new Update( 'clients', - json_encode(['@id' => '/clients/'.$client->getUuid(), 'status' => $client->getStatus()]) + json_encode($data) ); $this->hub->publish($update); diff --git a/src/EventListener/TraceStatusProgressNotifier.php b/src/EventListener/TraceStatusProgressNotifier.php index 53eaff9..9737c46 100644 --- a/src/EventListener/TraceStatusProgressNotifier.php +++ b/src/EventListener/TraceStatusProgressNotifier.php @@ -42,7 +42,7 @@ class TraceStatusProgressNotifier { $update = new Update( 'traces', - json_encode(['@id' => '/traces/' . $trace->getUuid(), 'status' => $trace->getStatus(), 'progress' => $trace->getProgress() / 100]) + json_encode(['@id' => '/traces/' . $trace->getUuid(), 'status' => $trace->getStatus(), 'progress' => $trace->getProgress()]) ); $this->hub->publish($update); diff --git a/src/EventSubscriber/MercureSubscriber.php b/src/EventSubscriber/MercureSubscriber.php index 4f225ef..aaf1b3d 100644 --- a/src/EventSubscriber/MercureSubscriber.php +++ b/src/EventSubscriber/MercureSubscriber.php @@ -58,9 +58,14 @@ class MercureSubscriber implements EventSubscriberInterface /** @var Client $client */ $client = $clientOutput->getEntity(); + $data[] = [ + '@id' => '/clients/' . $client->getUuid(), + 'status' => $client->getStatus(), + ]; + $update = new Update( 'clients', - json_encode(['@id' => '/clients/'.$client->getUuid(), 'status' => $client->getStatus()]) + json_encode($data) ); $this->hub->publish($update); diff --git a/src/EventSubscriber/OrganizationalUnitSubscriber.php b/src/EventSubscriber/OrganizationalUnitSubscriber.php index d305545..b41a44b 100644 --- a/src/EventSubscriber/OrganizationalUnitSubscriber.php +++ b/src/EventSubscriber/OrganizationalUnitSubscriber.php @@ -5,18 +5,25 @@ namespace App\EventSubscriber; use ApiPlatform\Symfony\EventListener\EventPriorities; use App\Controller\OgBoot\PxeBootFile\PostAction; use App\Dto\Output\OrganizationalUnitOutput; +use App\Entity\Client; use App\Entity\NetworkSettings; use App\Entity\OrganizationalUnit; +use App\Model\OrganizationalUnitTypes; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; final readonly class OrganizationalUnitSubscriber implements EventSubscriberInterface { public function __construct( private EntityManagerInterface $entityManager, + private readonly PostAction $postAction, ) { @@ -29,6 +36,12 @@ final readonly class OrganizationalUnitSubscriber implements EventSubscriberInte ]; } + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ public function updateNetworkSettings(ViewEvent $event): void { $organizationalUnitOutput = $event->getControllerResult(); @@ -50,7 +63,9 @@ final readonly class OrganizationalUnitSubscriber implements EventSubscriberInte $this->updateChildrenNetworkSettings($organizationalUnitEntity, $newNetworkSettings); - $this->entityManager->flush(); + if ($organizationalUnitEntity->getType() === OrganizationalUnitTypes::CLASSROOM) { + //$this->syncOgBoot($organizationalUnitEntity); + } } @@ -58,14 +73,21 @@ final readonly class OrganizationalUnitSubscriber implements EventSubscriberInte { /** @var OrganizationalUnit $childUnit */ foreach ($parentUnit->getOrganizationalUnits() as $childUnit) { - //var_dump($childUnit->getNetworkSettings()->getMcastPort()); if ($childUnit->isExcludeParentChanges()) { $childUnit->setNetworkSettings(null); } else{ $childUnit->setNetworkSettings($networkSettings); + foreach ($childUnit->getClients() as $client) { + $client->setOgLive($networkSettings->getOgLive()); + $client->setMenu($networkSettings->getMenu()); + $client->setRepository($networkSettings->getRepository()); + $this->entityManager->persist($client); + } + } $this->entityManager->persist($childUnit); + $this->entityManager->flush(); $this->updateChildrenNetworkSettings($childUnit, $networkSettings); } @@ -94,7 +116,32 @@ final readonly class OrganizationalUnitSubscriber implements EventSubscriberInte $newNetworkSettings->setMenu($organizationalUnitEntity->getNetworkSettings()->getMenu()); $newNetworkSettings->setRepository($organizationalUnitEntity->getNetworkSettings()->getRepository()); $newNetworkSettings->setOgLive($organizationalUnitEntity->getNetworkSettings()->getOgLive()); + $newNetworkSettings->setNetiface($organizationalUnitEntity->getNetworkSettings()->getNetiface()); return $newNetworkSettings; } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + private function syncOgBoot(OrganizationalUnit $organizationalUnitEntity): void + { + $clients = $this->entityManager->getRepository(Client::class)->findClientsByOrganizationalUnitAndDescendants($organizationalUnitEntity->getId(), []); + + if (empty($clients)) { + return; + } + + /** @var Client $client */ + foreach ($clients as $client) { + if ($client->getTemplate() === null) { + continue; + } + + $this->postAction->__invoke($client, $client->getTemplate()); + } + } } \ No newline at end of file diff --git a/src/Factory/ImageImageRepositoryFactory.php b/src/Factory/ImageImageRepositoryFactory.php new file mode 100644 index 0000000..057f907 --- /dev/null +++ b/src/Factory/ImageImageRepositoryFactory.php @@ -0,0 +1,58 @@ + + */ +final class ImageImageRepositoryFactory extends ModelFactory +{ + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services + * + * @todo inject services if required + */ + public function __construct() + { + parent::__construct(); + } + + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories + * + * @todo add your default values here + */ + protected function getDefaults(): array + { + return [ + 'createdAt' => self::faker()->dateTime(), + 'image' => ImageFactory::new(), + 'name' => self::faker()->text(255), + 'repository' => ImageRepositoryFactory::new(), + 'status' => self::faker()->text(255), + 'updatedAt' => self::faker()->dateTime(), + ]; + } + + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization + */ + protected function initialize(): self + { + return $this + // ->afterInstantiate(function(ImageImageRepository $imageImageRepository): void {}) + ; + } + + protected static function getClass(): string + { + return ImageImageRepository::class; + } +} diff --git a/src/Model/ClientStatus.php b/src/Model/ClientStatus.php index c9882bb..01d88fb 100644 --- a/src/Model/ClientStatus.php +++ b/src/Model/ClientStatus.php @@ -6,6 +6,7 @@ final class ClientStatus { public const string OFF = 'off'; public const string INITIALIZING = 'initializing'; + public const string DISCONNECTED = 'disconnected'; public const string TURNING_OFF = 'turning-off'; public const string OG_LIVE = 'og-live'; public const string BUSY = 'busy'; @@ -20,6 +21,7 @@ final class ClientStatus self::OFF => 'Apagado', self::TURNING_OFF => 'Apagando', self::INITIALIZING => 'Inicializando', + self::DISCONNECTED => 'Conexión perdida', self::OG_LIVE => 'OG Live', self::BUSY => 'Ocupado', self::LINUX => 'Linux', diff --git a/src/Model/CommandTypes.php b/src/Model/CommandTypes.php index d86c277..4f31c21 100644 --- a/src/Model/CommandTypes.php +++ b/src/Model/CommandTypes.php @@ -12,6 +12,7 @@ final class CommandTypes public const string BACKUP_IMAGE = 'backup-image'; public const string IMPORT_IMAGE = 'import-image'; public const string EXPORT_IMAGE = 'export-image'; + public const string RENAME_IMAGE = 'rename-image'; public const string CONVERT_IMAGE_TO_VIRTUAL = 'convert-image-to-virtual'; public const string TRANSFER_IMAGE = 'transfer-image'; public const string POWER_ON = 'power-on'; @@ -21,6 +22,7 @@ final class CommandTypes public const string LOGOUT = 'logout'; public const string PARTITION_AND_FORMAT = 'partition-and-format'; public const string INSTALL_OGLIVE = 'install-oglive'; + public const string RUN_SCRIPT = 'run-script'; private const array COMMAND_TYPES = [ self::DEPLOY_IMAGE => 'Deploy Image', @@ -32,6 +34,7 @@ final class CommandTypes self::BACKUP_IMAGE => 'Backup Image', self::IMPORT_IMAGE => 'Import image', self::EXPORT_IMAGE => 'Export image', + self::RENAME_IMAGE => 'Rename Image', self::POWER_ON => 'Encender', self::REBOOT => 'Reiniciar', self::SHUTDOWN => 'Apagar', @@ -40,6 +43,7 @@ final class CommandTypes self::PARTITION_AND_FORMAT => 'Partition and Format', self::TRANSFER_IMAGE => 'Transfer Image', self::INSTALL_OGLIVE => 'Instalar OgLive', + self::RUN_SCRIPT => 'Run Script', ]; public static function getCommandTypes(): array diff --git a/src/Repository/ImageImageRepositoryRepository.php b/src/Repository/ImageImageRepositoryRepository.php index a72dc6b..c40e35d 100644 --- a/src/Repository/ImageImageRepositoryRepository.php +++ b/src/Repository/ImageImageRepositoryRepository.php @@ -2,7 +2,9 @@ namespace App\Repository; +use App\Entity\Image; use App\Entity\ImageImageRepository; +use App\Entity\ImageRepository as Repository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -15,4 +17,27 @@ class ImageImageRepositoryRepository extends AbstractRepository { parent::__construct($registry, ImageImageRepository::class); } + + public function findLatestVersionByImageAndRepository(Image $image, Repository $repository): ?ImageImageRepository + { + return $this->createQueryBuilder('i') + ->andWhere('i.image = :imageId') + ->setParameter('imageId', $image->getId()) + ->andWhere('i.repository = :repository') + ->setParameter('repository', $repository->getId()) + ->orderBy('i.version', 'DESC') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + public function findLatestVersion(): ?ImageImageRepository + { + return $this->createQueryBuilder('i') + ->orderBy('i.version', 'DESC') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + } diff --git a/src/Service/CreatePartitionService.php b/src/Service/CreatePartitionService.php index 5b63ea8..f5153b1 100644 --- a/src/Service/CreatePartitionService.php +++ b/src/Service/CreatePartitionService.php @@ -26,11 +26,19 @@ class CreatePartitionService $receivedPartitions = []; - foreach ($data['cfg'] as $cfg) { - if (!isset($cfg['disk'], $cfg['par'], $cfg['tam'], $cfg['uso'], $cfg['fsi'])) { - continue; - } + $filteredCfg = array_filter($data['cfg'], function ($cfg) { + return isset($cfg['disk'], $cfg['par'], $cfg['tam'], $cfg['uso'], $cfg['fsi']); + }); + foreach ($data['cfg'] as $cfg) { + if (!empty($cfg['fwt'])) { + $clientEntity->setFirmwareType($cfg['fwt']); + $this->entityManager->persist($clientEntity); + break; + } + } + + foreach ($filteredCfg as $cfg) { $partitionEntity = $this->entityManager->getRepository(Partition::class) ->findOneBy(['client' => $clientEntity, 'diskNumber' => $cfg['disk'], 'partitionNumber' => $cfg['par']]); @@ -38,6 +46,8 @@ class CreatePartitionService $partitionEntity = new Partition(); } + $partitionEntity->setOperativeSystem(null); + if (isset($cfg['soi']) && $cfg['soi'] !== '') { $operativeSystem = $this->entityManager->getRepository(OperativeSystem::class) ->findOneBy(['name' => $cfg['soi']]); diff --git a/src/State/Processor/ImageProcessor.php b/src/State/Processor/ImageProcessor.php index ac7ad1a..efea176 100644 --- a/src/State/Processor/ImageProcessor.php +++ b/src/State/Processor/ImageProcessor.php @@ -10,6 +10,7 @@ use ApiPlatform\Metadata\Put; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Validator\ValidatorInterface; use App\Controller\OgAgent\CreateImageAction; +use App\Controller\OgRepository\Image\RenameAction; use App\Controller\OgRepository\Image\TransferAction; use App\Dto\Input\ImageInput; use App\Dto\Input\ImageRepositoryInput; @@ -17,15 +18,16 @@ use App\Dto\Output\ImageOutput; use App\Entity\ImageImageRepository; use App\Repository\ImageRepository; use App\Repository\ImageRepositoryRepository as ImageRepositoryRepository; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; readonly class ImageProcessor implements ProcessorInterface { public function __construct( - private ImageRepositoryRepository $imageRepositoryRepository, private ImageRepository $imageRepository, private ValidatorInterface $validator, private CreateImageAction $createImageActionController, + private KernelInterface $kernel, ) { } @@ -61,16 +63,24 @@ readonly class ImageProcessor implements ProcessorInterface $entity = $this->imageRepository->findOneByUuid($uriVariables['uuid']); } - $image = $data->createOrUpdateEntity($entity); - $this->validator->validate($image); - if ($data->source !== 'input') { - $response = $this->createImageActionController->__invoke($image); + if ($data->selectedImage){ + //$content = $this->renameActionController->__invoke($data->selectedImage->getEntity()); + + $response = $this->createImageActionController->__invoke($data->selectedImage->getEntity()); + + } else { + $image = $data->createOrUpdateEntity($entity); + + if ($this->kernel->getEnvironment() !== 'test') { + $response = $this->createImageActionController->__invoke($image); + } + + $this->validator->validate($image); + $this->imageRepository->save($image); } - $this->imageRepository->save($image); - - return new ImageOutput($image); + return new ImageOutput($data->selectedImage?->getEntity() ?? $image); } private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null diff --git a/src/State/Provider/ImageImageRepositoryProvider.php b/src/State/Provider/ImageImageRepositoryProvider.php index 9c47ca3..ba5a1aa 100644 --- a/src/State/Provider/ImageImageRepositoryProvider.php +++ b/src/State/Provider/ImageImageRepositoryProvider.php @@ -9,6 +9,7 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Put; use ApiPlatform\State\Pagination\TraversablePaginator; use ApiPlatform\State\ProviderInterface; +use App\Dto\Input\ImageImageRepositoryInput; use App\Dto\Output\ImageImageRepositoryOutput; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -26,6 +27,9 @@ readonly class ImageImageRepositoryProvider implements ProviderInterface switch ($operation){ case $operation instanceof GetCollection: return $this->provideCollection($operation, $uriVariables, $context); + case $operation instanceof Patch: + case $operation instanceof Put: + return $this->provideInput($operation, $uriVariables, $context); case $operation instanceof Get: return $this->provideItem($operation, $uriVariables, $context); } @@ -53,4 +57,15 @@ readonly class ImageImageRepositoryProvider implements ProviderInterface return new ImageImageRepositoryOutput($item); } + + public function provideInput(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + if (isset($uriVariables['uuid'])) { + $item = $this->itemProvider->provide($operation, $uriVariables, $context); + + return $item !== null ? new ImageImageRepositoryInput($item) : null; + } + + return new ImageImageRepositoryInput(); + } } diff --git a/tests/Functional/ImageTest.php b/tests/Functional/ImageTest.php index 9b323c7..419d86d 100644 --- a/tests/Functional/ImageTest.php +++ b/tests/Functional/ImageTest.php @@ -9,6 +9,7 @@ use App\Entity\OrganizationalUnit; use App\Entity\SoftwareProfile; use App\Factory\ClientFactory; use App\Factory\ImageFactory; +use App\Factory\ImageImageRepositoryFactory; use App\Factory\ImageRepositoryFactory; use App\Factory\OrganizationalUnitFactory; use App\Factory\SoftwareProfileFactory; @@ -70,12 +71,12 @@ class ImageTest extends AbstractTest SoftwareProfileFactory::createOne(['description' => self::SOFTWARE_PROFILE]); $swPIri = $this->findIriBy(SoftwareProfile::class, ['description' => self::SOFTWARE_PROFILE]); - $imageRepositories = ImageRepositoryFactory::createMany(5); + $imageRepositories = ImageImageRepositoryFactory::createMany(5); $this->createClientWithCredentials()->request('POST', '/images',['json' => [ 'name' => self::IMAGE_CREATE, 'softwareProfile' => $swPIri, - 'imageRepositories' => array_map(fn($repo) => '/image-repositories/'. $repo->getUuid(), $imageRepositories) + 'imageImageRepositories' => array_map(fn($repo) => '/image-repositories/'. $repo->getUuid(), $imageRepositories) ]]); $this->assertResponseStatusCodeSame(201); @@ -102,11 +103,11 @@ class ImageTest extends AbstractTest ImageFactory::createOne(['name' => self::IMAGE_CREATE]); $iri = $this->findIriBy(Image::class, ['name' => self::IMAGE_CREATE]); - $imageRepositories = ImageRepositoryFactory::createMany(5); + $imageRepositories = ImageImageRepositoryFactory::createMany(5); $this->createClientWithCredentials()->request('PUT', $iri, ['json' => [ 'name' => self::IMAGE_UPDATE, - 'imageRepositories' => array_map(fn($repo) => '/image-repositories/'. $repo->getUuid(), $imageRepositories) + 'imageImageRepositories' => array_map(fn($repo) => '/image-repositories/'. $repo->getUuid(), $imageRepositories) ]]); $this->assertResponseIsSuccessful();