Compare commits

..

No commits in common. "a8d22cfac01f72ae36d481eaca610184ecffdd13" and "20b2ea042911a49513d8ee4676e737d5ce3c9bf6" have entirely different histories.

55 changed files with 991 additions and 1596 deletions

1364
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,8 @@ when@dev:
handlers:
main:
type: stream
level: info
path: php://stderr
formatter: App\Formatter\CustomLineFormatter
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration

View File

@ -33,8 +33,6 @@ security:
- { path: ^/og-repository/webhook, roles: PUBLIC_ACCESS }
- { path: ^/og-lives/install/webhook, roles: PUBLIC_ACCESS }
- { path: ^/auth/refresh, roles: PUBLIC_ACCESS }
- { path: ^/menu-browser, roles: PUBLIC_ACCESS }
- { path: ^/menu/, roles: PUBLIC_ACCESS }
- { path: ^/, roles: IS_AUTHENTICATED_FULLY }
when@test:

View File

@ -1,33 +0,0 @@
<?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 Version20241211074943 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('DROP INDEX UNIQ_IDENTIFIER_NAME ON og_live');
$this->addSql('ALTER TABLE og_live DROP name');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE og_live ADD name VARCHAR(255) NOT NULL');
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_NAME ON og_live (name)');
}
}

View File

@ -1,33 +0,0 @@
<?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 Version20241211075520 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 network_settings DROP FOREIGN KEY FK_48869B54F7E54CF3');
$this->addSql('ALTER TABLE network_settings ADD CONSTRAINT FK_48869B54F7E54CF3 FOREIGN KEY (og_live_id) REFERENCES og_live (id) ON DELETE SET NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE network_settings DROP FOREIGN KEY FK_48869B54F7E54CF3');
$this->addSql('ALTER TABLE network_settings ADD CONSTRAINT FK_48869B54F7E54CF3 FOREIGN KEY (og_live_id) REFERENCES og_live (id)');
}
}

View File

@ -1,33 +0,0 @@
<?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 Version20241211080733 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 og_live CHANGE filename filename VARCHAR(255) NOT NULL');
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_FILENAME ON og_live (filename)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX UNIQ_IDENTIFIER_FILENAME ON og_live');
$this->addSql('ALTER TABLE og_live CHANGE filename filename VARCHAR(255) DEFAULT NULL');
}
}

View File

@ -1,31 +0,0 @@
<?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 Version20241216080914 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 menu DROP title');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE menu ADD title VARCHAR(255) NOT NULL');
}
}

View File

@ -1,31 +0,0 @@
<?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 Version20250107121226 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 menu ADD is_default TINYINT(1) NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE menu DROP is_default');
}
}

View File

@ -1,33 +0,0 @@
<?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 Version20250107124654 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_C7440455CCD7E912');
$this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455CCD7E912 FOREIGN KEY (menu_id) REFERENCES menu (id) ON DELETE SET 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 FOREIGN KEY FK_C7440455CCD7E912');
$this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455CCD7E912 FOREIGN KEY (menu_id) REFERENCES menu (id)');
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Entity\Menu;
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;
#[AsCommand(name: 'opengnsys:load-default-menu', description: 'Load the default menu')]
class LoadDefaultMenuCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $entityManager
)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$menu = new Menu();
$menu->setName('Default menu');
$menu->setResolution('1920x1080');
$menu->setComments('Default menu comments');
$menu->setPublicUrl('main');
$menu->setIsDefault(true);
$this->entityManager->persist($menu);
$this->entityManager->flush();
return Command::SUCCESS;
}
}

View File

@ -6,9 +6,7 @@ namespace App\Command\Migration;
use App\Entity\Client;
use App\Entity\Image;
use App\Entity\ImageRepository;
use App\Entity\OrganizationalUnit;
use App\Model\ImageStatus;
use App\Model\OrganizationalUnitTypes;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
@ -72,26 +70,18 @@ class MigrateImagesCommand extends Command
}
$imageEntity = $imagesRepository->findOneBy(['migrationId' => $image['idimagen']]);
$repository = $this->entityManager->getRepository(ImageRepository::class)->findAll()[0];
if(!$imageEntity) {
$imageEntity = new Image();
$imageEntity->setMigrationId((string) $image['idimagen']);
$imageEntity->setName($image['nombreca']);
$imageEntity->setClient($clientEntity);
//$imageEntity->setOrganizationalUnit($ouEntity);
$imageEntity->setRemotePc(false);
$imageEntity->setStatus(ImageStatus::SUCCESS);
$imageEntity->setRepository($repository);
$imageEntity->setOrganizationalUnit($ouEntity);
$imageEntity->setRevision((string) $image['revision']);
$imageEntity->setDescription($image['descripcion']);
$imageEntity->setComments($image['comentarios']);
}
$this->entityManager->persist($imageEntity);
$this->entityManager->flush();
}
$this->entityManager->flush();

View File

@ -18,9 +18,6 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class DeployImageAction extends AbstractController
@ -35,74 +32,35 @@ class DeployImageAction extends AbstractController
{
}
/**
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
* @throws ServerExceptionInterface
*/
public function __invoke(DeployImageInput $input, Image $image): JsonResponse
{
/** @var Partition $partition */
$partition = $input->partition->getEntity();
$inputData = [
'method' => $input->method,
'client' => $input->client->getEntity()->getUuid(),
'image' => $image->getUuid(),
'p2pMode' => $input->p2pMode,
'p2pTime' => $input->p2pTime,
'mcastIp' => $input->mcastIp,
'mcastPort' => $input->mcastPort,
'mcastSpeed' => $input->mcastSpeed,
'mcastMode' => $input->mcastMode,
'numDisk' => (string) $partition->getDiskNumber(),
'numPartition' => (string) $partition->getPartitionNumber(),
];
switch ($input->method){
case DeployMethodTypes::UNICAST:
case DeployMethodTypes::UNICAST_DIRECT:
$inputData = [
'method' => $input->method,
'client' => $input->client->getEntity()->getUuid(),
'image' => $image->getUuid(),
'numDisk' => (string) $partition->getDiskNumber(),
'numPartition' => (string) $partition->getPartitionNumber(),
];
$agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, DeployMethodTypes::UNICAST);
$this->createService->__invoke($input->client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData);
break;
case DeployMethodTypes::MULTICAST_UFTP:
case DeployMethodTypes::MULTICAST_UDPCAST:
case DeployMethodTypes::MULTICAST:
case DeployMethodTypes::MULTICAST_UFTP_DIRECT:
case DeployMethodTypes::MULTICAST_UDPCAST_DIRECT:
$inputData = [
'method' => $input->method,
'client' => $input->client->getEntity()->getUuid(),
'image' => $image->getUuid(),
'mcastIp' => $input->mcastIp,
'mcastPort' => $input->mcastPort,
'mcastSpeed' => $input->mcastSpeed,
'mcastMode' => $input->mcastMode,
'numDisk' => (string) $partition->getDiskNumber(),
'numPartition' => (string) $partition->getPartitionNumber(),
];
try {
$this->deployImageOgRepositoryAction->__invoke($input, $image, $this->httpClient);
} catch (\Exception $e) {
return new JsonResponse(data: ['error' => $e->getMessage()], status: Response::HTTP_INTERNAL_SERVER_ERROR);
}
$agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, DeployMethodTypes::MULTICAST);
$this->createService->__invoke($input->client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData);
break;
case DeployMethodTypes::TORRENT:
$inputData = [
'method' => $input->method,
'client' => $input->client->getEntity()->getUuid(),
'image' => $image->getUuid(),
'p2pMode' => $input->p2pMode,
'p2pTime' => $input->p2pTime,
'numDisk' => (string) $partition->getDiskNumber(),
'numPartition' => (string) $partition->getPartitionNumber(),
];
$agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, DeployMethodTypes::TORRENT);
$this->createService->__invoke($input->client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData);
$this->createService->__invoke($image->getClient(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData);
break;
}

View File

@ -1,59 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Entity\Client;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Twig\Environment;
class MenuBrowserController extends AbstractController
{
public function __construct(
protected readonly EntityManagerInterface $entityManager,
private Environment $twig,
)
{
}
#[Route('/menu-browser')]
public function index(Request $request): Response
{
$host = $request->getClientIp();
$partitions = [];
$client = $this->entityManager->getRepository(Client::class)->findOneBy(['ip' => $host]);
if ($client) {
$partitions = $client->getPartitions();
}
$menuName = $client->getMenu()->getPublicUrl();
return $this->render('browser/' . $menuName . '.html.twig', [
'ip' => $host,
'partitions' => $partitions,
]);
}
#[Route('/menu/{templateName}', name: 'render_menu_template')]
public function renderMenu(string $templateName, Request $request): Response
{
$templatePath = 'browser/' . $templateName . '.html.twig';
if (!$this->twig->getLoader()->exists($templatePath)) {
throw $this->createNotFoundException(sprintf('La plantilla "%s" no existe.', $templateName));
}
return $this->render($templatePath, [
'ip' => 'invitado',
'partitions' => [],
]);
}
}

View File

@ -16,7 +16,6 @@ use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@ -31,10 +30,9 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
class CreateImageAction extends AbstractController
{
public function __construct(
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
protected readonly LoggerInterface $logger,
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
)
{
}
@ -79,7 +77,6 @@ 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', [
'verify_peer' => false,
'verify_host' => false,
@ -89,9 +86,7 @@ class CreateImageAction extends AbstractController
'json' => $data,
]);
} catch (TransportExceptionInterface $e) {
$this->logger->error('Error creating image', ['image' => $image->getId(), 'error' => $e->getMessage()]);
return new JsonResponse(
data: ['error' => $e->getMessage()],
status: Response::HTTP_INTERNAL_SERVER_ERROR

View File

@ -11,11 +11,9 @@ use App\Entity\Image;
use App\Entity\Partition;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\DeployMethodTypes;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@ -30,14 +28,14 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
class DeployImageAction extends AbstractController
{
public function __construct(
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
protected readonly LoggerInterface $logger,
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
)
{
}
public function __invoke(Image $image, DeployImageInput $input, string $method)
{
if (!$image->getClient()->getIp()) {
@ -52,26 +50,6 @@ class DeployImageAction extends AbstractController
/** @var Partition $partition */
$partition = $input->partition->getEntity();
$method = match ($input->method) {
DeployMethodTypes::MULTICAST_UFTP_DIRECT, DeployMethodTypes::MULTICAST_UDPCAST_DIRECT, => 'multicast-direct',
DeployMethodTypes::MULTICAST, DeployMethodTypes::MULTICAST_UFTP, DeployMethodTypes::MULTICAST_UDPCAST => 'multicast',
DeployMethodTypes::UNICAST_DIRECT => 'unicast-direct',
DeployMethodTypes::UNICAST => 'unicast',
DeployMethodTypes::TORRENT => 'torrent',
default => throw new ValidatorException('Invalid method'),
};
$ptcMulticastValue = "$method $input->mcastPort:$input->mcastMode:$input->mcastIp:$input->mcastSpeed:$input->maxClients:$input->maxTime";
$ptcTorrentValue = "$method $input->p2pMode:$input->p2pTime";
$ptcUnicastValue = $method;
$ptcValue = match ($input->method) {
DeployMethodTypes::MULTICAST, DeployMethodTypes::MULTICAST_UFTP, DeployMethodTypes::MULTICAST_UFTP_DIRECT, DeployMethodTypes::MULTICAST_UDPCAST, DeployMethodTypes::MULTICAST_UDPCAST_DIRECT => $ptcMulticastValue,
DeployMethodTypes::UNICAST, DeployMethodTypes::UNICAST_DIRECT => $ptcUnicastValue,
DeployMethodTypes::TORRENT => $ptcTorrentValue,
default => throw new ValidatorException('Invalid method'),
};
$data = [
'dsk' => (string) $partition->getDiskNumber(),
'par' => (string) $partition->getPartitionNumber(),
@ -80,7 +58,7 @@ class DeployImageAction extends AbstractController
'nci' => $image->getName(),
'ipr' => $image->getRepository()->getIp(),
'nfn' => 'RestaurarImagen',
'ptc' => $ptcValue,
'ptc' => $method,
'ids' => '0'
];
@ -93,10 +71,8 @@ class DeployImageAction extends AbstractController
],
'json' => $data,
]);
$this->logger->info('Deploying image', ['image' => $image->getId()]);
} catch (TransportExceptionInterface $e) {
$this->logger->error('Error deploying image', ['image' => $image->getId(), 'error' => $e->getMessage()]);
return new JsonResponse(
data: ['error' => $e->getMessage()],
status: Response::HTTP_INTERNAL_SERVER_ERROR

View File

@ -15,7 +15,6 @@ use App\Model\ImageStatus;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@ -30,10 +29,9 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
class PartitionAssistantAction extends AbstractController
{
public function __construct(
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
protected readonly LoggerInterface $logger,
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
)
{
}
@ -72,11 +70,12 @@ class PartitionAssistantAction extends AbstractController
'par' => (string) $partition->partitionNumber,
'cpt' => $partition->partitionCode,
'sfi' => $partition->filesystem,
'tam' => (string) (integer) ($partition->size * 1024),
'tam' => (string) ($partition->size * 1024),
'ope' => $partition->format ? "1" : "0",
];
}
// Hacer una llamada por cada disco
foreach ($disks as $diskNumber => $diskInfo) {
$data = [];
if (!empty($diskInfo['diskData'])) {
@ -100,9 +99,7 @@ class PartitionAssistantAction extends AbstractController
],
'json' => $result,
]);
$this->logger->info('Partitioning disk', ['client' => $client->getId(), 'disk' => $diskNumber]);
} catch (TransportExceptionInterface $e) {
$this->logger->error('Error partitioning disk', ['client' => $client->getId(), 'disk' => $diskNumber, 'error' => $e->getMessage()]);
return new JsonResponse(
data: ['error' => "Error en disco $diskNumber: " . $e->getMessage()],
status: Response::HTTP_INTERNAL_SERVER_ERROR

View File

@ -14,7 +14,6 @@ use App\Model\ImageStatus;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@ -32,7 +31,6 @@ class PowerOffAction extends AbstractController
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
protected readonly LoggerInterface $logger,
)
{
}
@ -57,10 +55,8 @@ class PowerOffAction extends AbstractController
],
'json' => $data,
]);
$this->logger->info('Powering off client', ['client' => $client->getId()]);
} catch (TransportExceptionInterface $e) {
$this->logger->error('Error powering off client', ['client' => $client->getId(), 'error' => $e->getMessage()]);
return new JsonResponse(
data: ['error' => $e->getMessage()],
status: Response::HTTP_INTERNAL_SERVER_ERROR

View File

@ -14,7 +14,6 @@ use App\Model\ImageStatus;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@ -29,10 +28,9 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
class RebootAction extends AbstractController
{
public function __construct(
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
protected readonly LoggerInterface $logger,
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
)
{
}
@ -57,10 +55,8 @@ class RebootAction extends AbstractController
],
'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

View File

@ -47,7 +47,7 @@ class StatusAction extends AbstractController
throw new ValidatorException('IP is required');
}
if ($client->getStatus() === ClientStatus::OG_LIVE || $client->getStatus() === ClientStatus::OFF || $client->getStatus() === ClientStatus::BUSY || $client->getStatus() === ClientStatus::INITIALIZING) {
if ($client->getStatus() === ClientStatus::OG_LIVE || $client->getStatus() === ClientStatus::OFF || $client->getStatus() === ClientStatus::BUSY) {
$response = $this->getOgLiveStatus($client);
}
@ -79,7 +79,6 @@ class StatusAction extends AbstractController
$client->setStatus($statusCode === Response::HTTP_OK ? ClientStatus::OG_LIVE : ClientStatus::OFF);
} catch (TransportExceptionInterface $e) {
$this->logger->error('Error checking client status', ['client' => $client->getId(), 'error' => $e->getMessage()]);
$client->setStatus(ClientStatus::OFF);
$this->entityManager->persist($client);
$this->entityManager->flush();
@ -117,7 +116,7 @@ class StatusAction extends AbstractController
} catch (TransportExceptionInterface $e) {
$client->setStatus(ClientStatus::OFF);
$this->logger->error('Error checking client status', ['client' => $client->getId(), 'error' => $e->getMessage()]);
return Response::HTTP_INTERNAL_SERVER_ERROR;
}

View File

@ -7,7 +7,6 @@ namespace App\Controller\OgAgent\Webhook;
use App\Entity\Client;
use App\Entity\OrganizationalUnit;
use App\Entity\Partition;
use App\Model\ClientStatus;
use App\Service\CreatePartitionService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -48,9 +47,6 @@ class AgentController extends AbstractController
return new JsonResponse(['message' => 'Client not found'], Response::HTTP_NOT_FOUND);
}
$clientEntity->setStatus(ClientStatus::OG_LIVE);
$this->entityManager->persist($clientEntity);
if (isset($data['cfg'])) {
$this->createPartitionService->__invoke($data, $clientEntity);
}

View File

@ -113,7 +113,34 @@ class ClientsController extends AbstractController
$this->logger->info('Image updated. Success.', ['image' => (string) $image->getUuid()]);
}
if ($data['nfn'] === 'RESPUESTA_RestaurarImagen'|| $data['nfn'] === 'RESPUESTA_Configurar') {
if ($data['nfn'] === 'RESPUESTA_RestaurarImagen') {
$trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
$image = $this->entityManager->getRepository(Image::class)->findOneBy(['uuid' => $data['idi']]);
$client = $trace->getClient();
if ($data['res'] === 1) {
$trace->setStatus(TraceStatus::SUCCESS);
$trace->setFinishedAt(new \DateTime());
$image->setStatus(ImageStatus::PENDING);
$client->setStatus(ClientStatus::OG_LIVE);
if (isset($data['cfg'])) {
$this->createPartitionService->__invoke($data,$client);
}
} else {
$trace->setStatus(TraceStatus::FAILED);
$trace->setFinishedAt(new \DateTime());
$trace->setOutput($data['der']);
}
$client->setStatus(ClientStatus::OG_LIVE);
$this->entityManager->persist($client);
$this->entityManager->persist($image);
$this->entityManager->persist($trace);
$this->entityManager->flush();
}
if ($data['nfn'] === 'RESPUESTA_Configurar') {
$trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
$client = $trace->getClient();
@ -131,6 +158,7 @@ class ClientsController extends AbstractController
}
$client->setStatus(ClientStatus::OG_LIVE);
$this->entityManager->persist($client);
$this->entityManager->persist($trace);
$this->entityManager->flush();
@ -157,7 +185,7 @@ class ClientsController extends AbstractController
}
$softwareProfile = new SoftwareProfile();
$softwareProfile->setDescription('Perfil software: ' . $image->getClient()->getName());
$softwareProfile->setDescription('Perfil: ' . $image->getName());
$softwareProfile->setOrganizationalUnit($image->getClient()->getOrganizationalUnit());
foreach ($existingSoftware as $softwareEntity) {

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\Controller\OgBoot;
use App\Controller\OgBoot\PxeBootFile\PostAction;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
@ -27,11 +26,8 @@ abstract class AbstractOgBootController extends AbstractController
protected string $ogBootApiUrl,
#[Autowire(env: 'OG_CORE_IP')]
protected string $ogCoreIP,
#[Autowire(env: 'OG_LOG_IP')]
protected string $ogLogIp,
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
)
{
}

View File

@ -32,22 +32,14 @@ class SyncAction extends AbstractOgBootController
foreach ($content['message']['installed_ogLives'] as $ogLive) {
$ogLiveEntity = $this->entityManager->getRepository(OgLive::class)->findOneBy(['checksum' => $ogLive['id']]);
if (!$ogLiveEntity) {
$ogLiveEntity = $this->entityManager->getRepository(OgLive::class)->findOneBy(['filename' => str_replace(self::OG_BOOT_DIRECTORY, '', $ogLive['directory'])]);
if (!$ogLiveEntity) {
$ogLiveEntity = new OgLive();
}
$ogLiveEntity = new OgLive();
}
$this->extracted($ogLiveEntity, $ogLive);
$this->entityManager->persist($ogLiveEntity);
}
$this->entityManager->flush();
if (isset($content['message']['default_oglive'])) {
$this->serDefaultOgLive($content['message']['default_oglive']);
}
//$this->serDefaultOgLive($content['default_oglive']);
return new JsonResponse(data: $content, status: Response::HTTP_OK);
}
@ -59,10 +51,13 @@ class SyncAction extends AbstractOgBootController
*/
private function extracted(OgLive $ogLiveEntity, mixed $ogLive): void
{
if (!$ogLiveEntity->getId()){
$ogLiveEntity->setName(str_replace(self::OG_BOOT_DIRECTORY, '', $ogLive['directory']));
}
$ogLiveEntity->setInstalled(true);
$ogLiveEntity->setArchitecture($ogLive['architecture']);
$ogLiveEntity->setDistribution($ogLive['distribution']);
$ogLiveEntity->setFilename(str_replace(self::OG_BOOT_DIRECTORY, '', $ogLive['directory']));
$ogLiveEntity->setFilename($ogLive['directory']);
$ogLiveEntity->setKernel($ogLive['kernel']);
$ogLiveEntity->setRevision($ogLive['revision']);
$ogLiveEntity->setDirectory($ogLive['directory']);
@ -72,7 +67,7 @@ class SyncAction extends AbstractOgBootController
private function serDefaultOgLive(string $defaultOgLive): void
{
$ogLiveEntity = $this->entityManager->getRepository(OgLive::class)->findOneBy(['filename' => $defaultOgLive]);
$ogLiveEntity = $this->entityManager->getRepository(OgLive::class)->findOneBy(['name' => $defaultOgLive]);
if (!$ogLiveEntity) {
return;

View File

@ -6,7 +6,6 @@ use App\Controller\OgBoot\AbstractOgBootController;
use App\Entity\OgLive;
use App\Model\OgLiveStatus;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@ -25,11 +24,11 @@ class InstallOgLiveResponseAction extends AbstractController
{
public CONST string OG_LIVE_INSTALL_SUCCESS = 'success';
public CONST string OG_LIVE_INSTALL_FAILED = 'failure';
const string OG_BOOT_DIRECTORY = '/opt/opengnsys/ogboot/tftpboot//';
public function __construct(
protected readonly EntityManagerInterface $entityManager,
protected readonly LoggerInterface $logger,
protected readonly EntityManagerInterface $entityManager
)
{
}
@ -64,7 +63,6 @@ class InstallOgLiveResponseAction extends AbstractController
}
$this->updateOgLive($ogLive, $message, $status);
$this->logger->info(sprintf('OgLive %s updated successfully', $ogLive->getChecksum()));
return new JsonResponse(data: sprintf('OgLive %s updated successfully', $ogLive->getChecksum()), status: Response::HTTP_OK);
}
@ -72,7 +70,6 @@ class InstallOgLiveResponseAction extends AbstractController
private function updateOgLive (OgLive $ogLive, array $details, string $status): void
{
if ($status === self::OG_LIVE_INSTALL_SUCCESS) {
$ogLive->setFilename(str_replace(self::OG_BOOT_DIRECTORY, '', $ogLive['directory']));
$ogLive->setInstalled(true);
$ogLive->setSynchronized(true);
$ogLive->setChecksum($details['id']);

View File

@ -28,14 +28,6 @@ class PostAction extends AbstractOgBootController
*/
public function __invoke(Client $client, PxeTemplate $pxeTemplate): JsonResponse
{
$ogRepoIp = $this->ogBootApiUrl;
if ($client->getRepository()) {
$ogRepoIp = $client->getRepository()->getIp();
} else if ($client->getOrganizationalUnit()->getNetworkSettings()->getRepository()) {
$ogRepoIp = $client->getOrganizationalUnit()->getNetworkSettings()->getRepository()->getIp();
}
$params = [
'json' => [
'template_name' => $pxeTemplate->getName(),
@ -48,10 +40,10 @@ class PostAction extends AbstractOgBootController
'computer_name' => $client->getName(),
'netiface' => $client->getNetiface(),
'group' => $client->getOrganizationalUnit()->getName(),
'ogrepo' => $ogRepoIp,
'ogrepo' => $client->getRepository() ? $client->getRepository()->getIp() : $client->getOrganizationalUnit()->getNetworkSettings()->getRepository()->getIp(),
'ogcore' => $this->ogCoreIP,
'oglive' => $this->ogBootApiUrl,
'oglog' => $this->ogLogIp,
'oglog' => $client->getOrganizationalUnit()->getNetworkSettings()?->getOgLog(),
'ogshare' => $client->getOrganizationalUnit()->getNetworkSettings()?->getOgShare()
? $client->getOrganizationalUnit()->getNetworkSettings()?->getOgShare(): $this->ogBootApiUrl,
'oglivedir' => $client->getOgLive()->getFilename(),

View File

@ -7,8 +7,6 @@ use App\Dto\Input\DeployImageInput;
use App\Entity\Command;
use App\Entity\Image;
use App\Model\CommandTypes;
use App\Model\DeployImageTypes;
use App\Model\DeployMethodTypes;
use App\Model\TraceStatus;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@ -26,31 +24,12 @@ class DeployImageAction extends AbstractOgRepositoryController
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
*/
public function __invoke(DeployImageInput $input, Image $data, HttpClientInterface $httpClient): JsonResponse
{
$client = $input->client;
$params = [
'json' => [
'ID_img' => $data->getImageFullsum(),
'bitrate' => (string) $input->mcastSpeed.'M',
'ip' => $input->mcastIp,
'port' => $input->mcastPort,
'method' => $input->mcastMode,
'nclients' => $input->maxClients,
'maxtime' => $input->maxTime
]
];
$type = match ($input->method) {
'udpcast', 'udpcast_direct' => DeployMethodTypes::MULTICAST_UDPCAST,
'p2p' => DeployMethodTypes::TORRENT,
default => DeployMethodTypes::MULTICAST_UFTP,
};
$content = $this->createRequest('POST', 'http://'.$data->getRepository()->getIp().':8006/ogrepository/v1/'.$type, $params);
$this->createService->__invoke($data->getClient(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, null);
return new JsonResponse(data: [], status: Response::HTTP_OK);
}

View File

@ -55,7 +55,7 @@ class WoLAction extends AbstractOgRepositoryController
$content = $this->createRequest('POST', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/wol', $params);
$client->setStatus(ClientStatus::INITIALIZING);
$client->setStatus(ClientStatus::OFF);
$this->entityManager->persist($client);
$this->entityManager->flush();

View File

@ -40,20 +40,14 @@ class DeployImageInput
public ?string $mcastIp = null;
#[Groups(['image:write'])]
public ?int $mcastSpeed = null;
public ?string $mcastSpeed = null;
#[OrganizationalUnitMulticastPort]
#[Groups(['image:write'])]
public ?int $mcastPort = null;
public ?string $mcastPort = null;
#[OrganizationalUnitMulticastMode]
#[Groups(['image:write'])]
public ?string $mcastMode = null;
#[Groups(['image:write'])]
public ?int $maxClients = null;
#[Groups(['image:write'])]
public ?int $maxTime = null;
}

View File

@ -16,6 +16,11 @@ final class MenuInput
#[ApiProperty(description: 'The name of the menu', example: "Menu 1")]
public ?string $name = null;
#[Assert\NotNull()]
#[Groups(['menu:write'])]
#[ApiProperty(description: 'The title of the menu', example: "Menu 1 title")]
public ?string $title = null;
#[Groups(['menu:write'])]
#[ApiProperty(description: 'Comments of the menu', example: "Menu 1 comments")]
public ?string $comments = null;
@ -33,10 +38,6 @@ final class MenuInput
#[ApiProperty(description: 'The private url of the menu', example: "http://example.com")]
public ?string $privateUrl = null;
#[Groups(['menu:write'])]
#[ApiProperty(description: 'The default menu', example: "false")]
public ?bool $isDefault = false;
public function __construct(?Menu $menu = null)
{
if (!$menu) {
@ -44,11 +45,11 @@ final class MenuInput
}
$this->name = $menu->getName();
$this->title = $menu->getTitle();
$this->comments = $menu->getComments();
$this->resolution = $menu->getResolution();
$this->publicUrl = $menu->getPublicUrl();
$this->privateUrl = $menu->getPrivateUrl();
$this->isDefault = $menu->isDefault();
}
public function createOrUpdateEntity(?Menu $menu = null): Menu
@ -58,11 +59,11 @@ final class MenuInput
}
$menu->setName($this->name);
$menu->setTitle($this->title);
$menu->setComments($this->comments);
$menu->setResolution($this->resolution);
$menu->setPublicUrl($this->publicUrl);
$menu->setPrivateUrl($this->privateUrl);
$menu->setIsDefault($this->isDefault);
return $menu;
}

View File

@ -10,7 +10,10 @@ use Symfony\Component\Validator\Constraints as Assert;
final class OgLiveInput
{
const string DOWNLOAD_URL = 'https://ognproject.evlt.uma.es/oglive//';
#[Assert\NotBlank(message: 'validators.hardware.name.not_blank')]
#[Groups(['og-live:write'])]
#[ApiProperty(description: 'The name of the ogLive', example: "OgLive 1")]
public ?string $name = null;
#[Groups(['og-live:write'])]
#[ApiProperty(description: 'The download url of the ogLive', example: "http://example.com/oglive1.iso")]
@ -22,6 +25,7 @@ final class OgLiveInput
return;
}
$this->name = $ogLive->getName();
$this->downloadUrl = $ogLive->getDownloadUrl();
}
@ -31,13 +35,7 @@ final class OgLiveInput
$ogLive = new OgLive();
}
$filename = str_replace(self::DOWNLOAD_URL, '', $this->downloadUrl);
if (str_ends_with($filename, '.iso')) {
$filename = substr($filename, 0, -4);
}
$ogLive->setFilename($filename);
$ogLive->setName($this->name);
$ogLive->setDownloadUrl($this->downloadUrl);
$ogLive->setStatus(OgLiveStatus::INACTIVE);

View File

@ -13,6 +13,9 @@ final class MenuOutput extends AbstractOutput
#[Groups(['menu:read', 'organizational-unit:read'])]
public string $name;
#[Groups(['menu:read'])]
public ?string $title = null;
#[Groups(['menu:read'])]
public string $resolution;
@ -25,9 +28,6 @@ final class MenuOutput extends AbstractOutput
#[Groups(['menu:read'])]
public ?string $privateUrl = null;
#[Groups(['menu:read'])]
public ?bool $isDefault = false;
#[Groups(['menu:read'])]
public \DateTime $createdAt;
@ -39,11 +39,11 @@ public function __construct(Menu $menu)
parent::__construct($menu);
$this->name = $menu->getName();
$this->title = $menu->getTitle();
$this->resolution = $menu->getResolution();
$this->comments = $menu->getComments();
$this->publicUrl = $menu->getPublicUrl();
$this->privateUrl = $menu->getPrivateUrl();
$this->isDefault = $menu->isDefault();
$this->createdAt = $menu->getCreatedAt();
$this->createdBy = $menu->getCreatedBy();
}

View File

@ -10,6 +10,9 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'OgLive')]
final class OgLiveOutput extends AbstractOutput
{
#[Groups(['og-live:read', 'client:read', "organizational-unit:read"])]
public string $name;
#[Groups(['og-live:read'])]
public ?bool $synchronized = false;
@ -34,8 +37,8 @@ final class OgLiveOutput extends AbstractOutput
#[Groups(['og-live:read'])]
public ?string $revision = '';
#[Groups(['og-live:read', 'client:read', "organizational-unit:read"])]
public ?string $filename = null;
#[Groups(['og-live:read'])]
public ?string $filename = '';
#[Groups(['og-live:read'])]
public ?string $kernel = '';
@ -53,6 +56,7 @@ final class OgLiveOutput extends AbstractOutput
{
parent::__construct($ogLive);
$this->name = $ogLive->getName();
$this->synchronized = $ogLive->isSynchronized();
$this->installed = $ogLive->isInstalled();
$this->isDefault = $ogLive->getIsDefault();

View File

@ -49,7 +49,6 @@ class Client extends AbstractEntity
private Collection $partitions;
#[ORM\ManyToOne]
#[ORM\JoinColumn( onDelete: 'SET NULL')]
private ?Menu $menu = null;
#[ORM\ManyToOne]
@ -64,7 +63,7 @@ class Client extends AbstractEntity
#[ORM\ManyToOne(inversedBy: 'clients')]
private ?PxeTemplate $template = null;
#[ORM\ManyToOne()]
#[ORM\ManyToOne(inversedBy: 'clients')]
private ?ImageRepository $repository = null;
#[ORM\ManyToOne(inversedBy: 'clients')]

View File

@ -12,6 +12,9 @@ class Menu extends AbstractEntity
{
use NameableTrait;
#[ORM\Column(length: 255)]
private ?string $title = null;
#[ORM\Column(length: 255)]
private ?string $resolution = null;
@ -24,12 +27,28 @@ class Menu extends AbstractEntity
#[ORM\Column(length: 255, nullable: true)]
private ?string $privateUrl = null;
#[ORM\Column]
private ?bool $isDefault = null;
/**
* @var Collection<int, Client>
*/
#[ORM\OneToMany(mappedBy: 'menu', targetEntity: Client::class)]
private Collection $clients;
public function __construct()
{
parent::__construct();
$this->clients = new ArrayCollection();
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
public function getResolution(): ?string
@ -80,14 +99,31 @@ class Menu extends AbstractEntity
return $this;
}
public function isDefault(): ?bool
/**
* @return Collection<int, Client>
*/
public function getClients(): Collection
{
return $this->isDefault;
return $this->clients;
}
public function setIsDefault(bool $isDefault): static
public function addClient(Client $client): static
{
$this->isDefault = $isDefault;
if (!$this->clients->contains($client)) {
$this->clients->add($client);
}
return $this;
}
public function removeClient(Client $client): static
{
if ($this->clients->removeElement($client)) {
// set the owning side to null (unless already changed)
if ($client->getMenu() === $this) {
$client->setMenu(null);
}
}
return $this;
}

View File

@ -70,7 +70,6 @@ class NetworkSettings extends AbstractEntity
private ?ImageRepository $repository = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn( onDelete: 'SET NULL')]
private ?OgLive $ogLive = null;
#[ORM\Column(length: 255, nullable: true)]

View File

@ -9,10 +9,11 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
#[ORM\Entity(repositoryClass: OgLiveRepository::class)]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_FILENAME', fields: ['filename'])]
#[UniqueEntity(fields: ['filename'], message: 'validators.og_live.filename.unique')]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_NAME', fields: ['name'])]
#[UniqueEntity(fields: ['name'], message: 'validators.og_live.name.unique')]
class OgLive extends AbstractEntity
{
use NameableTrait;
use SynchronizedTrait;
#[ORM\Column(length: 255, nullable: true)]
@ -36,7 +37,7 @@ class OgLive extends AbstractEntity
#[ORM\Column(length: 255, nullable: true)]
private ?string $directory = null;
#[ORM\Column(length: 255, nullable: false)]
#[ORM\Column(length: 255, nullable: true)]
private ?string $filename = null;
#[ORM\Column(nullable: true)]

View File

@ -97,6 +97,12 @@ class OrganizationalUnit extends AbstractEntity
#[ORM\Column]
private ?bool $reserved = false;
/**
* @var Collection<int, Image>
*/
#[ORM\OneToMany(mappedBy: 'organizationalUnit', targetEntity: Image::class)]
private Collection $images;
public function __construct()
{
parent::__construct();
@ -104,6 +110,7 @@ class OrganizationalUnit extends AbstractEntity
$this->users = new ArrayCollection();
$this->clients = new ArrayCollection();
$this->softwareProfiles = new ArrayCollection();
$this->images = new ArrayCollection();
}
public function getDescription(): ?string
@ -429,4 +436,34 @@ class OrganizationalUnit extends AbstractEntity
return $this;
}
/**
* @return Collection<int, Image>
*/
public function getImages(): Collection
{
return $this->images;
}
public function addImage(Image $image): static
{
if (!$this->images->contains($image)) {
$this->images->add($image);
$image->setOrganizationalUnit($this);
}
return $this;
}
public function removeImage(Image $image): static
{
if ($this->images->removeElement($image)) {
// set the owning side to null (unless already changed)
if ($image->getOrganizationalUnit() === $this) {
$image->setOrganizationalUnit(null);
}
}
return $this;
}
}

View File

@ -27,7 +27,7 @@ final readonly class ClientSubscriber implements EventSubscriberInterface
public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => ['updatePxe', EventPriorities::POST_WRITE],
KernelEvents::VIEW => ['sendMail', EventPriorities::POST_WRITE],
];
}
@ -37,7 +37,7 @@ final readonly class ClientSubscriber implements EventSubscriberInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function updatePxe(ViewEvent $event): void
public function sendMail(ViewEvent $event): void
{
$clientOutput = $event->getControllerResult();
$method = $event->getRequest()->getMethod();

View File

@ -33,7 +33,7 @@ final class MenuFactory extends ModelFactory
'createdAt' => self::faker()->dateTime(),
'name' => self::faker()->text(255),
'resolution' => self::faker()->text(255),
'isDefault' => self::faker()->boolean(),
'title' => self::faker()->text(255),
'updatedAt' => self::faker()->dateTime(),
];
}

View File

@ -34,7 +34,8 @@ final class OgLiveFactory extends ModelFactory
{
return [
'createdAt' => self::faker()->dateTime(),
'filename' => self::faker()->text(255),
'name' => self::faker()->text(255),
'downloadUrl' => self::faker()->text(255),
'status' => OgLiveStatus::ACTIVE,
'updatedAt' => self::faker()->dateTime(),
];

View File

@ -1,26 +0,0 @@
<?php
namespace App\Formatter;
use Monolog\Formatter\LineFormatter;
class CustomLineFormatter extends LineFormatter
{
public function __construct()
{
parent::__construct(null, 'Y-m-d H:i:s', true, true);
}
public function format($record): string
{
$output = [
'severity' => $record['level_name'],
'operation' => $record['channel'],
'component' => 'ogcore',
'params' => $record['context'],
'desc' => $record['message'],
];
return json_encode($output, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL;
}
}

View File

@ -5,13 +5,8 @@ namespace App\Model;
final class DeployMethodTypes
{
public const string MULTICAST = 'multicast';
public const string MULTICAST_UFTP = 'uftp';
public const string MULTICAST_UFTP_DIRECT = 'uftp-direct';
public const string MULTICAST_UDPCAST = 'udpcast';
public const string MULTICAST_UDPCAST_DIRECT = 'udp-direct';
public const string UNICAST = 'unicast';
public const string UNICAST_DIRECT = 'unicast-direct';
public const string TORRENT = 'p2p';
public const string TORRENT = 'torrent';
private const array DEPLOYMENT_METHOD_TYPES = [
self::MULTICAST => 'Multicast',

View File

@ -4,8 +4,8 @@ namespace App\Model;
final class OrganizationalUnitMulticastModes
{
public const string HALF_DUPLEX = 'half';
public const string FULL_DUPLEX = 'full';
public const string HALF_DUPLEX = 'half-duplex';
public const string FULL_DUPLEX = 'full-duplex';
private const array MCAST_MODES = [
self::HALF_DUPLEX => 'Half Duplex',

View File

@ -15,40 +15,4 @@ class ClientRepository extends AbstractRepository
{
parent::__construct($registry, Client::class);
}
public function findClientsByOrganizationalUnitAndDescendants(int $organizationalUnitId): array
{
$query = $this->getEntityManager()->createQuery(
'SELECT o.path
FROM App\Entity\OrganizationalUnit o
WHERE o.id = :id'
)->setParameter('id', $organizationalUnitId);
$result = $query->getOneOrNullResult();
if (!$result) {
return [];
}
$path = $result['path'] . '/%';
$query = $this->getEntityManager()->createQuery(
'SELECT o.id
FROM App\Entity\OrganizationalUnit o
WHERE o.id = :id OR o.path LIKE :path'
)
->setParameter('id', $organizationalUnitId)
->setParameter('path', $path);
$unitIds = array_column($query->getArrayResult(), 'id');
$query = $this->getEntityManager()->createQuery(
'SELECT c
FROM App\Entity\Client c
WHERE c.organizationalUnit IN (:unitIds)'
)
->setParameter('unitIds', $unitIds);
return $query->getResult();
}
}

View File

@ -6,15 +6,13 @@ use App\Entity\Client;
use App\Entity\OperativeSystem;
use App\Entity\Partition;
use App\Model\PartitionTypes;
use App\Service\Utils\GetPartitionCodeService;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
class CreatePartitionService
{
public function __construct(
protected EntityManagerInterface $entityManager,
protected GetPartitionCodeService $partitionCodeService
protected EntityManagerInterface $entityManager
)
{
}
@ -56,15 +54,11 @@ class CreatePartitionService
$partitionEntity->setSize($cfg['tam']);
if (isset($cfg['cpt']) && $cfg['cpt'] !== '') {
if ($cfg['par'] === "0") {
$partitionType = $this->partitionCodeService->__invoke($cfg['cpt']);
} else {
$decimalValue = hexdec($cfg['cpt']);
$partitionType = PartitionTypes::getPartitionType($decimalValue);
}
$decimalValue = hexdec($cfg['cpt']);
$partitionType = PartitionTypes::getPartitionType($decimalValue);
if ($partitionType) {
$partitionEntity->setPartitionCode($partitionType['name'] ?? $partitionType);
$partitionEntity->setPartitionCode($partitionType['name']);
}
} else {
$partitionEntity->setPartitionCode(PartitionTypes::getPartitionType(0)['name']);

View File

@ -1,17 +0,0 @@
<?php
namespace App\Service\Utils;
class GetPartitionCodeService
{
public function __invoke(string $partitionCode): string
{
return match ($partitionCode) {
"1" => "MSDOS",
"2" => "GPT",
"3" => "LVM",
"4" => "ZPOOL",
default => "UNKNOWN",
};
}
}

View File

@ -13,14 +13,12 @@ use App\Dto\Input\ClientInput;
use App\Dto\Output\ClientOutput;
use App\Dto\Output\UserGroupOutput;
use App\Repository\ClientRepository;
use App\Repository\MenuRepository;
readonly class ClientProcessor implements ProcessorInterface
class ClientProcessor implements ProcessorInterface
{
public function __construct(
private ClientRepository $clientRepository,
private MenuRepository $menuRepository,
private ValidatorInterface $validator
private readonly ClientRepository $clientRepository,
private readonly ValidatorInterface $validator
)
{
}
@ -54,18 +52,11 @@ readonly class ClientProcessor implements ProcessorInterface
$entity = $this->clientRepository->findOneByUuid($uriVariables['uuid']);
}
$defaultMenu = $this->menuRepository->findOneBy(['isDefault' => true]);
$userGroup = $data->createOrUpdateEntity($entity);
$this->validator->validate($userGroup);
$this->clientRepository->save($userGroup);
$client = $data->createOrUpdateEntity($entity);
if ($defaultMenu) {
$client->setMenu($defaultMenu);
}
$this->validator->validate($client);
$this->clientRepository->save($client);
return new ClientOutput($client);
return new ClientOutput($userGroup);
}
private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null

View File

@ -11,13 +11,11 @@ use ApiPlatform\State\Pagination\TraversablePaginator;
use ApiPlatform\State\ProviderInterface;
use App\Dto\Input\ClientInput;
use App\Dto\Output\ClientOutput;
use App\Repository\ClientRepository;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
readonly class ClientProvider implements ProviderInterface
{
public function __construct(
private ClientRepository $clientRepository,
private ProviderInterface $collectionProvider,
private ProviderInterface $itemProvider
)
@ -37,25 +35,18 @@ readonly class ClientProvider implements ProviderInterface
}
}
public function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object
private function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object
{
$organizationalUnitId = $context['filters']['organizationalUnit.id'] ?? null;
$paginator = $this->collectionProvider->provide($operation, $uriVariables, $context);
if ($organizationalUnitId) {
$clients = $this->clientRepository->findClientsByOrganizationalUnitAndDescendants((int) $organizationalUnitId);
$items = new \ArrayObject();
foreach ($clients as $client) {
$items[] = new ClientOutput($client);
}
return new TraversablePaginator($items, 1, count($clients), count($clients));
$items = new \ArrayObject();
foreach ($paginator->getIterator() as $item){
$items[] = new ClientOutput($item);
}
return $this->collectionProvider->provide($operation, $uriVariables, $context);
return new TraversablePaginator($items, $paginator->getCurrentPage(), $paginator->getItemsPerPage(), $paginator->getTotalItems());
}
public function provideItem(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$item = $this->itemProvider->provide($operation, $uriVariables, $context);

View File

@ -1,6 +1,6 @@
{
"api-platform/core": {
"version": "3.4",
"version": "3.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
@ -13,8 +13,17 @@
"src/ApiResource/.gitignore"
]
},
"dama/doctrine-test-bundle": {
"version": "8.2",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "7.2",
"ref": "896306d79d4ee143af9eadf9b09fd34a8c391b70"
}
},
"doctrine/doctrine-bundle": {
"version": "2.13",
"version": "2.12",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
@ -27,6 +36,18 @@
"src/Repository/.gitignore"
]
},
"doctrine/doctrine-fixtures-bundle": {
"version": "3.6",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.0",
"ref": "1f5514cfa15b947298df4d771e694e578d4c204d"
},
"files": [
"src/DataFixtures/AppFixtures.php"
]
},
"doctrine/doctrine-migrations-bundle": {
"version": "3.3",
"recipe": {
@ -41,21 +62,16 @@
]
},
"gesdinet/jwt-refresh-token-bundle": {
"version": "1.4",
"version": "1.3",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "1.0",
"ref": "2390b4ed5c195e0b3f6dea45221f3b7c0af523a0"
},
"files": [
"config/packages/gesdinet_jwt_refresh_token.yaml",
"config/routes/gesdinet_jwt_refresh_token.yaml",
"src/Entity/RefreshToken.php"
]
}
},
"lexik/jwt-authentication-bundle": {
"version": "3.1",
"version": "3.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
@ -67,7 +83,7 @@
]
},
"nelmio/cors-bundle": {
"version": "2.5",
"version": "2.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
@ -78,6 +94,20 @@
"config/packages/nelmio_cors.yaml"
]
},
"phpunit/phpunit": {
"version": "9.6",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "9.6",
"ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
},
"files": [
".env.test",
"phpunit.xml.dist",
"tests/bootstrap.php"
]
},
"ramsey/uuid-doctrine": {
"version": "2.1",
"recipe": {
@ -88,16 +118,13 @@
}
},
"stof/doctrine-extensions-bundle": {
"version": "1.12",
"version": "1.11",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "1.2",
"ref": "e805aba9eff5372e2d149a9ff56566769e22819d"
},
"files": [
"config/packages/stof_doctrine_extensions.yaml"
]
}
},
"symfony/console": {
"version": "6.4",
@ -116,12 +143,11 @@
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.4",
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
"version": "1.0",
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
},
"files": [
".env",
".env.dev"
".env"
]
},
"symfony/framework-bundle": {
@ -130,7 +156,7 @@
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "32126346f25e1cee607cc4aa6783d46034920554"
"ref": "a91c965766ad3ff2ae15981801643330eb42b6a5"
},
"files": [
"config/packages/cache.yaml",
@ -143,6 +169,15 @@
"src/Kernel.php"
]
},
"symfony/maker-bundle": {
"version": "1.60",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/monolog-bundle": {
"version": "3.10",
"recipe": {
@ -155,6 +190,21 @@
"config/packages/monolog.yaml"
]
},
"symfony/phpunit-bridge": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.3",
"ref": "a411a0480041243d97382cac7984f7dce7813c08"
},
"files": [
".env.test",
"bin/phpunit",
"phpunit.xml.dist",
"tests/bootstrap.php"
]
},
"symfony/routing": {
"version": "6.4",
"recipe": {
@ -218,5 +268,30 @@
"files": [
"config/packages/validator.yaml"
]
},
"symfony/web-profiler-bundle": {
"version": "6.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.1",
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
},
"files": [
"config/packages/web_profiler.yaml",
"config/routes/web_profiler.yaml"
]
},
"zenstruck/foundry": {
"version": "1.38",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.10",
"ref": "37c2f894cc098ab4c08874b80cccc8e2f8de7976"
},
"files": [
"config/packages/zenstruck_foundry.yaml"
]
}
}

View File

@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Menú de Opciones</title>
<style>
.navbar {
color: white;
padding: 15px 30px;
text-align: center;
top: 0;
width: 100%;
z-index: 1000;
}
.navbar img {
width: auto;
position: absolute; /* Logo en la izquierda */
left: 30px; /* Ajuste de la distancia desde la izquierda */
}
.navbar h1 {
margin: 0;
font-size: 1.8em;
}
.navbar a {
color: white;
text-decoration: none;
padding: 5px 10px;
margin-left: 10px;
}
.container {
margin: 80px auto 0; /* Deja espacio para la barra de navegación */
width: 90%;
max-width: 1000px;
text-align: center; /* Centra los elementos en navegadores básicos */
}
.menu-container {
display: block;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
width: 100%;
margin-top: 20px;
}
.menu-item {
background-color: #f0f8ff; /* Fondo suave */
padding: 15px;
margin: 15px 0; /* Espaciado entre cajas */
border: 1px solid #ddd;
border-radius: 5px;
display: inline-block;
width: 45%; /* Dos elementos por fila */
box-sizing: border-box;
}
.menu-item a {
font-size: 1.2em;
color: #007bff;
text-decoration: none;
font-weight: bold;
}
.menu-item p {
font-size: 0.9em;
color: #555;
}
@media (max-width: 768px) {
.menu-item {
width: 100%; /* En pantallas pequeñas, las cajas ocupan todo el ancho */
}
}
.windows {
background-color: #e0f7fa; /* Azul suave para Windows */
}
.linux {
background-color: #c8e6c9; /* Verde suave para Linux */
}
.apagar {
background-color: #ffccbc; /* Naranja suave para Apagar */
}
.partition-container {
margin-top: 20px;
padding: 20px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
}
.partition-header {
font-size: 1.5em;
margin-bottom: 15px;
color: #333;
}
.partition-item {
display: flex;
flex-wrap: wrap; /* Permite adaptarse a pantallas pequeñas */
align-items: center;
text-align: left; /* Asegura que el contenido está alineado a la izquierda */
padding: 10px;
margin-bottom: 10px;
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.partition-item span {
margin-right: 15px;
font-size: 1em;
color: #555;
}
.partition-link {
font-size: 0.9em;
color: #007bff;
text-decoration: none;
font-weight: bold;
}
.partition-link:hover {
text-decoration: underline;
color: #0056b3;
}
@media (max-width: 768px) {
.partition-item {
flex-direction: column;
}
.partition-item span {
margin-bottom: 5px;
}
.partition-link {
margin-top: 10px;
}
}
</style>
</head>
<body>
<!-- Barra de navegación superior -->
<div class="navbar">
<img src="{{ asset('images/img.png') }}" alt="Logo">
</div>
<div class="container">
<div class="menu-container">
<h1>Bienvenido {{ ip }}</h1>
<div class="menu-item windows">
<a href="command+confirm:restoreImage REPO windows 1 1">Instalar Windows</a>
<p>El proceso de instalación tardará unos minutos.</p>
</div>
<div class="menu-item linux">
<a href="command+output+confirm:restoreImage REPO linux 1 2">Instalar GNU/Linux</a>
<p>El proceso de instalación tardará unos minutos.</p>
</div>
<div class="menu-item apagar">
<a href="command:poweroff">Apagar</a>
<p>Apagar el ordenador.</p>
</div>
<div class="menu-item apagar">
<a href="command:reboot">Reiniciar</a>
<p>Reiniciar el ordenador.</p>
</div>
</div>
<div class="partition-container">
<h2 class="partition-header">Particiones del sistema</h2>
{% if partitions|length > 0 %}
{% for partition in partitions %}
<div class="partition-item">
<span><strong>Disco:</strong> {{ partition.diskNumber }} </span>
<span><strong>Partición:</strong> {{ partition.partitionNumber }}</span>
<span>Tamaño: {{ (partition.size / 1024)|number_format(2) }} MB</span>
<span>Tipo: {{ partition.filesystem }}</span>
<span><strong>SO:</strong> {{ partition.operativeSystem ? partition.operativeSystem.name : '-' }}</span>
{% if partition.operativeSystem %}
<a href="command+output:bootOs {{ partition.diskNumber }} {{ partition.partitionNumber }}" class="partition-link">Arrancar {{ partition.operativeSystem.name }}</a>
{% endif %}
</div>
{% endfor %}
{% else %}
<p>No hay particiones disponibles.</p>
{% endif %}
</div>
</div>
</body>
</html>

View File

@ -65,6 +65,7 @@ class MenuTest extends AbstractTest
$this->createClientWithCredentials()->request('POST', '/menus',['json' => [
'name' => self::MENU_CREATE,
'title' => self::MENU_CREATE,
'resolution' => "1920x1080",
]]);
@ -74,6 +75,7 @@ class MenuTest extends AbstractTest
'@context' => '/contexts/MenuOutput',
'@type' => 'Menu',
'name' => self::MENU_CREATE,
'title' => self::MENU_CREATE,
]);
}
@ -88,7 +90,7 @@ class MenuTest extends AbstractTest
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
MenuFactory::createOne(['name' => self::MENU_UPDATE, 'resolution' => "1920x1080"]);
MenuFactory::createOne(['name' => self::MENU_UPDATE, 'title' => self::MENU_UPDATE, 'resolution' => "1920x1080"]);
$iri = $this->findIriBy(Menu::class, ['name' => self::MENU_UPDATE]);
$this->createClientWithCredentials()->request('PUT', $iri, ['json' => [
@ -115,7 +117,7 @@ class MenuTest extends AbstractTest
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
MenuFactory::createOne(['name' => self::MENU_DELETE, 'resolution' => "1920x1080"]);
MenuFactory::createOne(['name' => self::MENU_DELETE, 'title' => self::MENU_DELETE, 'resolution' => "1920x1080"]);
$iri = $this->findIriBy(Menu::class, ['name' => self::MENU_DELETE]);
$this->createClientWithCredentials()->request('DELETE', $iri);

View File

@ -61,8 +61,8 @@ class OgLiveTest extends AbstractTest
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
$this->createClientWithCredentials()->request('POST', '/og-lives',['json' => [
'filename' => self::OGLIVE_CREATE,
'downloadUrl' => self::OGLIVE_CREATE
'name' => self::OGLIVE_CREATE,
'downloadUrl' => 'http://example.com',
]]);
$this->assertResponseStatusCodeSame(201);
@ -70,7 +70,8 @@ class OgLiveTest extends AbstractTest
$this->assertJsonContains([
'@context' => '/contexts/OgLiveOutput',
'@type' => 'OgLive',
'filename' => self::OGLIVE_CREATE,
'name' => self::OGLIVE_CREATE,
'downloadUrl' => 'http://example.com',
'status' => OgLiveStatus::INACTIVE
]);
}
@ -86,17 +87,19 @@ class OgLiveTest extends AbstractTest
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
OgLiveFactory::createOne(['filename' => self::OGLIVE_CREATE, 'downloadUrl' => self::OGLIVE_UPDATE]);
$iri = $this->findIriBy(OgLive::class, ['filename' => self::OGLIVE_CREATE]);
OgLiveFactory::createOne(['name' => self::OGLIVE_CREATE, 'downloadUrl' => 'http://example.com']);
$iri = $this->findIriBy(OgLive::class, ['name' => self::OGLIVE_CREATE]);
$this->createClientWithCredentials()->request('PUT', $iri, ['json' => [
'filename' => self::OGLIVE_UPDATE,
'name' => self::OGLIVE_UPDATE,
'downloadUrl' => 'http://example-2.com',
]]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains([
'@id' => $iri,
'filename' => self::OGLIVE_UPDATE,
'name' => self::OGLIVE_UPDATE,
'downloadUrl' => 'http://example-2.com',
]);
}
@ -111,13 +114,13 @@ class OgLiveTest extends AbstractTest
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
OgLiveFactory::createOne(['filename' => self::OGLIVE_CREATE, 'downloadUrl' => 'http://example.com']);
$iri = $this->findIriBy(OgLive::class, ['filename' => self::OGLIVE_CREATE]);
OgLiveFactory::createOne(['name' => self::OGLIVE_CREATE, 'downloadUrl' => 'http://example.com']);
$iri = $this->findIriBy(OgLive::class, ['name' => self::OGLIVE_CREATE]);
$this->createClientWithCredentials()->request('DELETE', $iri);
$this->assertResponseStatusCodeSame(204);
$this->assertNull(
static::getContainer()->get('doctrine')->getRepository(OgLive::class)->findOneBy(['filename' => self::OGLIVE_CREATE])
static::getContainer()->get('doctrine')->getRepository(OgLive::class)->findOneBy(['name' => self::OGLIVE_CREATE])
);
}
}

View File

@ -52,7 +52,7 @@ validators:
not_blank: 'The name should not be blank.'
og_live:
filename:
name:
not_blank: 'The name should not be blank.'
unique: 'The name should be unique.'

View File

@ -39,11 +39,6 @@ validators:
not_blank: 'El nombre no debería estar vacío.'
unique: 'El nombre debería ser único. Ya existe una imagen con ese nombre.'
og_live:
filename:
not_blank: 'El nombre no debería estar vacío.'
unique: 'El nombre debería ser único. Ya existe un archivo con ese nombre.'
network_settings:
ip_address:
invalid: 'La dirección IP "{{ value }}" no es válida.'