diff --git a/.env.test b/.env.test index d0486867..1bc47cf3 100644 --- a/.env.test +++ b/.env.test @@ -3,3 +3,5 @@ KERNEL_CLASS='App\Kernel' APP_SECRET='$ecretf0rt3st' SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther +VAPID_PUBLIC_KEY='test' +VAPID_PRIVATE_KEY='test' diff --git a/screenshots/7.9.1/original/original-subscriptions.png b/screenshots/7.9.1/original/original-subscriptions.png index 24c2b207..0e2bd8a1 100644 Binary files a/screenshots/7.9.1/original/original-subscriptions.png and b/screenshots/7.9.1/original/original-subscriptions.png differ diff --git a/screenshots/7.9.1/resized/resized-subscriptions.png b/screenshots/7.9.1/resized/resized-subscriptions.png index 222222d0..4c2fe645 100644 Binary files a/screenshots/7.9.1/resized/resized-subscriptions.png and b/screenshots/7.9.1/resized/resized-subscriptions.png differ diff --git a/src/Command/SendNotificationsCommand.php b/src/Command/SendNotificationsCommand.php index 91130c25..2e7bee98 100644 --- a/src/Command/SendNotificationsCommand.php +++ b/src/Command/SendNotificationsCommand.php @@ -53,59 +53,61 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($notifications as $notification) { foreach ($subscriptions as $subscription) { - switch ($subscription->getType()) { - case AppSubscriptionModel::TYPE_PUSH: - $publicKey = $subscription->getPublicKey(); - $authenticationSecret = $subscription->getAuthenticationSecret(); - $contentEncoding = $subscription->getContentEncoding(); - - if ($publicKey && $authenticationSecret && $contentEncoding) { - $payload = [ - 'tag' => uniqid('', true), - 'title' => $notification->getTitle(), - 'body' => $notification->getBody(), - 'icon' => $notification->getIcon(), - ]; - - $subscription = Subscription::create([ - 'endpoint' => $subscription->getEndpoint(), - 'publicKey' => $publicKey, - 'authToken' => $authenticationSecret, - 'contentEncoding' => $contentEncoding, - ]); - - $webPush->queueNotification($subscription, json_encode($payload)); - } - break; - - case AppSubscriptionModel::TYPE_SLACK: - try { - $options = [ - 'json' => [ - 'text' => $notification->getTitle()."\r\n".$notification->getBody(), - ], - ]; - $this->client->request('POST', $subscription->getEndpoint(), $options); - } catch (TransportException $e) { - $output->writeln('Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().''); - } - break; - - case AppSubscriptionModel::TYPE_TEAMS: - try { - $options = [ - 'json' => [ - '@context' => 'https://schema.org/extensions', - '@type' => 'MessageCard', + if (true === in_array($notification->getType(), $subscription->getNotifications())) { + switch ($subscription->getType()) { + case AppSubscriptionModel::TYPE_PUSH: + $publicKey = $subscription->getPublicKey(); + $authenticationSecret = $subscription->getAuthenticationSecret(); + $contentEncoding = $subscription->getContentEncoding(); + + if ($publicKey && $authenticationSecret && $contentEncoding) { + $payload = [ + 'tag' => uniqid('', true), 'title' => $notification->getTitle(), - 'text' => $notification->getBody(), - ], - ]; - $this->client->request('POST', $subscription->getEndpoint(), $options); - } catch (TransportException $e) { - $output->writeln('Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().''); - } - break; + 'body' => $notification->getBody(), + 'icon' => $notification->getIcon(), + ]; + + $subscription = Subscription::create([ + 'endpoint' => $subscription->getEndpoint(), + 'publicKey' => $publicKey, + 'authToken' => $authenticationSecret, + 'contentEncoding' => $contentEncoding, + ]); + + $webPush->queueNotification($subscription, json_encode($payload)); + } + break; + + case AppSubscriptionModel::TYPE_SLACK: + try { + $options = [ + 'json' => [ + 'text' => $notification->getTitle()."\r\n".$notification->getBody(), + ], + ]; + $this->client->request('POST', $subscription->getEndpoint(), $options); + } catch (TransportException $e) { + $output->writeln('Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().''); + } + break; + + case AppSubscriptionModel::TYPE_TEAMS: + try { + $options = [ + 'json' => [ + '@context' => 'https://schema.org/extensions', + '@type' => 'MessageCard', + 'title' => $notification->getTitle(), + 'text' => $notification->getBody(), + ], + ]; + $this->client->request('POST', $subscription->getEndpoint(), $options); + } catch (TransportException $e) { + $output->writeln('Message failed to sent for subscription '.$subscription->getEndpoint().': '.$e->getMessage().''); + } + break; + } } } } diff --git a/src/Controller/AppSubscriptionsController.php b/src/Controller/AppSubscriptionsController.php index c2740cde..034e72a5 100644 --- a/src/Controller/AppSubscriptionsController.php +++ b/src/Controller/AppSubscriptionsController.php @@ -3,9 +3,12 @@ namespace App\Controller; use App\Controller\AbstractAppController; +use App\Exception\CallException; +use App\Form\Type\AppSubscriptionType; use App\Model\CallRequestModel; use App\Manager\AppSubscriptionManager; use App\Manager\AppNotificationManager; +use App\Model\AppNotificationModel; use App\Model\AppSubscriptionModel; use DeviceDetector\DeviceDetector; use Minishlink\WebPush\WebPush; @@ -16,31 +19,29 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Security\Core\Security; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; /** * @Route("/admin") */ class AppSubscriptionsController extends AbstractAppController { - public function __construct(AppSubscriptionManager $appSubscriptionManager, AppNotificationManager $appNotificationManager, Security $security) + public function __construct(AppSubscriptionManager $appSubscriptionManager, AppNotificationManager $appNotificationManager, Security $security, string $vapidPublicKey, string $vapidPrivateKey) { $this->appSubscriptionManager = $appSubscriptionManager; $this->appNotificationManager = $appNotificationManager; $this->user = $security->getUser(); + $this->vapidPublicKey = $vapidPublicKey; + $this->vapidPrivateKey = $vapidPrivateKey; } /** * @Route("/subscriptions", name="app_subscriptions") */ - public function index(Request $request, string $vapidPublicKey, string $vapidPrivateKey): Response + public function index(Request $request): Response { $this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global'); - if ('' == $vapidPublicKey || '' == $vapidPrivateKey) { - $this->addFlash('warning', 'Run bin/console app:generate-vapid'); - $this->addFlash('warning', 'Edit VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY in .env file'); - } - if (false === $this->appNotificationManager->infoFileExists()) { $this->addFlash('warning', 'Add to cron */5 * * * * cd '.str_replace('/public', '', $request->getBasePath()).' && bin/console app:send-notifications'); } @@ -52,48 +53,101 @@ public function index(Request $request, string $vapidPublicKey, string $vapidPri return $this->renderAbstract($request, 'Modules/subscription/subscription_index.html.twig', [ 'subscriptions' => $subscriptions, - 'applicationServerKey' => $vapidPublicKey, + 'applicationServerKey' => $this->vapidPublicKey, ]); } /** - * @Route("/subscriptions/create", name="app_subscriptions_create") + * @Route("/subscriptions/create/{type}", name="app_subscriptions_create") */ - public function create(Request $request): JsonResponse + public function create(Request $request, string $type): Response { $this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global'); - $json = []; + if (false === in_array($type, AppSubscriptionModel::getTypes())) { + throw new AccessDeniedException(); + } + + if (AppSubscriptionModel::TYPE_PUSH == $type) { + if ('' == $this->vapidPublicKey || '' == $this->vapidPrivateKey) { + $this->addFlash('warning', 'Run bin/console app:generate-vapid'); + $this->addFlash('warning', 'Edit VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY in .env file'); + + throw new AccessDeniedException(); + } + } + + $dd = new DeviceDetector($request->headers->get('User-Agent')); + $dd->skipBotDetection(); + $dd->parse(); - if ($content = $request->getContent()) { - $content = json_decode($content, true); + $client = $dd->getClient(); + $os = $dd->getOs(); - $dd = new DeviceDetector($request->headers->get('User-Agent')); - $dd->skipBotDetection(); - $dd->parse(); + $subscription = new AppSubscriptionModel(); + $subscription->setUserId($this->user->getId()); + $subscription->setType($type); + $subscription->setIp($request->getClientIp()); + $subscription->setOs($os ? $os['name'].' '.$os['version'] : false); + $subscription->setClient($client ? $client['name'].' '.$client['version'] : false); + $subscription->setNotifications(AppNotificationModel::getTypes()); - $client = $dd->getClient(); - $os = $dd->getOs(); + $form = $this->createForm(AppSubscriptionType::class, $subscription, ['type' => $type]); - $subscription = new AppSubscriptionModel(); - $subscription->setUserId($this->user->getId()); - $subscription->setType($content['type']); - $subscription->setEndpoint($content['endpoint']); - if (AppSubscriptionModel::TYPE_PUSH == $content['type']) { - $subscription->setPublicKey($content['public_key']); - $subscription->setAuthenticationSecret($content['authentication_secret']); - $subscription->setContentEncoding($content['content_encoding']); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + try { + $callResponse = $this->appSubscriptionManager->send($subscription); + + $this->addFlash('info', json_encode($callResponse->getContent())); + + return $this->redirectToRoute('app_subscriptions'); + } catch (CallException $e) { + $this->addFlash('danger', $e->getMessage()); } - $subscription->setIp($request->getClientIp()); - $subscription->setOs($os ? $os['name'].' '.$os['version'] : false); - $subscription->setClient($client ? $client['name'].' '.$client['version'] : false); + } + + return $this->renderAbstract($request, 'Modules/subscription/subscription_create.html.twig', [ + 'form' => $form->createView(), + 'type' => $type, + 'applicationServerKey' => $this->vapidPublicKey, + ]); + } + + /** + * @Route("/subscriptions/{id}/update", name="app_subscriptions_update") + */ + public function update(Request $request, string $id): Response + { + $this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global'); - $callResponse = $this->appSubscriptionManager->send($subscription); + $subscription = $this->appSubscriptionManager->getById($id); - return new JsonResponse(json_encode($callResponse->getContent()), JsonResponse::HTTP_OK); + if (null === $subscription) { + throw new NotFoundHttpException(); } - return new JsonResponse($json, JsonResponse::HTTP_OK); + $form = $this->createForm(AppSubscriptionType::class, $subscription, ['type' => $subscription->getType(), 'context' => 'update']); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + try { + $callResponse = $this->appSubscriptionManager->send($subscription); + + $this->addFlash('info', json_encode($callResponse->getContent())); + + return $this->redirectToRoute('app_subscriptions'); + } catch (CallException $e) { + $this->addFlash('danger', $e->getMessage()); + } + } + + return $this->renderAbstract($request, 'Modules/subscription/subscription_update.html.twig', [ + 'subscription' => $subscription, + 'form' => $form->createView(), + ]); } /** @@ -119,7 +173,7 @@ public function delete(Request $request, string $id): Response /** * @Route("/subscriptions/{id}/test", name="app_subscriptions_test") */ - public function test(Request $request, string $id, string $vapidPublicKey, string $vapidPrivateKey): JsonResponse + public function test(Request $request, string $id): JsonResponse { $this->denyAccessUnlessGranted('APP_SUBSCRIPTIONS', 'global'); @@ -129,6 +183,8 @@ public function test(Request $request, string $id, string $vapidPublicKey, strin throw new NotFoundHttpException(); } + $this->clusterHealth = $this->elasticsearchClusterManager->getClusterHealth(); + $json = []; switch ($subscription->getType()) { @@ -136,8 +192,8 @@ public function test(Request $request, string $id, string $vapidPublicKey, strin $apiKeys = [ 'VAPID' => [ 'subject' => 'https://github.com/stephanediondev/elasticsearch-admin', - 'publicKey' => $vapidPublicKey, - 'privateKey' => $vapidPrivateKey, + 'publicKey' => $this->vapidPublicKey, + 'privateKey' => $this->vapidPrivateKey, ], ]; @@ -150,7 +206,7 @@ public function test(Request $request, string $id, string $vapidPublicKey, strin if ($publicKey && $authenticationSecret && $contentEncoding) { $payload = [ 'tag' => uniqid('', true), - 'title' => 'test', + 'title' => $this->clusterHealth['cluster_name'].': test', 'body' => 'test', 'icon' => 'favicon-green-144.png', ]; diff --git a/src/Controller/ElasticsearchRepositoryController.php b/src/Controller/ElasticsearchRepositoryController.php index 4dc1c150..1368b4a6 100644 --- a/src/Controller/ElasticsearchRepositoryController.php +++ b/src/Controller/ElasticsearchRepositoryController.php @@ -53,6 +53,10 @@ public function create(Request $request, string $type): Response { $this->denyAccessUnlessGranted('REPOSITORIES_CREATE', 'global'); + if (false === in_array($type, ElasticsearchRepositoryModel::getTypes())) { + throw new AccessDeniedException(); + } + if ('s3' == $type && false === $this->callManager->hasPlugin('repository-s3')) { throw new AccessDeniedException(); } diff --git a/src/Form/Type/AppSubscriptionType.php b/src/Form/Type/AppSubscriptionType.php new file mode 100644 index 00000000..a3157c4e --- /dev/null +++ b/src/Form/Type/AppSubscriptionType.php @@ -0,0 +1,132 @@ +appSubscriptionManager = $appSubscriptionManager; + $this->translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $fields = []; + + if ('create' == $options['context']) { + $fields[] = 'endpoint'; + + if (AppSubscriptionModel::TYPE_PUSH == $options['type']) { + $fields[] = 'public_key'; + $fields[] = 'authentication_secret'; + $fields[] = 'content_encoding'; + } + } + + $fields[] = 'notifications'; + + foreach ($fields as $field) { + switch ($field) { + case 'endpoint': + $builder->add('endpoint', TextType::class, [ + 'label' => 'endpoint', + 'required' => true, + 'constraints' => [ + new NotBlank(), + ], + ]); + break; + case 'public_key': + $builder->add('public_key', TextType::class, [ + 'label' => 'public_key', + 'required' => true, + 'constraints' => [ + new NotBlank(), + ], + 'attr' => [ + 'autocomplete' => 'nope', + ], + ]); + break; + case 'authentication_secret': + $builder->add('authentication_secret', PasswordType::class, [ + 'label' => 'authentication_secret', + 'required' => true, + 'constraints' => [ + new NotBlank(), + ], + 'attr' => [ + 'autocomplete' => 'new-password', + ], + ]); + break; + case 'content_encoding': + $builder->add('content_encoding', TextType::class, [ + 'label' => 'content_encoding', + 'required' => true, + 'constraints' => [ + new NotBlank(), + ], + ]); + break; + case 'notifications': + $builder->add('notifications', ChoiceType::class, [ + 'multiple' => true, + 'choices' => AppNotificationModel::getTypes(), + 'choice_label' => function ($choice, $key, $value) { + return $value; + }, + 'choice_translation_domain' => false, + 'label' => 'notifications', + 'required' => false, + ]); + break; + } + } + + $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { + $form = $event->getForm(); + + if ($form->has('endpoint') && $form->get('endpoint')->getData()) { + $user = $this->appSubscriptionManager->getByEndpoint($form->get('endpoint')->getData()); + + if ($user) { + $form->get('endpoint')->addError(new FormError( + $this->translator->trans('endpoint_already_used') + )); + } + } + }); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => AppSubscriptionModel::class, + 'type' => false, + 'context' => 'create', + ]); + } + + public function getBlockPrefix() + { + return 'data'; + } +} diff --git a/src/Manager/AppNotificationManager.php b/src/Manager/AppNotificationManager.php index 963b3e11..45ddab17 100644 --- a/src/Manager/AppNotificationManager.php +++ b/src/Manager/AppNotificationManager.php @@ -113,6 +113,7 @@ public function compareInfo(array $previousInfo, array $lastInfo): array if (true === isset($previousInfo['cluster_health']) && $previousInfo['cluster_health'] && $previousInfo['cluster_health'] != $lastInfo['cluster_health']) { $notification = new AppNotificationModel(); + $notification->setType(AppNotificationModel::TYPE_CLUSTER_HEALTH); $notification->setTitle($this->clusterHealth['cluster_name'].': health'); $notification->setBody(ucfirst($lastInfo['cluster_health'])); $notification->setIcon('favicon-'.$lastInfo['cluster_health'].'-144.png'); @@ -124,6 +125,7 @@ public function compareInfo(array $previousInfo, array $lastInfo): array $nodesDown = array_diff($previousInfo['nodes'], $lastInfo['nodes']); foreach ($nodesDown as $nodeDown) { $notification = new AppNotificationModel(); + $notification->setType(AppNotificationModel::TYPE_NODE_DOWN); $notification->setTitle($this->clusterHealth['cluster_name'].': node down'); $notification->setBody($nodeDown); $notification->setIcon('favicon-red-144.png'); @@ -134,6 +136,7 @@ public function compareInfo(array $previousInfo, array $lastInfo): array $nodesUp = array_diff($lastInfo['nodes'], $previousInfo['nodes']); foreach ($nodesUp as $nodeUp) { $notification = new AppNotificationModel(); + $notification->setType(AppNotificationModel::TYPE_NODE_UP); $notification->setTitle($this->clusterHealth['cluster_name'].': node up'); $notification->setBody($nodeUp); $notification->setIcon('favicon-green-144.png'); @@ -146,6 +149,7 @@ public function compareInfo(array $previousInfo, array $lastInfo): array foreach ($lastInfo['disk_threshold'] as $node => $values) { if (true === isset($previousInfo['disk_threshold'][$node]) && $previousInfo['disk_threshold'][$node]['watermark'] != $values['watermark']) { $notification = new AppNotificationModel(); + $notification->setType(AppNotificationModel::TYPE_DISK_THRESHOLD); $notification->setTitle($this->clusterHealth['cluster_name'].': disk threshold'); $notification->setBody($node.' '.$values['percent'].'%'); $notification->setIcon('favicon-'.$this->getColor($values['watermark']).'-144.png'); @@ -157,6 +161,7 @@ public function compareInfo(array $previousInfo, array $lastInfo): array if (true === isset($previousInfo['license']) && $previousInfo['license'] && $previousInfo['license'] != $lastInfo['license']) { $notification = new AppNotificationModel(); + $notification->setType(AppNotificationModel::TYPE_LICENSE); $notification->setTitle($this->clusterHealth['cluster_name'].': license'); switch ($lastInfo['license']) { case 'license_ok': @@ -179,6 +184,7 @@ public function compareInfo(array $previousInfo, array $lastInfo): array if (true === isset($previousInfo['versions']) && $previousInfo['versions'] && count($previousInfo['versions']) != count($lastInfo['versions'])) { $notification = new AppNotificationModel(); + $notification->setType(AppNotificationModel::TYPE_VERSION); $notification->setTitle($this->clusterHealth['cluster_name'].': ES version'); if (1 == count($lastInfo['versions'])) { $notification->setBody('One version ('.$lastInfo['versions'][0].')'); diff --git a/src/Manager/AppSubscriptionManager.php b/src/Manager/AppSubscriptionManager.php index 4dd7d7b5..b7c9144f 100644 --- a/src/Manager/AppSubscriptionManager.php +++ b/src/Manager/AppSubscriptionManager.php @@ -35,6 +35,32 @@ public function getById(string $id): ?AppSubscriptionModel return $subscriptionModel; } + public function getByEndpoint(string $endpoint): ?AppSubscriptionModel + { + $subscriptionModel = null; + + $query = [ + 'q' => 'endpoint:"'.$endpoint.'"', + ]; + $callRequest = new CallRequestModel(); + $callRequest->setPath('/.elasticsearch-admin-subscriptions/_search'); + $callRequest->setQuery($query); + $callResponse = $this->callManager->call($callRequest); + $results = $callResponse->getContent(); + + if ($results && 1 == count($results['hits']['hits'])) { + foreach ($results['hits']['hits'] as $row) { + $subscription = ['id' => $row['_id']]; + $subscription = array_merge($subscription, $row['_source']); + + $subscriptionModel = new AppSubscriptionModel(); + $subscriptionModel->convert($subscription); + } + } + + return $subscriptionModel; + } + public function getAll(?array $query = []): array { $query['size'] = 1000; @@ -72,10 +98,20 @@ public function send(AppSubscriptionModel $subscriptionModel): CallResponseModel $json = $subscriptionModel->getJson(); $callRequest = new CallRequestModel(); $callRequest->setMethod('POST'); - if (true === $this->callManager->hasFeature('_doc_as_type')) { - $callRequest->setPath('/.elasticsearch-admin-subscriptions/_doc'); + if ($subscriptionModel->getId()) { + $callRequest->setMethod('PUT'); + if (true === $this->callManager->hasFeature('_doc_as_type')) { + $callRequest->setPath('/.elasticsearch-admin-subscriptions/_doc/'.$subscriptionModel->getId()); + } else { + $callRequest->setPath('/.elasticsearch-admin-subscriptions/doc/'.$subscriptionModel->getId()); + } } else { - $callRequest->setPath('/.elasticsearch-admin-subscriptions/doc/'); + $callRequest->setMethod('POST'); + if (true === $this->callManager->hasFeature('_doc_as_type')) { + $callRequest->setPath('/.elasticsearch-admin-subscriptions/_doc'); + } else { + $callRequest->setPath('/.elasticsearch-admin-subscriptions/doc/'); + } } $callRequest->setJson($json); $callRequest->setQuery(['refresh' => 'true']); diff --git a/src/Model/AppNotificationModel.php b/src/Model/AppNotificationModel.php index 783bb1aa..33c04b61 100644 --- a/src/Model/AppNotificationModel.php +++ b/src/Model/AppNotificationModel.php @@ -6,12 +6,33 @@ class AppNotificationModel extends AbstractAppModel { + const TYPE_CLUSTER_HEALTH = 'cluster_health'; + const TYPE_NODE_DOWN = 'node_down'; + const TYPE_NODE_UP = 'node_up'; + const TYPE_DISK_THRESHOLD = 'disk_threshold'; + const TYPE_LICENSE = 'license'; + const TYPE_VERSION = 'version'; + + private $type; + private $title; private $body; private $icon; + public function getType(): ?string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + public function getTitle(): ?string { return $this->title; @@ -47,4 +68,16 @@ public function setIcon(?string $icon): self return $this; } + + public static function getTypes() + { + return [ + self::TYPE_CLUSTER_HEALTH, + self::TYPE_NODE_DOWN, + self::TYPE_NODE_UP, + self::TYPE_DISK_THRESHOLD, + self::TYPE_LICENSE, + self::TYPE_VERSION, + ]; + } } diff --git a/src/Model/AppSubscriptionModel.php b/src/Model/AppSubscriptionModel.php index bbab1284..4be744d5 100644 --- a/src/Model/AppSubscriptionModel.php +++ b/src/Model/AppSubscriptionModel.php @@ -32,6 +32,8 @@ class AppSubscriptionModel extends AbstractAppModel private $client; + private $notifications = []; + private $createdAt; public function __construct() @@ -159,6 +161,19 @@ public function setClient(?string $client): self return $this; } + public function getNotifications(): ?array + { + return array_values($this->notifications); + } + + public function setNotifications($notifications): self + { + $this->notifications = $notifications; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; @@ -189,6 +204,9 @@ public function convert(?array $subscription): self $this->setIp($subscription['ip']); $this->setOs($subscription['os']); $this->setClient($subscription['client']); + if (true === isset($subscription['notifications']) && 0 < count($subscription['notifications'])) { + $this->setNotifications($subscription['notifications']); + } $this->setCreatedAt(new \Datetime($subscription['created_at'])); return $this; } @@ -208,6 +226,10 @@ public function getJson(): array 'created_at' => $this->getCreatedAt()->format('Y-m-d H:i:s'), ]; + if ($this->getNotifications()) { + $json['notifications'] = $this->getNotifications(); + } + return $json; } @@ -215,4 +237,15 @@ public function __toString(): string { return $this->id; } + + public static function getTypes() + { + return [ + self::TYPE_PUSH, + self::TYPE_EMAIL, + self::TYPE_SMS, + self::TYPE_SLACK, + self::TYPE_TEAMS, + ]; + } } diff --git a/src/Model/ElasticsearchRepositoryModel.php b/src/Model/ElasticsearchRepositoryModel.php index bbcf7a17..3f40cd71 100644 --- a/src/Model/ElasticsearchRepositoryModel.php +++ b/src/Model/ElasticsearchRepositoryModel.php @@ -172,16 +172,6 @@ public function setSettings(?array $settings): self return $this; } - public static function allowedTypes(): ?array - { - return [ - self::TYPE_FS => self::TYPE_FS, - self::TYPE_S3 => self::TYPE_S3, - self::TYPE_GCS => self::TYPE_GCS, - self::TYPE_AZURE => self::TYPE_AZURE, - ]; - } - public function convert(?array $repository): self { $this->setName($repository['name']); @@ -465,4 +455,14 @@ public function __toString(): string { return $this->name; } + + public static function getTypes(): ?array + { + return [ + self::TYPE_FS, + self::TYPE_S3, + self::TYPE_GCS, + self::TYPE_AZURE, + ]; + } } diff --git a/templates/Modules/subscription/subscription_create.html.twig b/templates/Modules/subscription/subscription_create.html.twig new file mode 100644 index 00000000..89983473 --- /dev/null +++ b/templates/Modules/subscription/subscription_create.html.twig @@ -0,0 +1,117 @@ +{% extends 'base.html.twig' %} +{% import 'Import/app_import.html.twig' as appImport %} + +{% block head_title %}{{ 'subscriptions'|trans }} - {{ ('create_subscription_' ~ type)|trans }}{% endblock %} + +{% block heading_1 %} + {{ appImport.heading({'level': 1, 'title': 'subscriptions'|trans}) }} +{% endblock %} + +{% block tabs %} + {% include 'Modules/subscription/subscription_tabs.html.twig' with {'active': 'create_subscription_' ~ type} %} +{% endblock %} + +{% block main_content %} + {% embed 'Embed/card_embed.html.twig' %} + {% import 'Import/app_import.html.twig' as appImport %} + {% block content %} + {{ appImport.heading({'level': 3, 'title': ('create_subscription_' ~ type)|trans}) }} + + {% embed 'Embed/buttons_embed.html.twig' %} + {% import 'Import/app_import.html.twig' as appImport %} + {% block content %} + {% if applicationServerKey and 'push' == type %} + + {{ 'allow_notifications'|trans }} + + {% endif %} + + {% if 'slack' == type %} + + {{ 'help'|trans }} + + {% endif %} + + {% if 'teams' == type %} + + {{ 'help'|trans }} + + {% endif %} + {% endblock %} + {% endembed %} + + {{ appImport.form({'form': form}) }} + {% endblock %} + {% endembed %} +{% endblock %} + +{% block scripts %} + {{ parent() }} + + {% if applicationServerKey and 'push' == type %} + + {% endif %} +{% endblock %} diff --git a/templates/Modules/subscription/subscription_index.html.twig b/templates/Modules/subscription/subscription_index.html.twig index db3fb6b5..38b45adb 100644 --- a/templates/Modules/subscription/subscription_index.html.twig +++ b/templates/Modules/subscription/subscription_index.html.twig @@ -7,139 +7,86 @@ {{ appImport.heading({'level': 1, 'title': 'subscriptions'|trans}) }} {% endblock %} +{% block tabs %} + {% include 'Modules/subscription/subscription_tabs.html.twig' with {'active': 'list'} %} +{% endblock %} + {% block main_content %}
-

{{ 'subscribe_note'|trans }}

+

{{ 'subscriptions_note'|trans }}

- {% if applicationServerKey %} - {% embed 'Embed/card_embed.html.twig' %} - {% import 'Import/app_import.html.twig' as appImport %} - {% block content %} - {{ appImport.heading({'level': 3, 'title': 'list'|trans, 'badge': {'title': subscriptions|length}}) }} + {% embed 'Embed/card_embed.html.twig' %} + {% import 'Import/app_import.html.twig' as appImport %} + {% block content %} + {{ appImport.heading({'level': 3, 'title': 'list'|trans, 'badge': {'title': subscriptions|length}}) }} - {% embed 'Embed/buttons_embed.html.twig' %} + {% if 0 < subscriptions|length %} + {% embed 'Embed/table_embed.html.twig' %} {% import 'Import/app_import.html.twig' as appImport %} - {% block content %} - - - - - - - - - {{ 'subscribe_push'|trans }} - + {% block thead %} + + {{ 'type'|trans }} + {{ 'os'|trans }} + {{ 'client'|trans }} + {{ 'ip'|trans }} + {{ 'notifications'|trans }} + {{ 'created_at'|trans }}{{ appImport.badge({'title': 'sort_desc'|trans, 'context': 'light'}) }} +   + {% endblock %} - {% endembed %} - - {% if 0 < subscriptions|length %} - {% embed 'Embed/table_embed.html.twig' %} - {% import 'Import/app_import.html.twig' as appImport %} - {% block thead %} + {% block tbody %} + {% for subscription in subscriptions %} - {{ 'type'|trans }} - {{ 'os'|trans }} - {{ 'client'|trans }} - {{ 'ip'|trans }} - {{ 'created_at'|trans }}{{ appImport.badge({'title': 'sort_desc'|trans, 'context': 'light'}) }} -   - - {% endblock %} - - {% block tbody %} - {% for subscription in subscriptions %} - - - {{ subscription.type }} - - - {{ subscription.os }} - - - {{ subscription.client }} - - - {{ subscription.ip }} - - - {{ subscription.createdAt|human_datetime }} - - - - {{ 'delete'|trans }} + + {{ ('subscription_' ~ subscription.type)|trans }} + + + {{ subscription.os }} + + + {{ subscription.client }} + + + {{ subscription.ip }} + + + {{ subscription.notifications|join(', ') }} + + + {{ subscription.createdAt|human_datetime }} + + + + {{ 'update'|trans }} + + + {{ appImport.buttonModal({ + 'id': 'SubscriptionDelete' ~ subscription.id, + 'title': 'delete'|trans, + 'body': ('subscription_' ~ subscription.type)|trans, + 'href': path('app_subscriptions_delete', {'id': subscription.id}), + }) }} + + {% if 'push' == subscription.type %} + + {{ 'unsubscribe'|trans }} - {% if 'push' == subscription.type %} - - {{ 'unsubscribe'|trans }} - - - {{ 'test'|trans }} - - {% endif %} - - - {% endfor %} - {% endblock %} - {% endembed %} - {% endif %} - {% endblock %} - {% endembed %} - {% endif %} + + {{ 'test'|trans }} + + {% endif %} + + + {% endfor %} + {% endblock %} + {% endembed %} + {% endif %} + {% endblock %} + {% endembed %} {% endblock %} {% block scripts %} @@ -147,49 +94,6 @@ {% if applicationServerKey %} {% endif %} diff --git a/templates/Modules/subscription/subscription_tabs.html.twig b/templates/Modules/subscription/subscription_tabs.html.twig new file mode 100644 index 00000000..16cead14 --- /dev/null +++ b/templates/Modules/subscription/subscription_tabs.html.twig @@ -0,0 +1,15 @@ +{% import 'Import/app_import.html.twig' as appImport %} + diff --git a/templates/Modules/subscription/subscription_update.html.twig b/templates/Modules/subscription/subscription_update.html.twig new file mode 100644 index 00000000..85162460 --- /dev/null +++ b/templates/Modules/subscription/subscription_update.html.twig @@ -0,0 +1,33 @@ +{% extends 'base.html.twig' %} +{% import 'Import/app_import.html.twig' as appImport %} + +{% block head_title %}{{ 'subscriptions'|trans }} - {{ subscription.id }}{% endblock %} + +{% block heading_1 %} + {{ appImport.heading({'level': 1, 'title': 'subscriptions'|trans, 'link': {'url': path('app_subscriptions')}}) }} +{% endblock %} + +{% block heading_2 %} + {{ appImport.heading({'level': 2, 'title': subscription.id}) }} +{% endblock %} + +{% block main_content %} + {% embed 'Embed/card_embed.html.twig' %} + {% import 'Import/app_import.html.twig' as appImport %} + {% block content %} + {{ appImport.heading({'level': 3, 'title': 'update'|trans}) }} + +

+ {{ 'type'|trans }}
+ {{ ('subscription_' ~ subscription.type)|trans }} +

+ +

+ {{ 'endpoint'|trans }}
+ {{ subscription.endpoint }} +

+ + {{ appImport.form({'form': form}) }} + {% endblock %} + {% endembed %} +{% endblock %} diff --git a/tests/Controller/AppSubscriptionsControllerTest.php b/tests/Controller/AppSubscriptionsControllerTest.php index 647e7733..81f5fd83 100644 --- a/tests/Controller/AppSubscriptionsControllerTest.php +++ b/tests/Controller/AppSubscriptionsControllerTest.php @@ -17,4 +17,48 @@ public function testIndex() $this->assertResponseStatusCodeSame(200); $this->assertPageTitleSame('Subscriptions'); } + + /** + * @Route("/subscriptions/create/{type}", name="app_subscriptions_create") + */ + public function testCreate403() + { + $this->client->request('GET', '/admin/subscriptions/create/'.uniqid()); + + $this->assertResponseStatusCodeSame(403); + } + + public function testCreatePush() + { + $this->client->request('GET', '/admin/subscriptions/create/push'); + + $this->assertResponseStatusCodeSame(200); + $this->assertPageTitleSame('Subscriptions - Create Push API'); + } + + public function testCreateSlack() + { + $this->client->request('GET', '/admin/subscriptions/create/slack'); + + $this->assertResponseStatusCodeSame(200); + $this->assertPageTitleSame('Subscriptions - Create Slack Incoming Webhook'); + } + + public function testCreateams() + { + $this->client->request('GET', '/admin/subscriptions/create/teams'); + + $this->assertResponseStatusCodeSame(200); + $this->assertPageTitleSame('Subscriptions - Create Microsoft Teams Incoming Webhook'); + } + + /** + * @Route("/subscriptions/{id}/update", name="app_subscriptions_update") + */ + public function testUpdate404() + { + $this->client->request('GET', '/admin/subscriptions/'.uniqid().'/update'); + + $this->assertResponseStatusCodeSame(404); + } } diff --git a/tests/Controller/ElasticsearchRepositoryControllerTest.php b/tests/Controller/ElasticsearchRepositoryControllerTest.php index d3d9bb77..3da91e31 100644 --- a/tests/Controller/ElasticsearchRepositoryControllerTest.php +++ b/tests/Controller/ElasticsearchRepositoryControllerTest.php @@ -21,6 +21,13 @@ public function testIndex() /** * @Route("/repositories/create/{type}", name="repositories_create") */ + public function testCreate403() + { + $this->client->request('GET', '/admin/repositories/create/'.uniqid()); + + $this->assertResponseStatusCodeSame(403); + } + public function testCreateFs() { $this->client->request('GET', '/admin/repositories/create/fs'); diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 395ca7c7..29834aa0 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -40,6 +40,7 @@ allocate_explanation: "Allocate explanation" allocation_explain: "Allocation explain" allocation_delayed: "Allocation delayed" allocation_failed: "Allocation failed" +allow_notifications: "Allow notifications" app_roles: "Roles" app_uninstall: "Uninstall" app_uninstall_note: "After confirmation, the indices below will be deleted. You will be able to install again using the secret key." @@ -129,6 +130,7 @@ audit_comments: note: "The setting {setting} is set to {value}." total_shards_per_node: note: "The setting {setting} is set to {value}." +authentication_secret: "Authentication secret" available: "Available" awaiting_info: "Awaiting info" awareness: "Awareness" @@ -168,6 +170,7 @@ confirm: "Confirm" connected: "Connected" connections: "Connections" container: "Container" +content_encoding: "Content encoding" copy: "Copy" console: "Console" coordinator_stats: "Coordinator stats" @@ -187,6 +190,10 @@ create_repository_gcs: "Create Google Cloud Storage repository" create_repository_azure: "Create Microsoft Azure repository" create_role: "Create role" create_snapshot: "Create snapshot" +create_subscription: "Create subscription" +create_subscription_push: "Create Push API" +create_subscription_slack: "Create Slack Incoming Webhook" +create_subscription_teams: "Create Microsoft Teams Incoming Webhook" create_user: "Create user" created_at: "Created at" creation_date: "Creation date" @@ -241,6 +248,8 @@ empty_note: "After confirmation, all the documents in this index will be deleted enable: "Enable" enabled: "Enabled" end_time: "End time" +endpoint: "Endpoint" +endpoint_already_used: "Endpoint already used" enrich: "Enrich policies" enrich_fields: "Enrich fields" errors: "Errors" @@ -503,6 +512,7 @@ not_available: "Not available" no: "No" no_attempt: "No attempt" no_valid_shard_copy: "No valid shard copy" +notifications: "Notifications" on_failure: "On failure" open: "Open" open_note: "Closed indices consume a significant amount of disk-space which can cause problems in managed environments." @@ -528,6 +538,7 @@ privileges: "Privileges" processor: "Processor" processors: "Processors" product_hunt: "Product Hunt" +public_key: "Public key" query: "Query" queue_size: "Queue size" readonly: "Read-only" @@ -669,11 +680,12 @@ stopped: "Stopped" stopping: "Stopping" storage_class: "Storage class" submit: "Submit" -subscribe_note: "Receive notifications about cluster health, node up, node down, disk threshold, license expiration, ES version" -subscribe_push: "Push API" -subscribe_slack: "Slack Incoming Webhook" -subscribe_teams: "Microsoft Teams Incoming Webhook" +subscription: "Subscriptions" +subscription_push: "Push API" +subscription_slack: "Slack Incoming Webhook" +subscription_teams: "Microsoft Teams Incoming Webhook" subscriptions: "Subscriptions" +subscriptions_note: "Receive notifications about cluster health, node up, node down, disk threshold, license expiration, ES version" summary: "Summary" success: "Success" tasks: "Tasks"