1<?php 2 3namespace Icinga\Module\Businessprocess\Renderer; 4 5use Icinga\Date\DateFormatter; 6use Icinga\Module\Businessprocess\BpConfig; 7use Icinga\Module\Businessprocess\BpNode; 8use Icinga\Module\Businessprocess\ImportedNode; 9use Icinga\Module\Businessprocess\Node; 10use Icinga\Module\Businessprocess\Web\Component\StateBall; 11use Icinga\Module\Businessprocess\Web\Form\CsrfToken; 12use ipl\Html\BaseHtmlElement; 13use ipl\Html\Html; 14 15class TreeRenderer extends Renderer 16{ 17 /** 18 * @inheritdoc 19 */ 20 public function render() 21 { 22 $bp = $this->config; 23 $htmlId = $bp->getHtmlId(); 24 $tree = Html::tag( 25 'ul', 26 [ 27 'id' => $htmlId, 28 'class' => ['bp', 'sortable', $this->wantsRootNodes() ? '' : 'process'], 29 'data-sortable-disabled' => $this->isLocked() ? 'true' : 'false', 30 'data-sortable-data-id-attr' => 'id', 31 'data-sortable-direction' => 'vertical', 32 'data-sortable-group' => json_encode([ 33 'name' => $this->wantsRootNodes() ? 'root' : $htmlId, 34 'put' => 'function:rowPutAllowed' 35 ]), 36 'data-sortable-invert-swap' => 'true', 37 'data-is-root-config' => $this->wantsRootNodes() ? 'true' : 'false', 38 'data-csrf-token' => CsrfToken::generate() 39 ], 40 $this->renderBp($bp) 41 ); 42 if ($this->wantsRootNodes()) { 43 $tree->getAttributes()->add( 44 'data-action-url', 45 $this->getUrl()->setParams(['config' => $bp->getName()])->getAbsoluteUrl() 46 ); 47 } else { 48 $nodeName = $this->parent instanceof ImportedNode 49 ? $this->parent->getNodeName() 50 : $this->parent->getName(); 51 $tree->getAttributes() 52 ->add('data-node-name', $nodeName) 53 ->add('data-action-url', $this->getUrl() 54 ->setParams([ 55 'config' => $this->parent->getBpConfig()->getName(), 56 'node' => $nodeName 57 ]) 58 ->getAbsoluteUrl()); 59 } 60 61 $this->add($tree); 62 return parent::render(); 63 } 64 65 /** 66 * @param BpConfig $bp 67 * @return string 68 */ 69 public function renderBp(BpConfig $bp) 70 { 71 $html = array(); 72 if ($this->wantsRootNodes()) { 73 $nodes = $bp->getChildren(); 74 } else { 75 $nodes = $this->parent->getChildren(); 76 } 77 78 foreach ($nodes as $name => $node) { 79 if ($node instanceof BpNode) { 80 $html[] = $this->renderNode($bp, $node); 81 } else { 82 $html[] = $this->renderChild($bp, $node); 83 } 84 } 85 86 return $html; 87 } 88 89 protected function getStateClassNames(Node $node) 90 { 91 $state = strtolower($node->getStateName()); 92 93 if ($node->isMissing()) { 94 return array('missing'); 95 } elseif ($state === 'ok') { 96 if ($node->hasMissingChildren()) { 97 return array('ok', 'missing-children'); 98 } else { 99 return array('ok'); 100 } 101 } else { 102 return array('problem', $state); 103 } 104 } 105 106 /** 107 * @param Node $node 108 * @param array $path 109 * @return BaseHtmlElement[] 110 */ 111 public function getNodeIcons(Node $node, array $path = null) 112 { 113 $icons = []; 114 if (empty($path) && $node instanceof BpNode) { 115 $icons[] = Html::tag('i', ['class' => 'icon icon-sitemap']); 116 } else { 117 $icons[] = $node->getIcon(); 118 } 119 $icons[] = (new StateBall(strtolower($node->getStateName())))->addAttributes([ 120 'title' => sprintf( 121 '%s %s', 122 $node->getStateName(), 123 DateFormatter::timeSince($node->getLastStateChange()) 124 ) 125 ]); 126 if ($node->isInDowntime()) { 127 $icons[] = Html::tag('i', ['class' => 'icon icon-moon']); 128 } 129 if ($node->isAcknowledged()) { 130 $icons[] = Html::tag('i', ['class' => 'icon icon-ok']); 131 } 132 return $icons; 133 } 134 135 /** 136 * @param BpConfig $bp 137 * @param Node $node 138 * @param array $path 139 * 140 * @return string 141 */ 142 public function renderNode(BpConfig $bp, Node $node, $path = array()) 143 { 144 $htmlId = $this->getId($node, $path); 145 $li = Html::tag( 146 'li', 147 [ 148 'id' => $htmlId, 149 'class' => ['bp', 'movable', $node->getObjectClassName()], 150 'data-node-name' => $node instanceof ImportedNode 151 ? $node->getNodeName() 152 : $node->getName() 153 ] 154 ); 155 $attributes = $li->getAttributes(); 156 $attributes->add('class', $this->getStateClassNames($node)); 157 if ($node->isHandled()) { 158 $attributes->add('class', 'handled'); 159 } 160 if ($node instanceof BpNode) { 161 $attributes->add('class', 'operator'); 162 } else { 163 $attributes->add('class', 'node'); 164 } 165 166 $div = Html::tag('div'); 167 $li->add($div); 168 169 $div->add($node->getLink()); 170 $div->add($this->getNodeIcons($node, $path)); 171 172 $div->add(Html::tag('span', null, $node->getAlias())); 173 174 if ($node instanceof BpNode) { 175 $div->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml())); 176 } 177 178 if ($node instanceof BpNode && $node->hasInfoUrl()) { 179 $div->add($this->createInfoAction($node)); 180 } 181 182 $differentConfig = $node->getBpConfig()->getName() !== $this->getBusinessProcess()->getName(); 183 if (! $this->isLocked() && !$differentConfig) { 184 $div->add($this->getActionIcons($bp, $node)); 185 } elseif ($differentConfig) { 186 $div->add($this->actionIcon( 187 'forward', 188 $this->getSourceUrl($node)->addParams(['mode' => 'tree'])->getAbsoluteUrl(), 189 mt('businessprocess', 'Show this process as part of its original configuration') 190 )->addAttributes(['data-base-target' => '_next'])); 191 } 192 193 $ul = Html::tag('ul', [ 194 'class' => ['bp', 'sortable'], 195 'data-sortable-disabled' => ($this->isLocked() || $differentConfig) ? 'true' : 'false', 196 'data-sortable-invert-swap' => 'true', 197 'data-sortable-data-id-attr' => 'id', 198 'data-sortable-draggable' => '.movable', 199 'data-sortable-direction' => 'vertical', 200 'data-sortable-group' => json_encode([ 201 'name' => $htmlId, // Unique, so that the function below is the only deciding factor 202 'put' => 'function:rowPutAllowed' 203 ]), 204 'data-csrf-token' => CsrfToken::generate(), 205 'data-action-url' => $this->getUrl() 206 ->setParams([ 207 'config' => $node->getBpConfig()->getName(), 208 'node' => $node instanceof ImportedNode 209 ? $node->getNodeName() 210 : $node->getName() 211 ]) 212 ->getAbsoluteUrl() 213 ]); 214 $li->add($ul); 215 216 $path[] = $differentConfig ? $node->getIdentifier() : $node->getName(); 217 foreach ($node->getChildren() as $name => $child) { 218 if ($child instanceof BpNode) { 219 $ul->add($this->renderNode($bp, $child, $path)); 220 } else { 221 $ul->add($this->renderChild($bp, $child, $path)); 222 } 223 } 224 225 return $li; 226 } 227 228 protected function renderChild($bp, Node $node, $path = null) 229 { 230 $li = Html::tag('li', [ 231 'class' => 'movable', 232 'id' => $this->getId($node, $path ?: []), 233 'data-node-name' => $node->getName() 234 ]); 235 236 $li->add($this->getNodeIcons($node, $path)); 237 238 $link = $node->getLink(); 239 $link->getAttributes()->set('data-base-target', '_next'); 240 $li->add($link); 241 242 if (! $this->isLocked() && $node->getBpConfig()->getName() === $this->getBusinessProcess()->getName()) { 243 $li->add($this->getActionIcons($bp, $node)); 244 } 245 246 return $li; 247 } 248 249 protected function getActionIcons(BpConfig $bp, Node $node) 250 { 251 if ($node instanceof BpNode) { 252 if ($bp->getMetadata()->canModify()) { 253 return [$this->createEditAction($bp, $node), $this->renderAddNewNode($node)]; 254 } else { 255 return ''; 256 } 257 } else { 258 return $this->createSimulationAction($bp, $node); 259 } 260 } 261 262 protected function createEditAction(BpConfig $bp, BpNode $node) 263 { 264 return $this->actionIcon( 265 'edit', 266 $this->getUrl()->with(array( 267 'action' => 'edit', 268 'editnode' => $node->getName() 269 )), 270 mt('businessprocess', 'Modify this node') 271 ); 272 } 273 274 protected function createSimulationAction(BpConfig $bp, Node $node) 275 { 276 return $this->actionIcon( 277 'magic', 278 $this->getUrl()->with(array( 279 //'config' => $bp->getName(), 280 'action' => 'simulation', 281 'simulationnode' => $node->getName() 282 )), 283 mt('businessprocess', 'Simulate a specific state') 284 ); 285 } 286 287 protected function createInfoAction(BpNode $node) 288 { 289 $url = $node->getInfoUrl(); 290 return $this->actionIcon( 291 'help', 292 $url, 293 sprintf('%s: %s', mt('businessprocess', 'More information'), $url) 294 )->addAttributes(['target' => '_blank']); 295 } 296 297 protected function actionIcon($icon, $url, $title) 298 { 299 return Html::tag( 300 'a', 301 [ 302 'href' => $url, 303 'title' => $title, 304 'class' => 'action-link' 305 ], 306 Html::tag('i', ['class' => 'icon icon-' . $icon]) 307 ); 308 } 309 310 protected function renderAddNewNode($parent) 311 { 312 return $this->actionIcon( 313 'plus', 314 $this->getUrl() 315 ->with('action', 'add') 316 ->with('node', $parent->getName()), 317 mt('businessprocess', 'Add a new business process node') 318 ); 319 } 320} 321