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"