diff --git a/application/controllers/CommandTransportController.php b/application/controllers/CommandTransportController.php new file mode 100644 index 00000000..15264eea --- /dev/null +++ b/application/controllers/CommandTransportController.php @@ -0,0 +1,147 @@ +assertPermission('config/modules'); + } + + public function indexAction() + { + $list = new CommandTransportList((new CommandTransportConfig())->select()); + + $this->addControl( + (new ButtonLink( + t('Create Command Transport'), + 'icingadb/command-transport/add', + 'plus' + ))->setBaseTarget('_next') + ); + + $this->addContent($list); + + $this->mergeTabs($this->Module()->getConfigTabs()); + $this->getTabs()->disableLegacyExtensions(); + $this->view->title = $this->getTabs() + ->activate('command-transports') + ->getActiveTab() + ->getLabel(); + } + + public function showAction() + { + $transportName = $this->params->getRequired('name'); + + $transportConfig = (new CommandTransportConfig()) + ->select() + ->where('name', $transportName) + ->fetchRow(); + if ($transportConfig === false) { + $this->httpNotFound(t('Unknown transport')); + } + + $form = new ApiTransportForm(); + $form->populate((array) $transportConfig); + $form->on(ApiTransportForm::ON_SUCCESS, function (ApiTransportForm $form) use ($transportName) { + (new CommandTransportConfig())->update( + 'transport', + $form->getValues(), + Filter::where('name', $transportName) + ); + + $this->redirectNow('icingadb/command-transport'); + }); + + $form->handleRequest(ServerRequest::fromGlobals()); + + $this->addContent($form); + + $this->setTitle(sprintf($this->translate('Command Transport: %s'), $transportName)); + $this->getTabs()->disableLegacyExtensions(); + } + + public function addAction() + { + $form = new ApiTransportForm(); + $form->on(ApiTransportForm::ON_SUCCESS, function (ApiTransportForm $form) { + (new CommandTransportConfig())->insert('transport', $form->getValues()); + $this->redirectNow('icingadb/command-transport'); + }); + + $form->handleRequest(ServerRequest::fromGlobals()); + + $this->addContent($form); + + $this->setTitle($this->translate('Add Command Transport')); + $this->getTabs()->disableLegacyExtensions(); + } + + public function removeAction() + { + $transportName = $this->params->getRequired('name'); + + $form = new ConfirmRemovalForm(); + $form->setAttrib('style', 'text-align:center;'); + $form->setOnSuccess(function () use ($transportName) { + (new CommandTransportConfig())->delete( + 'transport', + Filter::where('name', $transportName) + ); + + $this->redirectNow('icingadb/command-transport'); + }); + + $form->handleRequest(); + + $this->addContent(HtmlString::create($form->render())); + + $this->setTitle($this->translate('Remove Command Transport: %s'), $transportName); + $this->getTabs()->disableLegacyExtensions(); + } + + public function sortAction() + { + $transportName = $this->params->getRequired('name'); + $newPosition = (int) $this->params->getRequired('pos'); + + $config = $this->Config('commandtransports'); + if (! $config->hasSection($transportName)) { + $this->httpNotFound(t('Unknown transport')); + } + + if ($newPosition < 0 || $newPosition > $config->count()) { + $this->httpBadRequest(t('Position out of bounds')); + } + + $transports = $config->getConfigObject()->toArray(); + $transportNames = array_keys($transports); + + array_splice($transportNames, array_search($transportName, $transportNames, true), 1); + array_splice($transportNames, $newPosition, 0, [$transportName]); + + $sortedTransports = []; + foreach ($transportNames as $name) { + $sortedTransports[$name] = $transports[$name]; + } + + $newConfig = Config::fromArray($sortedTransports); + $newConfig->saveIni($config->getConfigFile()); + + $this->redirectNow('icingadb/command-transport'); + } +} diff --git a/application/forms/ApiTransportForm.php b/application/forms/ApiTransportForm.php new file mode 100644 index 00000000..c05f8fc2 --- /dev/null +++ b/application/forms/ApiTransportForm.php @@ -0,0 +1,102 @@ +addElement('text', 'name', [ + 'required' => true, + 'label' => t('Transport Name') + ]); + + $this->addElement('hidden', 'transport', [ + 'value' => 'api' + ]); + + $this->addElement('text', 'host', [ + 'required' => true, + 'id' => 'api_transport_host', + 'label' => t('Host'), + 'description' => t('Hostname or address of the Icinga master') + ]); + + // TODO: Don't rely only on browser validation + $this->addElement('number', 'port', [ + 'required' => true, + 'label' => t('Port'), + 'value' => 5665, + 'min' => 1, + 'max' => 65536 + ]); + + $this->addElement('text', 'username', [ + 'required' => true, + 'label' => t('API Username'), + 'description' => t('User to authenticate with using HTTP Basic Auth') + ]); + + // TODO: Use a password element + $this->addElement('text', 'password', [ + 'required' => true, + 'label' => t('API Password') + ]); + + $this->addElement('submit', 'btn_submit', [ + 'label' => t('Save') + ]); + + $this->addElement($this->createCsrfCounterMeasure(Session::getSession()->getId())); + } + + public function validate() + { + parent::validate(); + if (! $this->isValid) { + return $this; + } + + if ($this->getPopulatedValue('force_creation') === 'n') { + return $this; + } + + try { + CommandTransport::createTransport(new ConfigObject($this->getValues()))->probe(); + } catch (CommandTransportException $e) { + $this->addMessage( + sprintf(t('Failed to successfully validate the configuration: %s'), $e->getMessage()) + ); + + $forceCheckbox = $this->createElement( + 'checkbox', + 'force_creation', + [ + 'ignore' => true, + 'label' => t('Force Changes'), + 'description' => t('Check this box to enforce changes without connectivity validation') + ] + ); + + $this->registerElement($forceCheckbox); + $this->decorate($forceCheckbox); + $this->prepend($forceCheckbox); + + $this->isValid = false; + } + + return $this; + } +} diff --git a/configuration.php b/configuration.php index 81a8ee4c..600d790a 100644 --- a/configuration.php +++ b/configuration.php @@ -212,7 +212,7 @@ namespace Icinga\Module\Icingadb $this->provideConfigTab('command-transports', [ 'label' => t('Command Transports'), 'title' => t('Configure command transports'), - 'url' => 'config/command-transports' + 'url' => 'command-transport' ]); $this->provideConfigTab('security', [ 'label' => t('Security'), diff --git a/library/Icingadb/Command/Transport/CommandTransportConfig.php b/library/Icingadb/Command/Transport/CommandTransportConfig.php new file mode 100644 index 00000000..e17fa041 --- /dev/null +++ b/library/Icingadb/Command/Transport/CommandTransportConfig.php @@ -0,0 +1,31 @@ + [ + 'name' => 'commandtransports', + 'module' => 'icingadb', + 'keyColumn' => 'name' + ] + ]; + + protected $queryColumns = [ + 'transport' => [ + 'name', + 'transport', + + // API options + 'host', + 'port', + 'username', + 'password' + ] + ]; +} diff --git a/library/Icingadb/Common/BaseOrderedItemList.php b/library/Icingadb/Common/BaseOrderedItemList.php new file mode 100644 index 00000000..435f8fd9 --- /dev/null +++ b/library/Icingadb/Common/BaseOrderedItemList.php @@ -0,0 +1,33 @@ +getItemClass(); + + $i = 0; + foreach ($this->data as $data) { + $item = new $itemClass($data, $this); + $item->setOrder($i++); + + $this->add($item); + } + + if ($this->isEmpty()) { + $this->setTag('div'); + $this->add(new EmptyState(t('No items found.'))); + } + } +} diff --git a/library/Icingadb/Common/BaseOrderedListItem.php b/library/Icingadb/Common/BaseOrderedListItem.php new file mode 100644 index 00000000..87d5ab74 --- /dev/null +++ b/library/Icingadb/Common/BaseOrderedListItem.php @@ -0,0 +1,37 @@ +order = (int) $order; + + return $this; + } + + /** + * Get this element's position + * + * @return int + */ + public function getOrder() + { + return $this->order; + } +} diff --git a/library/Icingadb/Widget/BaseItemList.php b/library/Icingadb/Widget/BaseItemList.php index 86b9da1d..454c0244 100644 --- a/library/Icingadb/Widget/BaseItemList.php +++ b/library/Icingadb/Widget/BaseItemList.php @@ -12,6 +12,8 @@ use ipl\Web\Url; /** * Base class for item lists + * + * @todo Move this to Icinga\Module\Icingadb\Common */ abstract class BaseItemList extends BaseHtmlElement { diff --git a/library/Icingadb/Widget/BaseListItem.php b/library/Icingadb/Widget/BaseListItem.php index e1b97227..cc7fa3d0 100644 --- a/library/Icingadb/Widget/BaseListItem.php +++ b/library/Icingadb/Widget/BaseListItem.php @@ -11,6 +11,8 @@ use ipl\Web\Filter\QueryString; /** * Base class for list items + * + * @todo Move this to Icinga\Module\Icingadb\Common */ abstract class BaseListItem extends BaseHtmlElement { diff --git a/library/Icingadb/Widget/ItemList/CommandTransportList.php b/library/Icingadb/Widget/ItemList/CommandTransportList.php new file mode 100644 index 00000000..615e7d64 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/CommandTransportList.php @@ -0,0 +1,22 @@ +getAttributes()->add('class', 'command-transport-list'); + $this->setDetailUrl(Url::fromPath('icingadb/command-transport/show')); + } + + protected function getItemClass() + { + return CommandTransportListItem::class; + } +} diff --git a/library/Icingadb/Widget/ItemList/CommandTransportListItem.php b/library/Icingadb/Widget/ItemList/CommandTransportListItem.php new file mode 100644 index 00000000..681f939e --- /dev/null +++ b/library/Icingadb/Widget/ItemList/CommandTransportListItem.php @@ -0,0 +1,69 @@ +setDetailFilter(Filter::equal('name', $this->item->name)); + } + + protected function assembleHeader(BaseHtmlElement $header) + { + } + + protected function assembleMain(BaseHtmlElement $main) + { + $main->add(new Link( + new HtmlElement('strong', null, $this->item->name), + Url::fromPath('icingadb/command-transport/show', ['name' => $this->item->name]) + )); + + $main->add(new Link( + new Icon('trash', ['title' => sprintf(t('Remove command transport "%s"'), $this->item->name)]), + Url::fromPath('icingadb/command-transport/remove', ['name' => $this->item->name]), + [ + 'class' => 'pull-right action-link', + 'data-icinga-modal' => true, + 'data-no-icinga-ajax' => true + ] + )); + + if ($this->getOrder() + 1 < $this->list->count()) { + $main->add((new Link( + new Icon('arrow-down'), + Url::fromPath('icingadb/command-transport/sort', [ + 'name' => $this->item->name, + 'pos' => $this->getOrder() + 1 + ]), + ['class' => 'pull-right action-link'] + ))->setBaseTarget('_self')); + } + + if ($this->getOrder() > 0) { + $main->add((new Link( + new Icon('arrow-up'), + Url::fromPath('icingadb/command-transport/sort', [ + 'name' => $this->item->name, + 'pos' => $this->getOrder() - 1 + ]), + ['class' => 'pull-right action-link'] + ))->setBaseTarget('_self')); + } + } + + protected function createVisual() + { + } +}