develop #21
6
.env
6
.env
|
@ -50,3 +50,9 @@ UDS_AUTH_USERNAME="natiqindel"
|
|||
UDS_AUTH_PASSWORD="correct horse battery staple"
|
||||
UDS_URL=https://localhost:8087/uds/rest/
|
||||
###< UDS ###
|
||||
|
||||
###> symfony/mercure-bundle ###
|
||||
MERCURE_URL=http://ogcore-mercure:3000/.well-known/mercure
|
||||
MERCURE_PUBLIC_URL=http://ogcore-mercure:3000/.well-known/mercure
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
###< symfony/mercure-bundle ###
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=prod
|
||||
APP_SECRET=e95c7f17da15ce1b03d77ad655379c34
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
DATABASE_URL="mysql://root:root@127.0.0.1:3306/ogcore?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
OG_1_DATABASE_URL="mysql://root:root@127.0.0.1:3306/ogcore_old_og?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='*'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
||||
###> lexik/jwt-authentication-bundle ###
|
||||
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
|
||||
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
||||
JWT_PASSPHRASE=8b9154df37ffa91ef9186ce095324e39e50ff3b023bb1ed34383abd019ba4515
|
||||
###< lexik/jwt-authentication-bundle ###
|
||||
|
||||
###> symfony/mercure-bundle ###
|
||||
MERCURE_URL=http://localhost:3000/.well-known/mercure
|
||||
MERCURE_PUBLIC_URL=http://localhost:3000/.well-known/mercure
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
###< symfony/mercure-bundle ###
|
38
CHANGELOG.md
38
CHANGELOG.md
|
@ -1,4 +1,38 @@
|
|||
# Changelog
|
||||
## [0.9.0] - 2025-03-04
|
||||
### 🔹 Added
|
||||
- Nueva funcionalidad para tener notificaciones en tiempo real. Instalación de bundle "Mercure".
|
||||
- Creacion de EventListener en Symfony, para publicar mensajes en Mercure, cuando se realicen cambios en la base de datos ( cambio de estado en lo equipos, y trazas).
|
||||
- Nuevo endpoint "backup image". Integracion con ogRepository.
|
||||
- Nuevo campo en "usuarios" el cual permite escoger la visualizacion por defecto de la vista "grupos".
|
||||
- Nuevo campo "dns" en "subredes" para gestionar los servidores DNS.
|
||||
- Integracion endpoint ogRepository para checkear la integridad de una imagen.
|
||||
- Nueva funcionalidad para cancelar despliegues de imagenes.
|
||||
- Añadido nuevo campo "cancelado" en trazas.
|
||||
|
||||
### ⚡ Changed
|
||||
- Cambios en logs. Cambios en salida (stderror -> file.log)
|
||||
- Modulo DHCP. Añadir equipos, ahora se gestiona con una unica llamada a la API.
|
||||
- Acciones masivas en equipos. Se ha cambiado la respuesta para que no fallen las peticiones si uno o mas equipos no da respuesta.
|
||||
|
||||
---
|
||||
## [0.8.1] - 2025-02-25
|
||||
### 🐛 Fixed
|
||||
- Corrección de bug en el deploy de imágenes
|
||||
|
||||
---
|
||||
|
||||
## [0.8.0] - 2025-01-10
|
||||
### 🔹 Added
|
||||
- Nuevos campos en "aulas" para la jerarquia en clientes.
|
||||
- Nueva funcionalidad "imagen global". Integracion con ogRepository.
|
||||
|
||||
### ⚡ Changed
|
||||
- Limpieza en campos "name" y "date" de ogLive. Es necesario parsear el campo "filename" para facilitar el uso al usuario en la web.
|
||||
### 🐛 Fixed
|
||||
- Corrección de bug que impedia borrar un cliente si tenia una traza enlazada.
|
||||
---
|
||||
|
||||
|
||||
## [0.7.3] - 2025-01-03
|
||||
### 🔹 Added
|
||||
|
@ -7,13 +41,9 @@
|
|||
- Se agregó la funcionalidad de borrar imágenes. Integración con ogRepository.
|
||||
- Se agregó el modo "TORRENT" y "UDPCAST" en el despliegue de imágenes.
|
||||
|
||||
### 🛠️ Fixed
|
||||
|
||||
### ⚡ Changed
|
||||
- Refactorización del webhook de ogRepository.
|
||||
|
||||
### 🛑 Removed
|
||||
|
||||
---
|
||||
|
||||
## Formato de cambios:
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
ogcore (1.0) unstable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Your Name <your.email@example.com> Thu, 01 Jan 1970 00:00:00 +0000
|
|
@ -0,0 +1,12 @@
|
|||
Package: ogcore
|
||||
Version: %%VERSION%%
|
||||
Section: base
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: mariadb-server, systemd, nginx, libzip-dev, zip, unzip, php8.3-opcache,
|
||||
php8.3-bcmath, php8.3-cli, php8.3-curl, php8.3-fpm, php8.3-gd,
|
||||
php8.3-ldap, php8.3-mbstring, php8.3-mysql, php8.3-common,
|
||||
php8.3-xml, php8.3-zip
|
||||
Maintainer: Nicolas Arenas <nicolas.arenas@qindel.com>
|
||||
Description: Description of the ogcore package
|
||||
This is a longer description of the ogcore package.
|
|
@ -0,0 +1,21 @@
|
|||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: ogcore
|
||||
Source: <source URL>
|
||||
|
||||
Files: *
|
||||
Copyright: 2023 Your Name <your.email@example.com>
|
||||
License: GPL-3+
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this package; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301 USA.
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Asegurarse de que el usuario exista
|
||||
USER="opengnsys"
|
||||
|
||||
|
||||
# Provisionar base de datos si es necesario en caso de instalación.
|
||||
|
||||
|
||||
# Detectar si es una instalación nueva o una actualización
|
||||
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
||||
cd /opt/opengnsys/ogcore/api
|
||||
mariadb -e "GRANT ALL ON *.* to 'root'@'localhost' IDENTIFIED BY 'root' WITH GRANT OPTION;"
|
||||
echo "Primera instalación"
|
||||
# Provisionar base de datos si es necesario en caso de instalación.
|
||||
echo "Creando par de claves para JWT"
|
||||
php bin/console lexik:jwt:generate-keypair --overwrite
|
||||
echo "Creando base de datos y cargando datos iniciales"
|
||||
php bin/console doctrine:database:create --if-not-exists
|
||||
php bin/console doctrine:migrations:migrate --no-interaction
|
||||
# Cargar datos iniciales
|
||||
echo "Cargando datos iniciales"
|
||||
php bin/console opengnsys:load-default-user
|
||||
|
||||
echo "Cargando usuarios y grupos por defecto"
|
||||
php bin/console app:load-default-user-groups
|
||||
echo "Cargando comandos por defecto"
|
||||
php bin/console app:load-default-commands
|
||||
echo "Cargando menú por defecto"
|
||||
php bin/console opengnsys:load-default-menu
|
||||
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
|
||||
cd /opt/opengnsys/ogcore/api
|
||||
echo "Actualización desde la versión $2"
|
||||
# Ejecutar comandos específicos para la actualización
|
||||
php bin/console doctrine:migrations:migrate --no-interaction
|
||||
# Otros comandos específicos para la actualización
|
||||
fi
|
||||
|
||||
# Cambiar la propiedad de los archivos al usuario especificado
|
||||
chown opengnsys:www-data /opt/opengnsys/
|
||||
chown -R opengnsys:www-data /opt/opengnsys/ogcore
|
||||
# Install http server stuff
|
||||
ln -s /opt/opengnsys/ogcore/etc/nginx/sites-available/ogcore.conf /etc/nginx/sites-enabled/ogcore.conf
|
||||
ln -s /opt/opengnsys/ogcore/etc/php/8.3/fpm/pool.d/ogcore-fpm.conf /etc/php/8.3/fpm/pool.d/ogcore-fpm.conf
|
||||
# Reiniciar servicios si es necesario
|
||||
# systemctl restart nombre_del_servicio
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl restart nginx
|
||||
systemctl restart php8.3-fpm
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Asegurarse de que el usuario exista
|
||||
USER="opengnsys"
|
||||
HOME_DIR="/opt/opengnsys"
|
||||
if id "$USER" &>/dev/null; then
|
||||
echo "El usuario $USER ya existe."
|
||||
else
|
||||
echo "Creando el usuario $USER con home en $HOME_DIR."
|
||||
useradd -m -d "$HOME_DIR" -s /bin/bash "$USER"
|
||||
fi
|
||||
|
||||
exit 0
|
|
@ -5,3 +5,9 @@ services:
|
|||
ports:
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/mercure-bundle ###
|
||||
mercure:
|
||||
ports:
|
||||
- "80"
|
||||
###< symfony/mercure-bundle ###
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "6.4.*",
|
||||
"symfony/http-client": "6.4.*",
|
||||
"symfony/mercure-bundle": "^0.3.9",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/property-access": "6.4.*",
|
||||
"symfony/property-info": "6.4.*",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "2df97d4a1797242acadb47d83b3fbe98",
|
||||
"content-hash": "ac4764e765324b86e616d7eb47a55e63",
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/core",
|
||||
|
@ -4981,6 +4981,173 @@
|
|||
],
|
||||
"time": "2024-11-27T12:49:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mercure",
|
||||
"version": "v0.6.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mercure.git",
|
||||
"reference": "304cf84609ef645d63adc65fc6250292909a461b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mercure/zipball/304cf84609ef645d63adc65fc6250292909a461b",
|
||||
"reference": "304cf84609ef645d63adc65fc6250292909a461b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"symfony/deprecation-contracts": "^2.0|^3.0|^4.0",
|
||||
"symfony/http-client": "^4.4|^5.0|^6.0|^7.0",
|
||||
"symfony/http-foundation": "^4.4|^5.0|^6.0|^7.0",
|
||||
"symfony/polyfill-php80": "^1.22",
|
||||
"symfony/web-link": "^4.4|^5.0|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"lcobucci/jwt": "^3.4|^4.0|^5.0",
|
||||
"symfony/event-dispatcher": "^4.4|^5.0|^6.0|^7.0",
|
||||
"symfony/http-kernel": "^4.4|^5.0|^6.0|^7.0",
|
||||
"symfony/phpunit-bridge": "^5.2|^6.0|^7.0",
|
||||
"symfony/stopwatch": "^4.4|^5.0|^6.0|^7.0",
|
||||
"twig/twig": "^2.0|^3.0|^4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/stopwatch": "Integration with the profiler performances"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/dunglas/mercure",
|
||||
"name": "dunglas/mercure"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "0.6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Mercure\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kévin Dunglas",
|
||||
"email": "dunglas@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Mercure Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"mercure",
|
||||
"push",
|
||||
"sse",
|
||||
"updates"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony/mercure/issues",
|
||||
"source": "https://github.com/symfony/mercure/tree/v0.6.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/dunglas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/mercure",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-04-08T12:51:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mercure-bundle",
|
||||
"version": "v0.3.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mercure-bundle.git",
|
||||
"reference": "77435d740b228e9f5f3f065b6db564f85f2cdb64"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mercure-bundle/zipball/77435d740b228e9f5f3f065b6db564f85f2cdb64",
|
||||
"reference": "77435d740b228e9f5f3f065b6db564f85f2cdb64",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"lcobucci/jwt": "^3.4|^4.0|^5.0",
|
||||
"php": ">=7.1.3",
|
||||
"symfony/config": "^4.4|^5.0|^6.0|^7.0",
|
||||
"symfony/dependency-injection": "^4.4|^5.4|^6.0|^7.0",
|
||||
"symfony/http-kernel": "^4.4|^5.0|^6.0|^7.0",
|
||||
"symfony/mercure": "^0.6.1",
|
||||
"symfony/web-link": "^4.4|^5.0|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^4.3.7|^5.0|^6.0|^7.0",
|
||||
"symfony/stopwatch": "^4.3.7|^5.0|^6.0|^7.0",
|
||||
"symfony/ux-turbo": "*",
|
||||
"symfony/var-dumper": "^4.3.7|^5.0|^6.0|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/messenger": "To use the Messenger integration"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "0.3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Bundle\\MercureBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kévin Dunglas",
|
||||
"email": "dunglas@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony MercureBundle",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"mercure",
|
||||
"push",
|
||||
"sse",
|
||||
"updates"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony/mercure-bundle/issues",
|
||||
"source": "https://github.com/symfony/mercure-bundle/tree/v0.3.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/dunglas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/mercure-bundle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T09:07:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/monolog-bridge",
|
||||
"version": "v6.4.13",
|
||||
|
|
|
@ -7,6 +7,7 @@ resources:
|
|||
groups: ['default', 'client:read']
|
||||
denormalizationContext:
|
||||
groups: ['client:write']
|
||||
|
||||
operations:
|
||||
ApiPlatform\Metadata\GetCollection:
|
||||
provider: App\State\Provider\ClientProvider
|
||||
|
|
|
@ -48,6 +48,14 @@ resources:
|
|||
uriTemplate: /image-image-repositories/{uuid}/deploy-image
|
||||
controller: App\Controller\DeployImageAction
|
||||
|
||||
backup_image_ogrepository:
|
||||
shortName: OgRepository Server
|
||||
class: ApiPlatform\Metadata\Post
|
||||
method: POST
|
||||
input: App\Dto\Input\BackupImageInput
|
||||
uriTemplate: /image-image-repositories/{uuid}/backup-image
|
||||
controller: App\Controller\OgRepository\Image\BackupImageAction
|
||||
|
||||
trash_delete_image_ogrepository:
|
||||
shortName: OgRepository Server
|
||||
description: Delete Image in OgRepository
|
||||
|
@ -84,6 +92,15 @@ resources:
|
|||
uriTemplate: /image-image-repositories/{uuid}/transfer-image
|
||||
controller: App\Controller\OgRepository\Image\TransferAction
|
||||
|
||||
get_status_image_ogrepository:
|
||||
shortName: OgRepository Server
|
||||
description: Get Status Image in OgRepository
|
||||
class: ApiPlatform\Metadata\Post
|
||||
method: POST
|
||||
input: false
|
||||
uriTemplate: /image-image-repositories/server/{uuid}/status
|
||||
controller: App\Controller\OgRepository\Image\GetStatusAction
|
||||
|
||||
properties:
|
||||
App\Entity\ImageImageRepository:
|
||||
id:
|
||||
|
|
|
@ -12,6 +12,15 @@ resources:
|
|||
ApiPlatform\Metadata\Get:
|
||||
provider: App\State\Provider\TraceProvider
|
||||
|
||||
cancel_trace:
|
||||
shortName: OgRepository Server
|
||||
description: Cancel Trace in OgRepository
|
||||
class: ApiPlatform\Metadata\Post
|
||||
method: POST
|
||||
input: false
|
||||
uriTemplate: /traces/server/{uuid}/cancel
|
||||
controller: App\Controller\OgRepository\Image\CancelTransmissionAction
|
||||
|
||||
order:
|
||||
createdAt: DESC
|
||||
|
||||
|
|
|
@ -17,4 +17,5 @@ return [
|
|||
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
|
||||
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -5,6 +5,9 @@ api_platform:
|
|||
path_segment_name_generator: api_platform.path_segment_name_generator.dash
|
||||
defaults:
|
||||
pagination_client_items_per_page: true
|
||||
mercure:
|
||||
enabled: true
|
||||
|
||||
collection:
|
||||
pagination:
|
||||
items_per_page_parameter_name: 'itemsPerPage'
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
mercure:
|
||||
hubs:
|
||||
default:
|
||||
url: '%env(MERCURE_URL)%'
|
||||
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
||||
jwt:
|
||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
||||
publish: '*'
|
|
@ -8,17 +8,9 @@ when@dev:
|
|||
main:
|
||||
type: stream
|
||||
level: info
|
||||
path: php://stderr
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
formatter: App\Formatter\CustomLineFormatter
|
||||
channels: ["!event"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
|
@ -28,11 +20,10 @@ when@test:
|
|||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
channels: ["!event"]
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: error
|
||||
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
|
@ -42,16 +33,11 @@ when@prod:
|
|||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||
nested:
|
||||
type: stream
|
||||
path: php://stderr
|
||||
level: debug
|
||||
formatter: monolog.formatter.json
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: error
|
||||
formatter: App\Formatter\CustomLineFormatter
|
||||
channels: ["!event"]
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
|
@ -59,5 +45,6 @@ when@prod:
|
|||
deprecation:
|
||||
type: stream
|
||||
channels: [deprecation]
|
||||
path: php://stderr
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
formatter: monolog.formatter.json
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ services:
|
|||
|
||||
api_platform.filter.client.search:
|
||||
parent: 'api_platform.doctrine.orm.search_filter'
|
||||
arguments: [ { 'id': 'exact', 'name': 'partial', 'serialNumber': 'exact', 'template.id': 'exact', organizationalUnit.id: 'exact', mac: 'exact', ip: 'exact' } ]
|
||||
arguments: [ { 'id': 'exact', 'uuid': exact, 'name': 'partial', 'serialNumber': 'exact', 'template.id': 'exact', status: 'exact', organizationalUnit.id: 'exact', mac: 'exact', ip: 'exact', subnet.id: 'exact' } ]
|
||||
tags: [ 'api_platform.filter' ]
|
||||
|
||||
api_platform.filter.client.exist:
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
services:
|
||||
App\EventListener\ClientStatusNotifier:
|
||||
tags: ~ # Esto elimina el listener en el entorno de test
|
|
@ -42,7 +42,31 @@ services:
|
|||
networks:
|
||||
- ogcore-network
|
||||
|
||||
|
||||
mercure:
|
||||
image: dunglas/mercure
|
||||
restart: unless-stopped
|
||||
container_name: ogcore-mercure
|
||||
environment:
|
||||
# Uncomment the following line to disable HTTPS,
|
||||
SERVER_NAME: ':3000'
|
||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
MERCURE_EXTRA_DIRECTIVES: |
|
||||
cors_origins *
|
||||
# Comment the following line to disable the development mode
|
||||
command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- mercure_data:/data
|
||||
- mercure_config:/config
|
||||
networks:
|
||||
- ogcore-network
|
||||
|
||||
volumes:
|
||||
mercure_data:
|
||||
mercure_config:
|
||||
database_data:
|
||||
|
||||
networks:
|
||||
|
|
|
@ -15,6 +15,17 @@ server {
|
|||
ssl_certificate /etc/nginx/certs/ogcore.uds-test.net.crt.pem;
|
||||
ssl_certificate_key /etc/nginx/certs/ogcore.uds-test.net.key.pem;
|
||||
|
||||
location /.well-known/mercure {
|
||||
proxy_pass https://mercure:3000/.well-known/mercure;
|
||||
proxy_read_timeout 24h;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /opengnsys/rest/ous// {
|
||||
rewrite ^/opengnsys/rest/ous//([0-9]+)/images /opengnsys/rest/ous/$1/images;
|
||||
rewrite ^/opengnsys/rest/ous//([0-9]+)/labs /opengnsys/rest/ous/$1/labs;
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
server {
|
||||
listen 8443 ssl;
|
||||
server_name _;
|
||||
root /opt/opengnsys/ogcore/api/public/;
|
||||
index index.html index.php;
|
||||
|
||||
ssl_certificate /opt/opengnsys/ogcore/etc/nginx/certs/ogcore.uds-test.net.crt.pem;
|
||||
ssl_certificate_key /opt/opengnsys/ogcore/etc/nginx/certs/ogcore.uds-test.net.key.pem;
|
||||
|
||||
location /opengnsys/rest/ous// {
|
||||
rewrite ^/opengnsys/rest/ous//([0-9]+)/images /opengnsys/rest/ous/$1/images;
|
||||
rewrite ^/opengnsys/rest/ous//([0-9]+)/labs /opengnsys/rest/ous/$1/labs;
|
||||
}
|
||||
|
||||
# Bloque principal para archivos
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$args;
|
||||
}
|
||||
|
||||
# Manejo de PHP
|
||||
location ~ \.php$ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/var/run/php/php8.3-fpm-ogcore.sock;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.*)$;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $request_uri;
|
||||
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
|
||||
}
|
||||
|
||||
# Bloque para errores PHP
|
||||
location ~ \.php$ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/ogcore-error.log;
|
||||
access_log /var/log/nginx/ogcore-access.log;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
[ogcore]
|
||||
user = opengnsys
|
||||
group = www-data
|
||||
listen = /run/php/php8.3-fpm-ogcore.sock
|
||||
listen.owner = opengnsys
|
||||
listen.group = www-data
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250225081416 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 ADD netiface 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 network_settings DROP netiface');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250227095120 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 user ADD groups_view 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 user DROP groups_view');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250227154452 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 subnet ADD dns 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 subnet DROP dns');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250304115209 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 user CHANGE groups_view groups_view 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 user CHANGE groups_view groups_view VARCHAR(255) NOT NULL');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/bash
|
||||
set -x
|
||||
set -e
|
||||
VERSION=$1
|
||||
DISTDIR=ogcore-$VERSION/opt/opengnsys/ogcore
|
||||
export COMPOSER_ALLOW_SUPERUSER=1
|
||||
export APP_ENV=prod
|
||||
rm -rf ogcore-$VERSION
|
||||
|
||||
cd ogcore
|
||||
composer dump-env prod
|
||||
composer install --no-dev --no-interaction --no-progress --optimize-autoloader
|
||||
cd ..
|
||||
|
||||
# Crear directorios necesarios
|
||||
mkdir -p $DISTDIR/api
|
||||
mkdir -p $DISTDIR/etc/nginx/certs
|
||||
mkdir -p $DISTDIR/etc/nginx/conf
|
||||
|
||||
for i in bin config migrations public src swagger-assets templates translations var vendor ; do
|
||||
cp -pr ogcore/$i $DISTDIR/api/$i
|
||||
done
|
||||
cp -pr ogcore/DEBIAN ogcore-$VERSION/
|
||||
cp -pr ogcore/etc $DISTDIR/
|
||||
cp ogcore/docker/certs/* $DISTDIR/etc/nginx/certs/
|
||||
chmod 755 ogcore-$VERSION/DEBIAN/postinst
|
||||
chmod 755 ogcore-$VERSION/DEBIAN/preinst
|
||||
cp ogcore/composer.json $DISTDIR/api
|
||||
cp ogcore/composer.lock $DISTDIR/api
|
||||
cp ogcore/symfony.lock $DISTDIR/api
|
||||
cp ogcore/.env.local.php $DISTDIR/api
|
||||
cp ogcore/env.json $DISTDIR/api
|
||||
|
||||
sed -i "s/%%VERSION%%/$VERSION/g" ogcore-$VERSION/DEBIAN/control
|
||||
# Imprimir el tag actual
|
||||
echo "Empaquetando TAG $TAG"
|
||||
rm -f ogcore-$VERSION.deb
|
||||
dpkg-deb --build ogcore-$VERSION
|
|
@ -15,6 +15,7 @@
|
|||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
|
||||
<server name="SYMFONY_PHPUNIT_VERSION" value="9.6" />
|
||||
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
|
|
|
@ -91,5 +91,4 @@ class LoadDefaultCommandsCommand extends Command
|
|||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Client;
|
||||
use App\Entity\Trace;
|
||||
use App\Model\ClientStatus;
|
||||
use App\Model\TraceStatus;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
#[AsCommand(name: 'opengnsys:test', description: 'Hello PhpStorm')]
|
||||
class TestCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly HubInterface $hub,
|
||||
private readonly EntityManagerInterface $entityManager
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$trace = $this->entityManager->getRepository(Trace::class)->find(7236);
|
||||
|
||||
$trace->setStatus(TraceStatus::SUCCESS);
|
||||
$trace->setProgress(1000);
|
||||
|
||||
$this->entityManager->persist($trace);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
|
@ -60,6 +60,10 @@ class DeployImageAction extends AbstractController
|
|||
];
|
||||
|
||||
$agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::UNICAST);
|
||||
if (!$agentJobId){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData);
|
||||
}
|
||||
break;
|
||||
|
@ -85,11 +89,14 @@ class DeployImageAction extends AbstractController
|
|||
try {
|
||||
$this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity(), $this->httpClient);
|
||||
} catch (\Exception $e) {
|
||||
//return new JsonResponse(data: ['error' => $e->getMessage()], status: Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
continue;
|
||||
}
|
||||
|
||||
$agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::MULTICAST);
|
||||
if (!$agentJobId){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData);
|
||||
}
|
||||
break;
|
||||
|
@ -109,11 +116,14 @@ class DeployImageAction extends AbstractController
|
|||
try {
|
||||
$this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity(), $this->httpClient);
|
||||
} catch (\Exception $e) {
|
||||
//return new JsonResponse(data: ['error' => $e->getMessage()], status: Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
continue;
|
||||
}
|
||||
|
||||
$agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::TORRENT);
|
||||
if (!$agentJobId){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ use App\Service\Trace\CreateService;
|
|||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
@ -39,6 +40,12 @@ class DeployImageAction extends AbstractController
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
*/
|
||||
public function __invoke(ImageImageRepository $imageImageRepository, DeployImageInput $input, Client $client, string $method)
|
||||
{
|
||||
$image = $imageImageRepository->getImage();
|
||||
|
@ -95,11 +102,15 @@ class DeployImageAction extends AbstractController
|
|||
]);
|
||||
$this->logger->info('Deploying image', ['image' => $image->getId()]);
|
||||
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$this->logger->error('Error deploying image', ['image' => $image->getId(), 'error' => $e->getMessage()]);
|
||||
$jobId = json_decode($response->getContent(), true)['job_id'];
|
||||
} catch (ClientExceptionInterface | ServerExceptionInterface | TransportExceptionInterface | TransportException $e) {
|
||||
$this->logger->error('Error deploying image', [
|
||||
'image' => $image->getId() ?? 'unknown',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$jobId = json_decode($response->getContent(), true)['job_id'];
|
||||
|
||||
$client->setStatus(ClientStatus::BUSY);
|
||||
$this->entityManager->persist($client);
|
||||
|
|
|
@ -17,6 +17,7 @@ use App\Service\Trace\CreateService;
|
|||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
@ -41,12 +42,17 @@ class PowerOffAction extends AbstractController
|
|||
public function __invoke(MultipleClientsInput $input): JsonResponse
|
||||
{
|
||||
foreach ($input->clients as $clientEntity) {
|
||||
/** @var Client $client */
|
||||
$client = $clientEntity->getEntity();
|
||||
|
||||
if (!$client->getIp()) {
|
||||
throw new ValidatorException('IP is required');
|
||||
}
|
||||
|
||||
if ($client->getStatus() === ClientStatus::OFF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'nfn' => 'Apagar',
|
||||
'ids' => '0'
|
||||
|
@ -62,21 +68,23 @@ class PowerOffAction extends AbstractController
|
|||
'json' => $data,
|
||||
]);
|
||||
$this->logger->info('Powering off client', ['client' => $client->getId()]);
|
||||
$jobId = json_decode($response->getContent(), true)['job_id'];
|
||||
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$this->logger->error('Error powering off client', ['client' => $client->getId(), 'error' => $e->getMessage()]);
|
||||
} catch (ClientExceptionInterface | ServerExceptionInterface | TransportExceptionInterface | TransportException $e) {
|
||||
$this->logger->error('Error power off client', [
|
||||
'image' => $client->getIp(),
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$jobId = json_decode($response->getContent(), true)['job_id'];
|
||||
|
||||
$client->setStatus(ClientStatus::OFF);
|
||||
$client->setStatus(ClientStatus::TURNING_OFF);
|
||||
$this->entityManager->persist($client);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, $jobId, []);
|
||||
}
|
||||
|
||||
return new JsonResponse(data: $client, status: Response::HTTP_OK);
|
||||
return new JsonResponse(data: [], status: Response::HTTP_OK);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,11 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|||
#[AsController]
|
||||
class ClientsController extends AbstractController
|
||||
{
|
||||
const string CREATE_IMAGE = 'RESPUESTA_CrearImagen';
|
||||
const string RESTORE_IMAGE = 'RESPUESTA_RestaurarImagen';
|
||||
const string CONFIGURE_IMAGE = 'RESPUESTA_Configurar';
|
||||
|
||||
|
||||
public function __construct(
|
||||
protected readonly EntityManagerInterface $entityManager,
|
||||
public readonly CreateAuxFilesAction $createAuxFilesAction,
|
||||
|
@ -54,7 +59,7 @@ class ClientsController extends AbstractController
|
|||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->toArray();
|
||||
$requiredFields = ['nfn', 'res', 'der', 'job_id'];
|
||||
$requiredFields = ['job_id'];
|
||||
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($data[$field])) {
|
||||
|
@ -64,7 +69,16 @@ class ClientsController extends AbstractController
|
|||
|
||||
$this->logger->info('Webhook data received', $data);
|
||||
|
||||
if ($data['nfn'] === 'RESPUESTA_CrearImagen') {
|
||||
if (isset($data['progress'])){
|
||||
$trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
|
||||
if ($trace){
|
||||
$trace->setProgress($data['progress'] * 1000);
|
||||
$this->entityManager->persist($trace);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['nfn']) && $data['nfn'] === self::CREATE_IMAGE) {
|
||||
$trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
|
||||
/** @var ImageImageRepository $imageImageRepository */
|
||||
$imageImageRepository = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['uuid' => $data['idi']]);
|
||||
|
@ -82,10 +96,8 @@ class ClientsController extends AbstractController
|
|||
if ($data['res'] === 1) {
|
||||
$trace->setStatus(TraceStatus::SUCCESS);
|
||||
$trace->setFinishedAt(new \DateTime());
|
||||
|
||||
$imageImageRepository->setStatus(ImageStatus::AUX_FILES_PENDING);
|
||||
$imageImageRepository->setCreated(true);
|
||||
|
||||
$this->entityManager->persist($imageImageRepository);
|
||||
|
||||
$this->logger->info('Start partition creation. ', ['image' => (string) $imageImageRepository->getUuid()]);
|
||||
|
@ -95,11 +107,13 @@ class ClientsController extends AbstractController
|
|||
$this->logger->info('Starting software profile creation. ', ['image' => (string) $imageImageRepository->getUuid()]);
|
||||
$this->createSoftwareProfile($data['inv_sft'], $imageImageRepository);
|
||||
$this->logger->info('Start aux files ogrepo API ', ['image' => (string) $imageImageRepository->getUuid()]);
|
||||
|
||||
try {
|
||||
$this->createAuxFilesAction->__invoke($imageImageRepository);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error creating aux files', ['image' => (string) $imageImageRepository->getUuid(), 'error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
$this->logger->info('End aux files ogrepo API ', ['image' => (string) $imageImageRepository->getUuid()]);
|
||||
} else {
|
||||
$trace->setStatus(TraceStatus::FAILED);
|
||||
|
@ -119,9 +133,8 @@ class ClientsController extends AbstractController
|
|||
$this->logger->info('Image updated. Success.', ['image' => (string) $imageImageRepository->getUuid()]);
|
||||
}
|
||||
|
||||
if ($data['nfn'] === 'RESPUESTA_RestaurarImagen'|| $data['nfn'] === 'RESPUESTA_Configurar') {
|
||||
if (isset($data['nfn']) && ($data['nfn'] === self::RESTORE_IMAGE || $data['nfn'] === self::CONFIGURE_IMAGE)) {
|
||||
$trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
|
||||
|
||||
$client = $trace->getClient();
|
||||
|
||||
if ($data['res'] === 1) {
|
||||
|
|
|
@ -46,7 +46,7 @@ class PostAction extends AbstractOgBootController
|
|||
'router' => $client->getOrganizationalUnit()->getNetworkSettings()->getRouter(),
|
||||
'netmask' => $client->getOrganizationalUnit()->getNetworkSettings() ? $client->getOrganizationalUnit()->getNetworkSettings()->getNetmask() : '255.255.255.0',
|
||||
'computer_name' => $client->getName(),
|
||||
'netiface' => $client->getNetiface(),
|
||||
'netiface' => $client->getNetiface() ? $client->getNetiface() : $client->getOrganizationalUnit()->getNetworkSettings()->getNetiface(),
|
||||
'group' => $client->getOrganizationalUnit()->getName(),
|
||||
'ogrepo' => $ogRepoIp,
|
||||
'ogcore' => $this->ogCoreIP,
|
||||
|
|
|
@ -33,6 +33,7 @@ class PostAction extends AbstractOgDhcpController
|
|||
'nextServer' => $data->getNextServer(),
|
||||
'bootFileName' => $data->getBootFileName(),
|
||||
'router' => $data->getRouter(),
|
||||
'DNS' => $data->getDns()
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
@ -29,27 +29,54 @@ class PostHostAction extends AbstractOgDhcpController
|
|||
*/
|
||||
public function __invoke(SubnetAddHostInput $input, Subnet $subnet): JsonResponse
|
||||
{
|
||||
$client = $input->client;
|
||||
$clients = $input->clients;
|
||||
|
||||
/** @var Client $clientEntity */
|
||||
$clientEntity = $client->getEntity();
|
||||
$success = [];
|
||||
$errors = [];
|
||||
|
||||
$data = [
|
||||
'host' => $clientEntity->getName(),
|
||||
'macAddress' => strtolower($clientEntity->getMac()),
|
||||
'address' => $clientEntity->getIp(),
|
||||
];
|
||||
foreach ($clients as $client) {
|
||||
/** @var Client $clientEntity */
|
||||
$clientEntity = $client->getEntity();
|
||||
|
||||
$params = [
|
||||
'json' => $data
|
||||
];
|
||||
$data = [
|
||||
'host' => $clientEntity->getName(),
|
||||
'macAddress' => strtolower($clientEntity->getMac()),
|
||||
'address' => $clientEntity->getIp(),
|
||||
];
|
||||
|
||||
$content = $this->createRequest('POST', 'http://'.$this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$subnet->getServerId().'/hosts', $params);
|
||||
$params = ['json' => $data];
|
||||
|
||||
$subnet->addClient($clientEntity);
|
||||
$this->entityManager->persist($subnet);
|
||||
$this->entityManager->flush();
|
||||
try {
|
||||
$content = $this->createRequest(
|
||||
'POST',
|
||||
'http://' . $this->ogDhcpApiUrl . '/ogdhcp/v1/subnets/' . $subnet->getServerId() . '/hosts',
|
||||
$params
|
||||
);
|
||||
|
||||
return new JsonResponse(data: $content, status: Response::HTTP_OK);
|
||||
// Guardar resultado exitoso
|
||||
$success[] = [
|
||||
'client' => $clientEntity->getName(),
|
||||
'response' => $content
|
||||
];
|
||||
|
||||
// Persistir solo si la llamada fue exitosa
|
||||
$subnet->addClient($clientEntity);
|
||||
$this->entityManager->persist($subnet);
|
||||
$this->entityManager->flush();
|
||||
} catch (\Throwable $e) { // Capturar cualquier error sin interrumpir
|
||||
$errors[] = [
|
||||
'client' => $clientEntity->getName(),
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse(
|
||||
[
|
||||
'success' => $success,
|
||||
'errors' => $errors
|
||||
],
|
||||
empty($errors) ? Response::HTTP_OK : Response::HTTP_MULTI_STATUS
|
||||
);
|
||||
}
|
||||
}
|
|
@ -63,7 +63,7 @@ class SyncAction extends AbstractOgDhcpController
|
|||
{
|
||||
$getParsedData = $this->getIpAddressAndNetmaskFromCIDRService->__invoke($subnet['subnet']);
|
||||
|
||||
$subnetEntity->setName($subnet['id'].' - '.$subnet['subnet']);
|
||||
$subnetEntity->setName('Subred -'.$subnet['id']);
|
||||
$subnetEntity->setBootFileName($subnet['boot-file-name'] ?? null);
|
||||
$subnetEntity->setIpAddress($getParsedData['ip']);
|
||||
$subnetEntity->setNetmask($getParsedData['mask']);
|
||||
|
|
|
@ -55,6 +55,7 @@ abstract class AbstractOgRepositoryController extends AbstractController
|
|||
$this->logger->error(sprintf('Client/Server error in request to %s: %s', $url, $e->getMessage()));
|
||||
|
||||
return [
|
||||
'code' => Response::HTTP_INTERNAL_SERVER_ERROR,
|
||||
'error' => 'Client/Server error',
|
||||
'details' => $e->getMessage(),
|
||||
];
|
||||
|
@ -62,6 +63,7 @@ abstract class AbstractOgRepositoryController extends AbstractController
|
|||
$this->logger->error(sprintf('Transport error in request to %s: %s', $url, $e->getMessage()));
|
||||
|
||||
return [
|
||||
'code' => Response::HTTP_INTERNAL_SERVER_ERROR,
|
||||
'error' => 'Transport error',
|
||||
'details' => $e->getMessage(),
|
||||
];
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\OgRepository\Image;
|
||||
|
||||
use App\Controller\OgRepository\AbstractOgRepositoryController;
|
||||
use App\Dto\Input\BackupImageInput;
|
||||
use App\Dto\Input\DeleteImageInput;
|
||||
use App\Entity\Image;
|
||||
use App\Entity\ImageImageRepository;
|
||||
use App\Entity\ImageRepository;
|
||||
use App\Model\CommandTypes;
|
||||
use App\Model\ImageStatus;
|
||||
use App\Model\TraceStatus;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
#[AsController]
|
||||
class BackupImageAction extends AbstractOgRepositoryController
|
||||
{
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
*/
|
||||
public function __invoke(BackupImageInput $input, ImageImageRepository $imageImageRepository): JsonResponse
|
||||
{
|
||||
$image = $imageImageRepository->getImage();
|
||||
|
||||
if (!$image->getName()) {
|
||||
throw new ValidatorException('Name is required');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'json' => [
|
||||
'ID_img' => $imageImageRepository->getImageFullsum(),
|
||||
'repo_ip' => $input->repoIp,
|
||||
'remote_path' => $input->remotePath,
|
||||
'user' => 'opengnsys'
|
||||
]
|
||||
];
|
||||
|
||||
$this->logger->info('Create backup image', ['image' => $image->getName()]);
|
||||
|
||||
$repository = $imageImageRepository->getRepository();
|
||||
|
||||
$content = $this->createRequest('PUT', 'http://'.$repository->getIp().':8006/ogrepository/v1/repo/images', $params);
|
||||
|
||||
$inputData = [
|
||||
'imageName' => $image->getName(),
|
||||
'repositoryUuid' => $repository->getUuid(),
|
||||
'imageUuid' => $imageImageRepository->getUuid(),
|
||||
'ID_img' => $imageImageRepository->getImageFullsum(),
|
||||
'repo_ip' => $input->repoIp,
|
||||
'remote_path' => $input->remotePath
|
||||
];
|
||||
|
||||
$this->createService->__invoke($image->getClient(), CommandTypes::BACKUP_IMAGE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData);
|
||||
|
||||
$imageImageRepository->setStatus(ImageStatus::BACKUP);
|
||||
$this->entityManager->persist($imageImageRepository);
|
||||
$this->entityManager->flush();
|
||||
|
||||
|
||||
return new JsonResponse(data: $content, status: Response::HTTP_OK);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\OgRepository\Image;
|
||||
|
||||
use App\Controller\OgRepository\AbstractOgRepositoryController;
|
||||
use App\Entity\Client;
|
||||
use App\Entity\Image;
|
||||
use App\Entity\ImageImageRepository;
|
||||
use App\Entity\Trace;
|
||||
use App\Model\CommandTypes;
|
||||
use App\Model\TraceStatus;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
#[AsController]
|
||||
class CancelTransmissionAction extends AbstractOgRepositoryController
|
||||
{
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
*/
|
||||
public function __invoke(Trace $data): JsonResponse
|
||||
{
|
||||
if ($data->getCommand() !== CommandTypes::DEPLOY_IMAGE) {
|
||||
throw new ValidatorException('Command is not DEPLOY_IMAGE');
|
||||
}
|
||||
|
||||
$input = $data->getInput();
|
||||
|
||||
if (!isset($input['client']) || !isset($input['image']) || !isset($input['method'])) {
|
||||
throw new ValidatorException('Client, image and method are required');
|
||||
}
|
||||
$client = $this->entityManager->getRepository(Client::class)->findOneBy(['uuid' => $input['client']]);
|
||||
$image = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['uuid' => $input['image']]);
|
||||
|
||||
if (!$client || !$image) {
|
||||
throw new ValidatorException('Client or image not found');
|
||||
}
|
||||
|
||||
$method = $input['method'];
|
||||
|
||||
if (!$image->getImageFullsum()) {
|
||||
throw new ValidatorException('Fullsum is required');
|
||||
}
|
||||
|
||||
$content = $this->createRequest('DELETE', 'http://'.$image->getRepository()->getIp().':8006/ogrepository/v1/'.$method.'/images/'.$image->getImageFullsum());
|
||||
|
||||
if (isset($content['error']) && $content['error'] === Response::HTTP_INTERNAL_SERVER_ERROR ) {
|
||||
throw new ValidatorException('Error cancelling transmission');
|
||||
}
|
||||
|
||||
$data->setStatus(TraceStatus::CANCELLED);
|
||||
$this->entityManager->persist($data);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(data: $content, status: Response::HTTP_OK);
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ class CreateAuxFilesAction extends AbstractOgRepositoryController
|
|||
|
||||
$this->logger->info('Creating aux files', ['image' => $image->getName()]);
|
||||
|
||||
$repository = $image->getClient()->getRepository();
|
||||
$repository = $data->getRepository();
|
||||
|
||||
$content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/images/torrentsum', $params);
|
||||
|
||||
|
@ -56,8 +56,6 @@ class CreateAuxFilesAction extends AbstractOgRepositoryController
|
|||
|
||||
$this->createService->__invoke($image->getClient(), CommandTypes::CREATE_IMAGE_AUX_FILE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData);
|
||||
|
||||
$this->logger->info('Aux files created successfully', ['image' => $image->getName()]);
|
||||
|
||||
$data->setStatus(ImageStatus::AUX_FILES_PENDING);
|
||||
$this->entityManager->persist($data);
|
||||
$this->entityManager->flush();
|
||||
|
|
|
@ -37,8 +37,6 @@ class DeleteTrashAction extends AbstractOgRepositoryController
|
|||
|
||||
$this->logger->info('Deleting image', ['image' => $image->getName()]);
|
||||
|
||||
$repository = $image->getClient()->getRepository();
|
||||
|
||||
$content = $this->createRequest('DELETE', 'http://'.$repository->getIp().':8006/ogrepository/v1/images/'.$imageImageRepository->getImageFullsum().'?method=trash');
|
||||
|
||||
$this->logger->info('Image deleted', ['image' => $image->getName()]);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\OgRepository\Image;
|
||||
|
||||
use App\Controller\OgRepository\AbstractOgRepositoryController;
|
||||
use App\Entity\Image;
|
||||
use App\Entity\ImageImageRepository;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
#[AsController]
|
||||
class GetStatusAction extends AbstractOgRepositoryController
|
||||
{
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
*/
|
||||
public function __invoke(ImageImageRepository $data): JsonResponse
|
||||
{
|
||||
if (!$data->getImageFullsum()) {
|
||||
throw new ValidatorException('Fullsum is required');
|
||||
}
|
||||
|
||||
$content = $this->createRequest('GET', 'http://'.$data->getRepository()->getIp().':8006/ogrepository/v1/status/images/'.$data->getImageFullsum());
|
||||
|
||||
if (isset($content['error']) && $content['error'] === Response::HTTP_INTERNAL_SERVER_ERROR ) {
|
||||
throw new ValidatorException('Error getting status');
|
||||
}
|
||||
|
||||
return new JsonResponse(data: $content, status: Response::HTTP_OK);
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ class RecoverAction extends AbstractOgRepositoryController
|
|||
|
||||
$this->logger->info('Recovering image', ['image' => $image->getName()]);
|
||||
|
||||
$repository = $image->getClient()->getRepository();
|
||||
$repository = $data->getRepository();
|
||||
|
||||
$content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/trash/images', $params);
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use App\Entity\Image;
|
|||
use App\Entity\ImageImageRepository;
|
||||
use App\Entity\ImageRepository;
|
||||
use App\Entity\Trace;
|
||||
use App\Model\CommandTypes;
|
||||
use App\Model\ImageStatus;
|
||||
use App\Model\TraceStatus;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
@ -18,117 +17,81 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
#[AsController]
|
||||
class ResponseController extends AbstractOgRepositoryController
|
||||
{
|
||||
/**
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
*/
|
||||
#[Route('/og-repository/webhook', name: 'og_repository_webhook', methods: ['POST'])]
|
||||
public function repositoryWebhook(Request $request): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
if (!isset($data['job_id'])) {
|
||||
return new JsonResponse(['message' => 'Invalid request'], Response::HTTP_BAD_REQUEST);
|
||||
return $this->jsonResponseError('Invalid request', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$action = $data['job_id'];
|
||||
|
||||
if (str_starts_with($action, "CreateAuxiliarFiles_")) {
|
||||
$this->handleCreateAuxFiles($data);
|
||||
} elseif (str_starts_with($action, "TransferImage_")) {
|
||||
$this->processImageAction($data, 'transfer');
|
||||
} elseif (str_starts_with($action, "ExportImage_")) {
|
||||
$this->processImageAction($data, 'export');
|
||||
} else {
|
||||
return new JsonResponse(['message' => 'Invalid action'], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
return new JsonResponse($data, Response::HTTP_OK);
|
||||
return match (true) {
|
||||
str_starts_with($action, "CreateAuxiliarFiles_") => $this->handleImageRepositoryAction($data, true),
|
||||
str_starts_with($action, "TransferImage_"), str_starts_with($action, "ExportImage_") => $this->processImageAction($data),
|
||||
str_starts_with($action, "BackupImage_") => $this->handleImageRepositoryAction($data),
|
||||
default => $this->jsonResponseError('Invalid action', Response::HTTP_BAD_REQUEST),
|
||||
};
|
||||
}
|
||||
|
||||
private function handleCreateAuxFiles(array $data): void
|
||||
private function handleImageRepositoryAction(array $data, bool $setFullsum = false): JsonResponse
|
||||
{
|
||||
$trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
|
||||
$imageUuid = $trace->getInput()['imageUuid'];
|
||||
$trace = $this->getTrace($data['job_id']);
|
||||
if (!$trace) return $this->jsonResponseError('Trace not found');
|
||||
|
||||
/* @var ImageImageRepository $imageImageRepository */
|
||||
$imageImageRepository = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['uuid' => $imageUuid]);
|
||||
$imageImageRepository = $this->getImageImageRepository($trace);
|
||||
if (!$imageImageRepository) return $this->jsonResponseError('Image not found', Response::HTTP_NOT_FOUND, $trace);
|
||||
|
||||
if ($imageImageRepository === null) {
|
||||
$this->updateTraceStatus($trace, TraceStatus::FAILED, 'Image not found');
|
||||
return;
|
||||
if ($setFullsum) {
|
||||
$imageImageRepository->setImageFullsum($data['image_id']);
|
||||
}
|
||||
|
||||
$imageImageRepository->setImageFullsum($data['image_id']);
|
||||
$imageImageRepository->setStatus(ImageStatus::SUCCESS);
|
||||
$this->entityManager->persist($imageImageRepository);
|
||||
|
||||
$this->entityManager->persist($imageImageRepository);
|
||||
$this->updateTraceStatus($trace, TraceStatus::SUCCESS);
|
||||
|
||||
return new JsonResponse(['message' => 'Success'], Response::HTTP_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
*/
|
||||
private function processImageAction(array $data, string $actionType): void
|
||||
private function processImageAction(array $data): JsonResponse
|
||||
{
|
||||
$trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
|
||||
$imageUuid = $trace->getInput()['imageUuid'];
|
||||
$repositoryUuid = $trace->getInput()['repositoryUuid'];
|
||||
|
||||
$image = $this->entityManager->getRepository(Image::class)->findOneBy(['uuid' => $imageUuid]);
|
||||
$repository = $this->entityManager->getRepository(ImageRepository::class)->findOneBy(['uuid' => $repositoryUuid]);
|
||||
$trace = $this->getTrace($data['job_id']);
|
||||
if (!$trace) return $this->jsonResponseError('Trace not found');
|
||||
|
||||
if ($data['success'] !== true) {
|
||||
$this->updateTraceStatus($trace, TraceStatus::FAILED, 'Action failed');
|
||||
return;
|
||||
return $this->jsonResponseError('Action failed', Response::HTTP_BAD_REQUEST, $trace);
|
||||
}
|
||||
|
||||
if ($image === null) {
|
||||
$this->updateTraceStatus($trace, TraceStatus::FAILED, 'Image not found');
|
||||
return;
|
||||
}
|
||||
$image = $this->getImage($trace);
|
||||
$repository = $this->getRepository($trace);
|
||||
if (!$image) return $this->jsonResponseError('Image not found', Response::HTTP_NOT_FOUND, $trace);
|
||||
if (!$repository) return $this->jsonResponseError('Repository not found', Response::HTTP_NOT_FOUND, $trace);
|
||||
|
||||
if ($repository === null) {
|
||||
$this->updateTraceStatus($trace, TraceStatus::FAILED, 'Repository not found');
|
||||
return;
|
||||
}
|
||||
$newImageRepo = new ImageImageRepository();
|
||||
$newImageRepo->setImage($image);
|
||||
$newImageRepo->setRepository($repository);
|
||||
$newImageRepo->setStatus(ImageStatus::SUCCESS);
|
||||
|
||||
if (isset($trace->getInput()['imageImageRepositoryUuid'])) {
|
||||
$imageImageRepositoryUuid = $trace->getInput()['imageImageRepositoryUuid'];
|
||||
$imageImageRepository = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['uuid' => $imageImageRepositoryUuid]);
|
||||
if ($trace->getInput()['imageImageRepositoryUuid'] ?? false) {
|
||||
$existingRepo = $this->entityManager->getRepository(ImageImageRepository::class)
|
||||
->findOneBy(['uuid' => $trace->getInput()['imageImageRepositoryUuid']]);
|
||||
|
||||
if ($imageImageRepository) {
|
||||
$this->updateTraceStatus($trace, TraceStatus::FAILED, 'Image repository not found');
|
||||
$imageImageRepository->setStatus(ImageStatus::SUCCESS);
|
||||
if ($existingRepo) {
|
||||
$newImageRepo->setImageFullsum($existingRepo->getImageFullsum());
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info("Image $actionType successful", ['image' => $image->getName()]);
|
||||
|
||||
// Creamos un objeto imagen nuevo, en el repositorio destino
|
||||
$newImageImageRepository = new ImageImageRepository();
|
||||
$newImageImageRepository->setImage($image);
|
||||
$newImageImageRepository->setRepository($repository);
|
||||
$newImageImageRepository->setStatus(ImageStatus::SUCCESS);
|
||||
|
||||
// Cambiamos el estado de la imagen anterior a SUCCESS
|
||||
$this->entityManager->persist($newImageImageRepository);
|
||||
$this->entityManager->persist($image);
|
||||
|
||||
$this->entityManager->persist($newImageRepo);
|
||||
$this->updateTraceStatus($trace, TraceStatus::SUCCESS);
|
||||
|
||||
return new JsonResponse(['message' => 'Success'], Response::HTTP_OK);
|
||||
}
|
||||
|
||||
private function updateTraceStatus(Trace $trace, string $status, string $output = null): void
|
||||
|
@ -142,9 +105,36 @@ class ResponseController extends AbstractOgRepositoryController
|
|||
|
||||
$this->entityManager->persist($trace);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
if ($status === TraceStatus::FAILED) {
|
||||
new JsonResponse(['message' => $output], Response::HTTP_NOT_FOUND);
|
||||
private function getTrace(string $jobId): ?Trace
|
||||
{
|
||||
return $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $jobId]);
|
||||
}
|
||||
|
||||
private function getImage(Trace $trace): ?Image
|
||||
{
|
||||
return $this->entityManager->getRepository(Image::class)
|
||||
->findOneBy(['uuid' => $trace->getInput()['imageUuid']]);
|
||||
}
|
||||
|
||||
private function getRepository(Trace $trace): ?ImageRepository
|
||||
{
|
||||
return $this->entityManager->getRepository(ImageRepository::class)
|
||||
->findOneBy(['uuid' => $trace->getInput()['repositoryUuid']]);
|
||||
}
|
||||
|
||||
private function getImageImageRepository(Trace $trace): ?ImageImageRepository
|
||||
{
|
||||
return $this->entityManager->getRepository(ImageImageRepository::class)
|
||||
->findOneBy(['uuid' => $trace->getInput()['imageUuid']]);
|
||||
}
|
||||
|
||||
private function jsonResponseError(string $message, int $status = Response::HTTP_BAD_REQUEST, ?Trace $trace = null): JsonResponse
|
||||
{
|
||||
if ($trace) {
|
||||
$this->updateTraceStatus($trace, TraceStatus::FAILED, $message);
|
||||
}
|
||||
return new JsonResponse(['message' => $message], $status);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,11 +58,16 @@ class WoLAction extends AbstractOgRepositoryController
|
|||
|
||||
$content = $this->createRequest('POST', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/wol', $params);
|
||||
|
||||
if (isset($content['error']) && $content['error'] === Response::HTTP_INTERNAL_SERVER_ERROR ) {
|
||||
$this->logger->error('Error sending WoL to client', ['mac' => $client->getMac()]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$client->setStatus(ClientStatus::INITIALIZING);
|
||||
$this->entityManager->persist($client);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, '', []);
|
||||
$this->createService->__invoke($client, CommandTypes::POWER_ON, TraceStatus::SUCCESS, '', []);
|
||||
}
|
||||
|
||||
return new JsonResponse(data: [], status: Response::HTTP_OK);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Dto\Input;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use App\Dto\Output\ClientOutput;
|
||||
use App\Dto\Output\ImageOutput;
|
||||
use App\Dto\Output\PartitionOutput;
|
||||
use App\Validator\Constraints\ClientsHaveSamePartitionCount;
|
||||
use App\Validator\Constraints\OrganizationalUnitMulticastMode;
|
||||
use App\Validator\Constraints\OrganizationalUnitMulticastPort;
|
||||
use App\Validator\Constraints\OrganizationalUnitP2PMode;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
final class BackupImageInput
|
||||
{
|
||||
#[Groups(['image-image-repository:write'])]
|
||||
#[ApiProperty(description: 'The repository ip', example: "")]
|
||||
public ?string $repoIp = null;
|
||||
|
||||
#[Groups(['image-image-repository:write'])]
|
||||
#[ApiProperty(description: 'The remote path', example: "")]
|
||||
public ?string $remotePath = null;
|
||||
}
|
|
@ -41,6 +41,9 @@ class NetworkSettingsInput
|
|||
#[Groups(['organizational-unit:write'])]
|
||||
public ?string $ntp = null;
|
||||
|
||||
#[Groups(['organizational-unit:write'])]
|
||||
public ?string $netiface = null;
|
||||
|
||||
#[OrganizationalUnitP2PMode]
|
||||
#[Groups(['organizational-unit:write'])]
|
||||
public ?string $p2pMode = null;
|
||||
|
@ -93,6 +96,7 @@ class NetworkSettingsInput
|
|||
$this->netmask = $networkSettings->getNetmask();
|
||||
$this->router = $networkSettings->getRouter();
|
||||
$this->ntp = $networkSettings->getNtp();
|
||||
$this->netiface = $networkSettings->getNetiface();
|
||||
$this->p2pMode = $networkSettings->getP2pMode();
|
||||
$this->p2pTime = $networkSettings->getP2pTime();
|
||||
$this->mcastIp = $networkSettings->getMcastIp();
|
||||
|
@ -130,6 +134,7 @@ class NetworkSettingsInput
|
|||
$networkSettings->setDns($this->dns);
|
||||
$networkSettings->setNetmask($this->netmask);
|
||||
$networkSettings->setRouter($this->router);
|
||||
$networkSettings->setNetiface($this->netiface);
|
||||
$networkSettings->setNtp($this->ntp);
|
||||
$networkSettings->setP2pMode($this->p2pMode);
|
||||
$networkSettings->setP2pTime($this->p2pTime);
|
||||
|
|
|
@ -12,7 +12,10 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
|
||||
final class SubnetAddHostInput
|
||||
{
|
||||
/**
|
||||
* @var ClientOutput[]
|
||||
*/
|
||||
#[Assert\NotNull]
|
||||
#[Groups(['subnet:write'])]
|
||||
public ?ClientOutput $client = null;
|
||||
public ?array $clients = [];
|
||||
}
|
|
@ -36,6 +36,10 @@ final class SubnetInput
|
|||
#[ApiProperty(description: 'The router of the subnet', example: "")]
|
||||
public ?string $router = null;
|
||||
|
||||
#[Groups(['subnet:write'])]
|
||||
#[ApiProperty(description: 'The dns server of the subnet', example: "")]
|
||||
public ?string $dns = null;
|
||||
|
||||
#[Groups(['subnet:write'])]
|
||||
#[ApiProperty(description: 'The boot file name of the subnet', example: "")]
|
||||
public ?string $bootFileName = null;
|
||||
|
@ -58,6 +62,7 @@ final class SubnetInput
|
|||
$this->ipAddress = $subnet->getIpAddress();
|
||||
$this->router = $subnet->getRouter();
|
||||
$this->nextServer = $subnet->getNextServer();
|
||||
$this->dns = $subnet->getDns();
|
||||
$this->bootFileName = $subnet->getBootFileName();
|
||||
|
||||
if ($subnet->getOrganizationalUnits()) {
|
||||
|
@ -76,6 +81,7 @@ final class SubnetInput
|
|||
$subnet->setName($this->name);
|
||||
$subnet->setNetmask($this->netmask);
|
||||
$subnet->setIpAddress($this->ipAddress);
|
||||
$subnet->setDns($this->dns);
|
||||
$subnet->setNextServer($this->nextServer);
|
||||
$subnet->setBootFileName($this->bootFileName);
|
||||
$subnet->setRouter($this->router);
|
||||
|
|
|
@ -29,6 +29,9 @@ final class UserInput
|
|||
#[Groups('user:write')]
|
||||
public ?string $password = null;
|
||||
|
||||
#[Groups('user:write')]
|
||||
public ?string $groupsView = null;
|
||||
|
||||
#[Assert\NotNull]
|
||||
#[Groups('user:write')]
|
||||
public ?bool $enabled = true;
|
||||
|
@ -63,6 +66,7 @@ final class UserInput
|
|||
|
||||
$this->username = $user->getUsername();
|
||||
$this->enabled= $user->isEnabled();
|
||||
$this->groupsView = $user->getGroupsView();
|
||||
|
||||
if ($user->getUserGroups()) {
|
||||
foreach ($user->getUserGroups() as $userGroup) {
|
||||
|
@ -85,6 +89,7 @@ final class UserInput
|
|||
|
||||
$user->setUsername($this->username);
|
||||
$user->setEnabled($this->enabled);
|
||||
$user->setGroupsView($this->groupsView);
|
||||
|
||||
foreach ($this->userGroups as $userGroup) {
|
||||
$userGroupsToAdd[] = $userGroup->getEntity();
|
||||
|
|
|
@ -30,6 +30,9 @@ final class NetworkSettingsOutput extends AbstractOutput
|
|||
#[Groups(['network-settings:read', "organizational-unit:read", "client:read"])]
|
||||
public ?string $ntp = null;
|
||||
|
||||
#[Groups(['network-settings:read', "organizational-unit:read", "client:read"])]
|
||||
public ?string $netiface = null;
|
||||
|
||||
#[Groups(['network-settings:read', "organizational-unit:read", "client:read"])]
|
||||
public ?string $p2pMode = null;
|
||||
|
||||
|
@ -80,6 +83,7 @@ final class NetworkSettingsOutput extends AbstractOutput
|
|||
$this->netmask = $networkSettings->getNetmask();
|
||||
$this->router = $networkSettings->getRouter();
|
||||
$this->ntp = $networkSettings->getNtp();
|
||||
$this->netiface = $networkSettings->getNetiface();
|
||||
$this->p2pMode = $networkSettings->getP2pMode();
|
||||
$this->p2pTime = $networkSettings->getP2pTime();
|
||||
$this->mcastIp = $networkSettings->getMcastIp();
|
||||
|
|
|
@ -38,7 +38,7 @@ final class OrganizationalUnitOutput extends AbstractOutput
|
|||
#[Groups(['organizational-unit:read'])]
|
||||
public ?self $parent = null;
|
||||
|
||||
#[Groups(['organizational-unit:read', 'organizational-unit:read:collection:short'])]
|
||||
#[Groups(['organizational-unit:read', 'client:read', 'organizational-unit:read:collection:short'])]
|
||||
public string $path;
|
||||
|
||||
#[Groups(['organizational-unit:read', "client:read"])]
|
||||
|
|
|
@ -30,6 +30,9 @@ final class SubnetOutput extends AbstractOutput
|
|||
#[Groups(['subnet:read'])]
|
||||
public ?string $router = null;
|
||||
|
||||
#[Groups(['subnet:read'])]
|
||||
public ?string $dns = null;
|
||||
|
||||
#[Groups(['subnet:read'])]
|
||||
public array $clients;
|
||||
|
||||
|
@ -54,6 +57,7 @@ final class SubnetOutput extends AbstractOutput
|
|||
$this->ipAddress = $subnet->getIpAddress();
|
||||
$this->nextServer = $subnet->getNextServer();
|
||||
$this->router = $subnet->getRouter();
|
||||
$this->dns = $subnet->getDns();
|
||||
$this->bootFileName = $subnet->getBootFileName();
|
||||
$this->synchronized = $subnet->isSynchronized();
|
||||
$this->serverId = $subnet->getServerId();
|
||||
|
|
|
@ -35,7 +35,7 @@ final class TraceOutput extends AbstractOutput
|
|||
public ?\DateTimeInterface $finishedAt = null;
|
||||
|
||||
#[Groups(['trace:read'])]
|
||||
public ?int $progress = null;
|
||||
public ?float $progress = null;
|
||||
|
||||
#[Groups(['trace:read'])]
|
||||
public \DateTime $createdAt;
|
||||
|
@ -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();
|
||||
$this->progress = $trace->getProgress() / 100;
|
||||
$this->createdAt = $trace->getCreatedAt();
|
||||
$this->createdBy = $trace->getCreatedBy();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ final class UserOutput extends AbstractOutput
|
|||
public array $allowedOrganizationalUnits;
|
||||
#[Groups(['user:read'])]
|
||||
public array $userGroups;
|
||||
#[Groups(['user:read'])]
|
||||
public ?string $groupsView = 'card';
|
||||
|
||||
#[Groups(['user:read'])]
|
||||
public \DateTime $createdAt;
|
||||
|
@ -37,6 +39,7 @@ final class UserOutput extends AbstractOutput
|
|||
$this->username = $user->getUsername();
|
||||
$this->roles = $user->getRoles();
|
||||
$this->enabled = $user->isEnabled();
|
||||
$this->groupsView = $user->getGroupsView();
|
||||
|
||||
$this->userGroups = $user->getUserGroups()->map(
|
||||
fn(UserGroup $userGroup) => new UserGroupOutput($userGroup)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use App\Repository\ClientRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
@ -152,6 +153,7 @@ class Client extends AbstractEntity
|
|||
return $this->status;
|
||||
}
|
||||
|
||||
#[ORM\PostPersist]
|
||||
public function setStatus(?string $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
|
|
@ -31,6 +31,9 @@ class NetworkSettings extends AbstractEntity
|
|||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $ntp = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $netiface = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $p2pTime = null;
|
||||
|
||||
|
@ -161,6 +164,18 @@ class NetworkSettings extends AbstractEntity
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getNetiface(): ?string
|
||||
{
|
||||
return $this->netiface;
|
||||
}
|
||||
|
||||
public function setNetiface(?string $netiface): static
|
||||
{
|
||||
$this->netiface = $netiface;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getP2pTime(): ?int
|
||||
{
|
||||
return $this->p2pTime;
|
||||
|
|
|
@ -43,6 +43,9 @@ class Subnet extends AbstractEntity
|
|||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $router = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $dns = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
@ -202,4 +205,16 @@ class Subnet extends AbstractEntity
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDns(): ?string
|
||||
{
|
||||
return $this->dns;
|
||||
}
|
||||
|
||||
public function setDns(?string $dns): static
|
||||
{
|
||||
$this->dns = $dns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,9 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate
|
|||
private ?string $newPassword = null;
|
||||
private ?string $repeatNewPassword = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $groupsView = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -245,4 +248,16 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGroupsView(): ?string
|
||||
{
|
||||
return $this->groupsView;
|
||||
}
|
||||
|
||||
public function setGroupsView(?string $groupsView): static
|
||||
{
|
||||
$this->groupsView = $groupsView;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\Client;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
#[AsEntityListener(event: Events::postUpdate, method: 'postUpdate', entity: Client::class)]
|
||||
class ClientStatusNotifier
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
private readonly HubInterface $hub
|
||||
)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function postUpdate(Client $client, PostUpdateEventArgs $event): void
|
||||
{
|
||||
try {
|
||||
$this->notifyClientStatusChange($client);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error al notificar el cambio de estado de un cliente: ', [
|
||||
'client' => $client->getUuid(),
|
||||
'status' => $client->getStatus(),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyClientStatusChange(Client $client): void
|
||||
{
|
||||
$update = new Update(
|
||||
'clients',
|
||||
json_encode(['@id' => '/clients/'.$client->getUuid(), 'status' => $client->getStatus()])
|
||||
);
|
||||
$this->hub->publish($update);
|
||||
|
||||
$this->logger->info('Evento Mercure disparado. Cambio en el estado de un cliente: ', [
|
||||
'client' => $client->getUuid(),
|
||||
'status' => $client->getStatus(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\Client;
|
||||
use App\Entity\Trace;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
#[AsEntityListener(event: Events::postUpdate, method: 'postUpdate', entity: Trace::class)]
|
||||
class TraceStatusProgressNotifier
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
private readonly HubInterface $hub
|
||||
)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function postUpdate(Trace $trace, PostUpdateEventArgs $event): void
|
||||
{
|
||||
try {
|
||||
$this->notifyTraceStatusProgressChange($trace);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error al notificar el cambio de estado de una traza/log: ', [
|
||||
'client' => $trace->getUuid(),
|
||||
'status' => $trace->getStatus(),
|
||||
'progress' => $trace->getProgress(),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyTraceStatusProgressChange(Trace $trace): void
|
||||
{
|
||||
$update = new Update(
|
||||
'traces',
|
||||
json_encode(['@id' => '/traces/' . $trace->getUuid(), 'status' => $trace->getStatus(), 'progress' => $trace->getProgress() / 100])
|
||||
);
|
||||
$this->hub->publish($update);
|
||||
|
||||
$this->logger->info('Evento Mercure disparado. Cambio en el estado de una traza/log: ', [
|
||||
'client' => $trace->getUuid(),
|
||||
'status' => $trace->getStatus(),
|
||||
'progress' => $trace->getProgress(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use ApiPlatform\Symfony\EventListener\EventPriorities;
|
||||
use App\Dto\Output\ClientOutput;
|
||||
use App\Entity\Client;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\ViewEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
class MercureSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
private readonly HubInterface $hub
|
||||
)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::VIEW => ['onKernelView', EventPriorities::POST_WRITE],
|
||||
];
|
||||
}
|
||||
|
||||
public function onKernelView(ViewEvent $event): void
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
$method = $request->getMethod();
|
||||
|
||||
$clientOutput = $event->getControllerResult();
|
||||
|
||||
if (!in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$method = $event->getRequest()->getMethod();
|
||||
|
||||
if (!$clientOutput instanceof ClientOutput || (Request::METHOD_POST !== $method && Request::METHOD_PUT !== $method && Request::METHOD_PATCH !== $method)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Client $client */
|
||||
$client = $clientOutput->getEntity();
|
||||
|
||||
$update = new Update(
|
||||
'clients',
|
||||
json_encode(['@id' => '/clients/'.$client->getUuid(), 'status' => $client->getStatus()])
|
||||
);
|
||||
$this->hub->publish($update);
|
||||
|
||||
$this->logger->info('Evento Mercure disparado', [
|
||||
'method' => $method,
|
||||
'path' => $request->getPathInfo()
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ final class UserFactory extends ModelFactory
|
|||
return [
|
||||
'password' => $hash,
|
||||
'roles' => [],
|
||||
'groupsView' => 'card',
|
||||
'username' => self::faker()->text(180),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ class CustomLineFormatter extends LineFormatter
|
|||
'component' => 'ogcore',
|
||||
'params' => $record['context'],
|
||||
'desc' => $record['message'],
|
||||
'datetime' => $record['datetime']->format('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
return json_encode($output, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
|
|
|
@ -23,6 +23,7 @@ class JWTCreatedListener
|
|||
$payload['username'] = $user->getUsername();
|
||||
$payload['uuid'] = $user->getUuid();
|
||||
$payload['roles'] = $user->getRoles();
|
||||
$payload['groupsView'] = $user->getGroupsView();
|
||||
|
||||
$event->setData($payload);
|
||||
}
|
||||
|
|
|
@ -5,27 +5,20 @@ namespace App\Model;
|
|||
final class ClientStatus
|
||||
{
|
||||
public const string OFF = 'off';
|
||||
|
||||
public const string INITIALIZING = 'initializing';
|
||||
|
||||
public const string TURNING_OFF = 'turning-off';
|
||||
public const string OG_LIVE = 'og-live';
|
||||
|
||||
public const string BUSY = 'busy';
|
||||
|
||||
public const string LINUX = 'linux';
|
||||
|
||||
public const string LINUX_SESSION = 'linux-session';
|
||||
|
||||
public const string MACOS = 'macos';
|
||||
|
||||
public const string MACOS_SESSION = 'macos-session';
|
||||
|
||||
public const string WINDOWS = 'windows';
|
||||
|
||||
public const string WINDOWS_SESSION = 'windows-session';
|
||||
|
||||
private const array CLIENT_STATUSES = [
|
||||
self::OFF => 'Apagado',
|
||||
self::TURNING_OFF => 'Apagando',
|
||||
self::INITIALIZING => 'Inicializando',
|
||||
self::OG_LIVE => 'OG Live',
|
||||
self::BUSY => 'Ocupado',
|
||||
|
|
|
@ -8,6 +8,7 @@ final class CommandTypes
|
|||
public const string RESTORE_IMAGE = 'restore-image';
|
||||
public const string CREATE_IMAGE = 'create-image';
|
||||
public const string CREATE_IMAGE_AUX_FILE = 'create-image-aux-file';
|
||||
public const string BACKUP_IMAGE = 'backup-image';
|
||||
public const string IMPORT_IMAGE = 'import-image';
|
||||
public const string EXPORT_IMAGE = 'export-image';
|
||||
public const string TRANSFER_IMAGE = 'transfer-image';
|
||||
|
@ -25,6 +26,7 @@ final class CommandTypes
|
|||
self::RESTORE_IMAGE => 'Update Cache',
|
||||
self::CREATE_IMAGE => 'Create Image',
|
||||
self::CREATE_IMAGE_AUX_FILE => 'Crear fichero auxiliar en repositorio',
|
||||
self::BACKUP_IMAGE => 'Crear backup de imagen',
|
||||
self::IMPORT_IMAGE => 'Importar imagen',
|
||||
self::EXPORT_IMAGE => 'Exportar imagen',
|
||||
self::POWER_ON => 'Encender',
|
||||
|
|
|
@ -11,6 +11,7 @@ final class ImageStatus
|
|||
public const string TRASH = 'trash';
|
||||
public const string FAILED = 'failed';
|
||||
public const string TRANSFERRING = 'transferring';
|
||||
public const string BACKUP = 'backup';
|
||||
|
||||
private const array STATUS = [
|
||||
self::PENDING => 'Pendiente',
|
||||
|
|
|
@ -8,12 +8,14 @@ final class TraceStatus
|
|||
public const string IN_PROGRESS = 'in-progress';
|
||||
public const string SUCCESS = 'success';
|
||||
public const string FAILED = 'failed';
|
||||
public const string CANCELLED = 'cancelled';
|
||||
|
||||
private const array STATUS = [
|
||||
self::PENDING => 'Pendiente',
|
||||
self::IN_PROGRESS => 'En progreso',
|
||||
self::SUCCESS => 'Finalizado con éxito',
|
||||
self::FAILED => 'Fallido',
|
||||
self::CANCELLED => 'Cancelado',
|
||||
];
|
||||
|
||||
public static function getStatus(): array
|
||||
|
|
|
@ -16,23 +16,24 @@ class ClientRepository extends AbstractRepository
|
|||
parent::__construct($registry, Client::class);
|
||||
}
|
||||
|
||||
public function findClientsByOrganizationalUnitAndDescendants(int $organizationalUnitId): array
|
||||
public function findClientsByOrganizationalUnitAndDescendants(int $organizationalUnitId, array $filters = []): array
|
||||
{
|
||||
$query = $this->getEntityManager()->createQuery(
|
||||
$entityManager = $this->getEntityManager();
|
||||
|
||||
$query = $entityManager->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(
|
||||
$query = $entityManager->createQuery(
|
||||
'SELECT o.id
|
||||
FROM App\Entity\OrganizationalUnit o
|
||||
WHERE o.id = :id OR o.path LIKE :path'
|
||||
|
@ -42,13 +43,122 @@ class ClientRepository extends AbstractRepository
|
|||
|
||||
$unitIds = array_column($query->getArrayResult(), 'id');
|
||||
|
||||
$query = $this->getEntityManager()->createQuery(
|
||||
'SELECT c
|
||||
FROM App\Entity\Client c
|
||||
WHERE c.organizationalUnit IN (:unitIds)'
|
||||
)
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$qb->select('c')
|
||||
->from('App\Entity\Client', 'c')
|
||||
->where('c.organizationalUnit IN (:unitIds)')
|
||||
->setParameter('unitIds', $unitIds);
|
||||
|
||||
return $query->getResult();
|
||||
foreach ($filters as $key => $value) {
|
||||
if ($key === 'order' || $key === 'page' || $key === 'itemsPerPage' || $key === 'organizationalUnit.id') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === 'exists') {
|
||||
foreach ($value as $field => $existsValue) {
|
||||
if ($existsValue === 'true') {
|
||||
$qb->andWhere("c.$field IS NOT NULL");
|
||||
} elseif ($existsValue === 'false') {
|
||||
$qb->andWhere("c.$field IS NULL");
|
||||
}
|
||||
}
|
||||
} elseif ($key === 'name') {
|
||||
// Aplicar búsqueda con LIKE para el campo "name"
|
||||
$qb->andWhere("c.name LIKE :name")
|
||||
->setParameter('name', "%$value%");
|
||||
} elseif ($key === 'query') {
|
||||
// Búsqueda en múltiples campos (name, ip, mac)
|
||||
$qb->andWhere($qb->expr()->orX(
|
||||
$qb->expr()->like('c.name', ':searchQuery'),
|
||||
$qb->expr()->like('c.ip', ':searchQuery'),
|
||||
$qb->expr()->like('c.mac', ':searchQuery')
|
||||
))->setParameter('searchQuery', "%$value%");
|
||||
} else {
|
||||
// Búsqueda exacta para otros campos
|
||||
$qb->andWhere("c.$key = :$key")->setParameter($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (isset($filters['order']) && is_array($filters['order'])) {
|
||||
foreach ($filters['order'] as $field => $direction) {
|
||||
if (in_array(strtolower($direction), ['asc', 'desc'])) {
|
||||
$qb->addOrderBy("c.$field", $direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($filters['page']) && isset($filters['itemsPerPage'])) {
|
||||
$page = max(1, (int) $filters['page']);
|
||||
$itemsPerPage = max(1, (int) $filters['itemsPerPage']);
|
||||
$qb->setFirstResult(($page - 1) * $itemsPerPage)
|
||||
->setMaxResults($itemsPerPage);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function countClientsByOrganizationalUnitAndDescendants(int $organizationalUnitId, array $filters = []): int
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
|
||||
// Obtener el path de la unidad organizativa raíz
|
||||
$query = $entityManager->createQuery(
|
||||
'SELECT o.path FROM App\Entity\OrganizationalUnit o WHERE o.id = :id'
|
||||
)->setParameter('id', $organizationalUnitId);
|
||||
|
||||
$result = $query->getOneOrNullResult();
|
||||
if (!$result) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$path = $result['path'] . '/%';
|
||||
|
||||
$query = $entityManager->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');
|
||||
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$qb->select('COUNT(c.id)')
|
||||
->from('App\Entity\Client', 'c')
|
||||
->where('c.organizationalUnit IN (:unitIds)')
|
||||
->setParameter('unitIds', $unitIds);
|
||||
|
||||
foreach ($filters as $key => $value) {
|
||||
if ($key === 'order' || $key === 'page' || $key === 'itemsPerPage' || $key === 'organizationalUnit.id') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === 'exists') {
|
||||
foreach ($value as $field => $existsValue) {
|
||||
if ($existsValue === 'true') {
|
||||
$qb->andWhere("c.$field IS NOT NULL");
|
||||
} elseif ($existsValue === 'false') {
|
||||
$qb->andWhere("c.$field IS NULL");
|
||||
}
|
||||
}
|
||||
} elseif ($key === 'name') {
|
||||
$qb->andWhere("c.name LIKE :name")
|
||||
->setParameter('name', "%$value%");
|
||||
} elseif ($key === 'query') {
|
||||
$qb->andWhere($qb->expr()->orX(
|
||||
$qb->expr()->like('c.name', ':searchQuery'),
|
||||
$qb->expr()->like('c.ip', ':searchQuery'),
|
||||
$qb->expr()->like('c.mac', ':searchQuery')
|
||||
))->setParameter('searchQuery', "%$value%");
|
||||
} else {
|
||||
$qb->andWhere("c.$key = :$key")->setParameter($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -39,23 +39,35 @@ readonly class ClientProvider implements ProviderInterface
|
|||
|
||||
public function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object
|
||||
{
|
||||
$organizationalUnitId = $context['filters']['organizationalUnit.id'] ?? null;
|
||||
$filters = $context['filters'] ?? [];
|
||||
|
||||
if ($organizationalUnitId) {
|
||||
$clients = $this->clientRepository->findClientsByOrganizationalUnitAndDescendants((int) $organizationalUnitId);
|
||||
if (isset($filters['organizationalUnit.id'])) {
|
||||
$organizationalUnitId = (int) $filters['organizationalUnit.id'];
|
||||
|
||||
$totalClients = $this->clientRepository->countClientsByOrganizationalUnitAndDescendants($organizationalUnitId, $filters);
|
||||
$clients = $this->clientRepository->findClientsByOrganizationalUnitAndDescendants($organizationalUnitId, $filters);
|
||||
|
||||
$items = new \ArrayObject();
|
||||
foreach ($clients as $client) {
|
||||
$items[] = new ClientOutput($client);
|
||||
}
|
||||
|
||||
return new TraversablePaginator($items, 1, count($clients), count($clients));
|
||||
}
|
||||
return new TraversablePaginator($items, $filters['page'] ?? 1, $filters['itemsPerPage'] ?? count($clients), $totalClients);
|
||||
} else {
|
||||
$paginator = $this->collectionProvider->provide($operation, $uriVariables, $context);
|
||||
|
||||
return $this->collectionProvider->provide($operation, $uriVariables, $context);
|
||||
$items = new \ArrayObject();
|
||||
foreach ($paginator->getIterator() as $item) {
|
||||
$items[] = new ClientOutput($item);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
99
symfony.lock
99
symfony.lock
|
@ -13,6 +13,18 @@
|
|||
"src/ApiResource/.gitignore"
|
||||
]
|
||||
},
|
||||
"dama/doctrine-test-bundle": {
|
||||
"version": "8.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "7.2",
|
||||
"ref": "896306d79d4ee143af9eadf9b09fd34a8c391b70"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/dama_doctrine_test_bundle.yaml"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.13",
|
||||
"recipe": {
|
||||
|
@ -27,6 +39,18 @@
|
|||
"src/Repository/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-fixtures-bundle": {
|
||||
"version": "3.7",
|
||||
"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": {
|
||||
|
@ -78,6 +102,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": {
|
||||
|
@ -143,6 +181,27 @@
|
|||
"src/Kernel.php"
|
||||
]
|
||||
},
|
||||
"symfony/maker-bundle": {
|
||||
"version": "1.61",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/mercure-bundle": {
|
||||
"version": "0.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "0.3",
|
||||
"ref": "528285147494380298f8f991ee8c47abebaf79db"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/mercure.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/monolog-bundle": {
|
||||
"version": "3.10",
|
||||
"recipe": {
|
||||
|
@ -155,6 +214,21 @@
|
|||
"config/packages/monolog.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/phpunit-bridge": {
|
||||
"version": "7.2",
|
||||
"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 +292,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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,9 @@ class ClientTest extends AbstractTest
|
|||
{
|
||||
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
|
||||
|
||||
ClientFactory::createMany(10);
|
||||
$ou = OrganizationalUnitFactory::new(['type' => OrganizationalUnitTypes::ORGANIZATIONAL_UNIT])->create()->_disableAutoRefresh();
|
||||
$ou->_save();
|
||||
ClientFactory::createOne(['organizationalUnit' => $ou]);
|
||||
|
||||
$this->createClientWithCredentials()->request('GET', '/clients');
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
|
@ -51,7 +53,7 @@ class ClientTest extends AbstractTest
|
|||
'@context' => '/contexts/Client',
|
||||
'@id' => '/clients',
|
||||
'@type' => 'hydra:Collection',
|
||||
'hydra:totalItems' => 10,
|
||||
'hydra:totalItems' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ class UserTest extends AbstractTest
|
|||
'username' => self::USER_CREATE,
|
||||
'password' => '12345678',
|
||||
'enabled' => true,
|
||||
'groupsView' => 'card'
|
||||
]]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(201);
|
||||
|
|
Loading…
Reference in New Issue