wecom.center.view.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <?php declare(strict_types = 0);
  2. /*
  3. ** WeCom Center - page view (three tabs: Alert / Optimization / Maintenance).
  4. ** Rendered as clean card HTML on top of Zabbix's page chrome.
  5. ** @var CView $this
  6. ** @var array $data
  7. */
  8. use Modules\WecomCenter\Lib\I18n;
  9. $this->addCssFile('modules/WecomCenter/assets/wecom.center.css');
  10. $e = function ($v) { return htmlspecialchars((string) ($v ?? ''), ENT_QUOTES, 'UTF-8'); };
  11. $t = function (string $k, array $a = []) use ($e) { return $e(I18n::t($k, $a)); };
  12. // Zabbix CTag escapes plain strings; wrap trusted, self-built markup so it renders as HTML.
  13. $raw = function (string $h) {
  14. return new class($h) {
  15. private $h;
  16. public function __construct(string $h) { $this->h = $h; }
  17. public function toString($destroy = true): string { return $this->h; }
  18. public function __toString(): string { return $this->h; }
  19. };
  20. };
  21. $save = $e((new CUrl('zabbix.php'))->setArgument('action', 'wecom.center.save')->getUrl());
  22. $view_url = (new CUrl('zabbix.php'))->setArgument('action', 'wecom.center.view')->getUrl();
  23. $mtid = $e($data['mediatypeid']);
  24. $active_tab = (string) ($_REQUEST['tab'] ?? 'alert');
  25. if (!in_array($active_tab, ['alert', 'opt', 'maint'], true)) {
  26. $active_tab = 'alert';
  27. }
  28. $tabHref = function (string $id) use ($data) {
  29. return htmlspecialchars((new CUrl('zabbix.php'))
  30. ->setArgument('action', 'wecom.center.view')
  31. ->setArgument('mediatypeid', $data['mediatypeid'])
  32. ->setArgument('tab', $id)
  33. ->getUrl(), ENT_QUOTES, 'UTF-8');
  34. };
  35. $html_page = (new CHtmlPage())->setTitle(I18n::t('page.title'));
  36. if (!$data['mediatype']) {
  37. $html_page->addItem($raw('<div class="wc-wrap"><div class="wc-card"><div class="wc-card__body">'
  38. .$t('msg.no_mediatype').'</div></div></div>'));
  39. $html_page->show();
  40. return;
  41. }
  42. $mask = (int) $data['severity'];
  43. $enabled = ((int) $data['active'] === 0);
  44. /* helper to build a card */
  45. $card = function (string $ico, string $title, string $body, string $extra = '', string $sub = '') {
  46. return '<div class="wc-card'.$extra.'"><div class="wc-card__head"><span class="wc-card__ico">'.$ico.'</span>'
  47. .'<span class="wc-card__title">'.$title.'</span>'
  48. .($sub !== '' ? '<span class="wc-card__sub">'.$sub.'</span>' : '')
  49. .'</div><div class="wc-card__body">'.$body.'</div></div>';
  50. };
  51. /* ============================ TAB 1: ALERT ============================ */
  52. // -- robot --
  53. $robotBody =
  54. '<form method="post" action="'.$save.'">'
  55. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'">'
  56. .'<input type="hidden" name="tab" value="alert">'
  57. .'<div class="wc-field"><label>'.$t('lbl.key').'</label>'
  58. .'<input class="wc-input wc-input--mono" type="text" name="webhook_url" value="'.$e($data['webhook_url']).'"></div>'
  59. .'<div class="wc-actions">'
  60. .'<button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_robot">'.$t('btn.save').'</button>'
  61. .'<button class="wc-btn wc-btn--ghost" type="submit" name="op" value="test_robot">'.$t('btn.test').'</button>'
  62. .'</div></form>';
  63. $robotCard = $card('&#129302;', $t('sec.robot'), $robotBody, '', $e($data['mediatype']['name'] ?? ''));
  64. // -- threshold --
  65. $sevChecks = '';
  66. for ($i = 1; $i <= 6; $i++) {
  67. $bit = 1 << ($i - 1);
  68. $chk = (($mask & $bit) !== 0) ? ' checked' : '';
  69. $sevChecks .= '<label class="wc-checkrow"><input type="checkbox" name="sev_'.$i.'"'.$chk.'> '
  70. .'<span class="wc-sev wc-sev-'.$i.'">'.$t('sev.'.$i).'</span></label>';
  71. }
  72. $thresholdBody =
  73. '<div class="wc-hint">'.$t('lbl.threshold_hint').'</div>'
  74. .'<form method="post" action="'.$save.'">'
  75. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'">'
  76. .'<input type="hidden" name="tab" value="alert">'
  77. .'<div class="wc-field"><label class="wc-checkrow"><input type="checkbox" name="enabled"'.($enabled ? ' checked' : '').'> '.$t('lbl.alerting_on').'</label></div>'
  78. .'<div class="wc-field"><label>'.$t('lbl.severity').'</label><div class="wc-checks">'.$sevChecks.'</div></div>'
  79. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_alert">'.$t('btn.save').'</button></div>'
  80. .'</form>';
  81. $thresholdCard = $card('&#9881;&#65039;', $t('sec.threshold'), $thresholdBody);
  82. // -- strategy presets --
  83. $presetRows = '';
  84. foreach ($data['presets'] as $key => $p) {
  85. $is_cur = ((string) $data['current_strategy'] === (string) $key);
  86. $name = $t($p['label']).($is_cur ? ' <span class="wc-pill wc-pill--cur">'.$t('preset.current').'</span>' : '');
  87. $prevUrl = $e((new CUrl('zabbix.php'))
  88. ->setArgument('action', 'wecom.center.view')
  89. ->setArgument('mediatypeid', $data['mediatypeid'])
  90. ->setArgument('tab', 'alert')
  91. ->setArgument('preview_preset', $key)
  92. ->getUrl());
  93. $presetRows .=
  94. '<div class="wc-preset">'
  95. .'<div class="wc-preset__main"><div class="wc-preset__name">'.$name.'</div>'
  96. .'<div class="wc-preset__desc">'.$t($p['desc']).'</div></div>'
  97. .'<div class="wc-preset__act">'
  98. .'<a class="wc-btn wc-btn--ghost wc-btn--sm" href="'.$prevUrl.'">'.$t('btn.preview').'</a>'
  99. .'<form method="post" action="'.$save.'" style="margin:0"><input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="alert">'
  100. .'<button class="wc-btn wc-btn--primary wc-btn--sm" type="submit" name="apply_strategy" value="'.$e($key).'">'.$t('btn.apply').'</button></form>'
  101. .'</div></div>';
  102. }
  103. $strategyBody = '<div class="wc-hint">'.$t('lbl.strategy_hint').'</div>'.$presetRows;
  104. $strategyCard = $card('&#128680;', $t('sec.strategy'), $strategyBody, ' wc-card--wide');
  105. // -- preview --
  106. $previewCard = '';
  107. if ($data['preview'] !== null) {
  108. $pv = $data['preview'];
  109. $pkey = $data['preview_key'];
  110. $rows = '';
  111. foreach ($pv['classes'] as $ck => $c) {
  112. $label = $t($data['classes'][$ck]['label'] ?? $ck);
  113. $rows .= '<tr><td>'.$label.'</td><td><span class="wc-sev wc-sev-'.((int) $c['target'] + 1).'">'.$t('sev.'.($c['target'] + 1)).'</span></td>'
  114. .'<td class="num">'.$e($c['count']).'</td><td class="num">'.$e($c['changes']).'</td></tr>';
  115. }
  116. $rows .= '<tr class="wc-tr-total"><td>'.$t('cls.total').'</td><td></td>'
  117. .'<td class="num">'.$e($pv['total']).'</td><td class="num">'.$e($pv['changes']).'</td></tr>';
  118. $tbl = '<table class="wc-table"><thead><tr><th>'.$t('cls.col.class').'</th><th>'.$t('cls.col.target').'</th>'
  119. .'<th class="num">'.$t('cls.col.count').'</th><th class="num">'.$t('cls.col.changes').'</th></tr></thead><tbody>'.$rows.'</tbody></table>';
  120. $body = '<div class="wc-hint">'.$t('preview.hint').'</div>'.$tbl;
  121. $previewCard = $card('&#128269;', $t('preview.title', [I18n::t($data['presets'][$pkey]['label'] ?? $pkey)]), $body, ' wc-card--wide');
  122. }
  123. $tab_alert = '<div class="wc-grid">'.$robotCard.$thresholdCard.$strategyCard.$previewCard.'</div>';
  124. /* ======================== TAB 2: OPTIMIZATION ========================= */
  125. // -- tuning --
  126. $tuneOpts = '';
  127. foreach (['small', 'medium', 'large'] as $pk) {
  128. $tuneOpts .= '<option value="'.$e($pk).'"'.(($data['tuning_profile'] === $pk) ? ' selected' : '').'>'.$t($pk === 'small' ? 'tuning.small' : ($pk === 'medium' ? 'tuning.medium' : 'tuning.large')).'</option>';
  129. }
  130. $tuneBody =
  131. '<div class="wc-hint">'.$t('lbl.tuning_hint').'</div>'
  132. .'<form method="get" action="zabbix.php"><input type="hidden" name="action" value="wecom.center.view">'
  133. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="opt">'
  134. .'<div class="wc-inline"><div class="wc-field"><label>'.$t('tuning.choose').'</label>'
  135. .'<select class="wc-input" name="tuning_profile">'.$tuneOpts.'</select></div>'
  136. .'<button class="wc-btn wc-btn--primary" type="submit" name="generate" value="1">'.$t('btn.generate').'</button></div></form>'
  137. .'<div class="wc-code__label" style="margin-top:16px">'.$t('tuning.env').'</div>'
  138. .'<div class="wc-code">'.$e($data['tuning']['env']).'</div>'
  139. .'<div class="wc-code__label">'.$t('tuning.cmd').'</div>'
  140. .'<div class="wc-code">'.$e($data['tuning']['cmd']).'</div>';
  141. $tuningCard = $card('&#9881;&#65039;', $t('sec.tuning'), $tuneBody, ' wc-card--wide');
  142. // -- template import --
  143. $tplOpts = '';
  144. if (!$data['templates']) {
  145. $tplOpts = '<option value="">'.$t('tpl.none').'</option>';
  146. }
  147. foreach ($data['templates'] as $tf) {
  148. $tplOpts .= '<option value="'.$e($tf['file']).'">'.$e($tf['name']).'</option>';
  149. }
  150. $importBody =
  151. '<div class="wc-hint">'.$t('lbl.template_hint').'</div>'
  152. .'<form method="post" action="'.$save.'">'
  153. .'<div class="wc-field"><label>'.$t('tpl.choose').'</label><select class="wc-input" name="template_file">'.$tplOpts.'</select></div>'
  154. .'<input type="hidden" name="tab" value="opt">'
  155. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="import_template">'.$t('btn.import').'</button></div>'
  156. .'</form>';
  157. $importCard = $card('&#128230;', $t('sec.template'), $importBody);
  158. // -- map --
  159. if ($data['server_host']) {
  160. $mapBody =
  161. '<div class="wc-hint">'.$t('map.hint').'</div>'
  162. .'<form method="post" action="'.$save.'">'
  163. .'<input type="hidden" name="hostid" value="'.$e($data['server_host']['hostid']).'">'
  164. .'<input type="hidden" name="tab" value="opt">'
  165. .'<div class="wc-field"><label>'.$t('map.host').'</label><div class="wc-muted">'.$e($data['server_host']['name']).'</div></div>'
  166. .'<div class="wc-inline">'
  167. .'<div class="wc-field"><label>'.$t('map.lat').'</label><input class="wc-input" type="text" name="lat" value="'.$e($data['server_loc']['location_lat']).'"></div>'
  168. .'<div class="wc-field"><label>'.$t('map.lon').'</label><input class="wc-input" type="text" name="lon" value="'.$e($data['server_loc']['location_lon']).'"></div>'
  169. .'</div>'
  170. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_map">'.$t('btn.save').'</button></div>'
  171. .'</form>';
  172. }
  173. else {
  174. $mapBody = '<div class="wc-empty">'.$t('msg.no_server_host').'</div>';
  175. }
  176. $mapCard = $card('&#128506;&#65039;', $t('sec.map'), $mapBody);
  177. // -- template switch --
  178. $srcOpts = '<option value="">'.$t('switch.pick').'</option>';
  179. $dstOpts = '<option value="">'.$t('switch.pick').'</option>';
  180. foreach ($data['huawei_tpls'] as $tp) {
  181. $selS = ((string) $data['switch_src'] === (string) $tp['templateid']) ? ' selected' : '';
  182. $selD = ((string) $data['switch_dst'] === (string) $tp['templateid']) ? ' selected' : '';
  183. $srcOpts .= '<option value="'.$e($tp['templateid']).'"'.$selS.'>'.$e($tp['host']).'</option>';
  184. $dstOpts .= '<option value="'.$e($tp['templateid']).'"'.$selD.'>'.$e($tp['host']).'</option>';
  185. }
  186. $switchBody =
  187. '<div class="wc-hint">'.$t('lbl.switch_hint').'</div>'
  188. .'<form method="get" action="zabbix.php"><input type="hidden" name="action" value="wecom.center.view">'
  189. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="opt">'
  190. .'<div class="wc-inline">'
  191. .'<div class="wc-field"><label>'.$t('switch.src').'</label><select class="wc-input" name="switch_src">'.$srcOpts.'</select></div>'
  192. .'<div class="wc-field"><label>'.$t('switch.dst').'</label><select class="wc-input" name="switch_dst">'.$dstOpts.'</select></div>'
  193. .'<button class="wc-btn wc-btn--ghost" type="submit" name="preview" value="1">'.$t('btn.preview').'</button>'
  194. .'</div></form>';
  195. if ($data['switch_preview'] !== null) {
  196. $sp = $data['switch_preview'];
  197. if ((int) $sp['count'] === 0) {
  198. $switchBody .= '<div class="wc-divider"></div><div class="wc-empty">'.$t('switch.none', [$sp['src_name']]).'</div>';
  199. }
  200. else {
  201. $chips = '';
  202. foreach ($sp['hosts'] as $hn) {
  203. $chips .= '<span class="wc-chip">'.$e($hn).'</span>';
  204. }
  205. $switchBody .= '<div class="wc-divider"></div>'
  206. .'<div class="wc-muted">'.$t('switch.result', [$sp['count'], $sp['src_name'], $sp['dst_name']]).'</div>'
  207. .'<div class="wc-chips">'.$chips.'</div>'
  208. .'<form method="post" action="'.$save.'" style="margin-top:14px">'
  209. .'<input type="hidden" name="switch_src" value="'.$e($sp['src']).'">'
  210. .'<input type="hidden" name="switch_dst" value="'.$e($sp['dst']).'">'
  211. .'<input type="hidden" name="tab" value="opt">'
  212. .'<button class="wc-btn wc-btn--danger" type="submit" name="op" value="apply_switch">'.$t('btn.confirm').'</button>'
  213. .'</form>';
  214. }
  215. }
  216. $switchCard = $card('&#128257;', $t('sec.switch'), $switchBody, ' wc-card--wide');
  217. $tab_opt = '<div class="wc-grid">'.$tuningCard.$importCard.$mapCard.$switchCard.'</div>';
  218. /* ======================== TAB 3: MAINTENANCE ========================== */
  219. $searchBody =
  220. '<div class="wc-hint">'.$t('lbl.trig_hint', [$data['trigger_count']]).'</div>'
  221. .'<form method="get" action="zabbix.php"><input type="hidden" name="action" value="wecom.center.view">'
  222. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="maint">'
  223. .'<div class="wc-inline"><div class="wc-field" style="flex:3"><input class="wc-input" type="text" name="trig_search" value="'.$e($data['trig_search']).'" placeholder="'.$t('lbl.trig_search').'"></div>'
  224. .'<button class="wc-btn wc-btn--primary" type="submit" name="search" value="1">'.$t('btn.search').'</button></div></form>';
  225. $maintBody = $searchBody;
  226. if ($data['trig_search'] !== '') {
  227. if ($data['triggers']) {
  228. $ids = [];
  229. $list = '';
  230. foreach ($data['triggers'] as $tg) {
  231. $ids[] = $tg['triggerid'];
  232. $host = !empty($tg['hosts']) ? $tg['hosts'][0]['host'] : '';
  233. $sev = ((int) $tg['priority']) + 1;
  234. $chk = ((int) $tg['status'] === 0) ? ' checked' : '';
  235. $list .= '<label class="wc-checkrow" style="display:block;padding:6px 0;border-bottom:1px solid #f2f4f7">'
  236. .'<input type="checkbox" name="trig_'.$e($tg['triggerid']).'"'.$chk.'> '
  237. .'<span class="wc-sev wc-sev-'.$sev.'">'.$t('sev.'.$sev).'</span> '
  238. .'<span class="wc-muted">'.$e($host).'</span> — '.$e($tg['description']).'</label>';
  239. }
  240. $maintBody .= '<div class="wc-divider"></div>'
  241. .'<form method="post" action="'.$save.'">'
  242. .'<input type="hidden" name="trig_ids" value="'.$e(implode(',', $ids)).'">'
  243. .'<input type="hidden" name="tab" value="maint">'
  244. .'<div class="wc-field"><label>'.$t('lbl.trig_list').'</label>'.$list.'</div>'
  245. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_triggers">'.$t('btn.save').'</button></div>'
  246. .'</form>';
  247. }
  248. else {
  249. $maintBody .= '<div class="wc-divider"></div><div class="wc-empty">'.$t('msg.no_trig_search').'</div>';
  250. }
  251. }
  252. $tab_maint = '<div class="wc-grid">'.$card('&#128295;', $t('sec.triggers'), $maintBody, ' wc-card--wide').'</div>';
  253. /* ============================ assemble =============================== */
  254. $hero = '<div class="wc-hero"><div class="wc-hero__title">'.$t('page.title').'</div>'
  255. .'<div class="wc-hero__sub">'.$t('hero.sub').'</div></div>';
  256. $tabbar = '<div class="wc-tabs">'
  257. .'<a class="wc-tab'.($active_tab === 'alert' ? ' is-active' : '').'" href="'.$tabHref('alert').'">'.$t('tab.alert').'</a>'
  258. .'<a class="wc-tab'.($active_tab === 'opt' ? ' is-active' : '').'" href="'.$tabHref('opt').'">'.$t('tab.opt').'</a>'
  259. .'<a class="wc-tab'.($active_tab === 'maint' ? ' is-active' : '').'" href="'.$tabHref('maint').'">'.$t('tab.maint').'</a>'
  260. .'</div>';
  261. $content = ($active_tab === 'opt') ? $tab_opt : (($active_tab === 'maint') ? $tab_maint : $tab_alert);
  262. $html_page->addItem($raw('<div class="wc-wrap">'.$hero.$tabbar.$content.'</div>'));
  263. $html_page->show();