wecom.center.view.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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', 'l2'], 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. $initBody = '<div class="wc-hint">'.$t('init.sub').'</div>'
  38. .'<form method="post" action="'.$save.'">'
  39. .'<div class="wc-field"><label>'.$t('lbl.init_key').'</label>'
  40. .'<input class="wc-input wc-input--mono" type="text" name="key" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"></div>'
  41. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="init_env">'.$t('btn.init').'</button></div>'
  42. .'</form>';
  43. $initCard = '<div class="wc-card"><div class="wc-card__head"><span class="wc-card__ico">&#9889;</span>'
  44. .'<span class="wc-card__title">'.$t('init.title').'</span></div>'
  45. .'<div class="wc-card__body">'.$initBody.'</div></div>';
  46. $heroI = '<div class="wc-hero"><div class="wc-hero__title">'.$t('page.title').'</div>'
  47. .'<div class="wc-hero__sub">'.$t('init.sub').'</div></div>';
  48. $html_page->addItem($raw('<div class="wc-wrap">'.$heroI.$initCard.'</div>'));
  49. $html_page->show();
  50. return;
  51. }
  52. $mask = (int) $data['severity'];
  53. $enabled = ((int) $data['active'] === 0);
  54. /* helper to build a card */
  55. $card = function (string $ico, string $title, string $body, string $extra = '', string $sub = '') {
  56. return '<div class="wc-card'.$extra.'"><div class="wc-card__head"><span class="wc-card__ico">'.$ico.'</span>'
  57. .'<span class="wc-card__title">'.$title.'</span>'
  58. .($sub !== '' ? '<span class="wc-card__sub">'.$sub.'</span>' : '')
  59. .'</div><div class="wc-card__body">'.$body.'</div></div>';
  60. };
  61. /* ============================ TAB 1: ALERT ============================ */
  62. // -- robot --
  63. $robotBody =
  64. '<form method="post" action="'.$save.'">'
  65. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'">'
  66. .'<input type="hidden" name="tab" value="alert">'
  67. .'<div class="wc-field"><label>'.$t('lbl.key').'</label>'
  68. .'<input class="wc-input wc-input--mono" type="text" name="webhook_url" value="'.$e($data['webhook_url']).'"></div>'
  69. .'<div class="wc-actions">'
  70. .'<button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_robot">'.$t('btn.save').'</button>'
  71. .'<button class="wc-btn wc-btn--ghost" type="submit" name="op" value="test_robot">'.$t('btn.test').'</button>'
  72. .'</div></form>';
  73. $robotCard = $card('&#129302;', $t('sec.robot'), $robotBody, '', $e($data['mediatype']['name'] ?? ''));
  74. // -- threshold --
  75. $sevChecks = '';
  76. for ($i = 1; $i <= 6; $i++) {
  77. $bit = 1 << ($i - 1);
  78. $chk = (($mask & $bit) !== 0) ? ' checked' : '';
  79. $sevChecks .= '<label class="wc-checkrow"><input type="checkbox" name="sev_'.$i.'"'.$chk.'> '
  80. .'<span class="wc-sev wc-sev-'.$i.'">'.$t('sev.'.$i).'</span></label>';
  81. }
  82. $thresholdBody =
  83. '<div class="wc-hint">'.$t('lbl.threshold_hint').'</div>'
  84. .'<form method="post" action="'.$save.'">'
  85. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'">'
  86. .'<input type="hidden" name="tab" value="alert">'
  87. .'<div class="wc-field"><label class="wc-checkrow"><input type="checkbox" name="enabled"'.($enabled ? ' checked' : '').'> '.$t('lbl.alerting_on').'</label></div>'
  88. .'<div class="wc-field"><label>'.$t('lbl.severity').'</label><div class="wc-checks">'.$sevChecks.'</div></div>'
  89. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_alert">'.$t('btn.save').'</button></div>'
  90. .'</form>';
  91. $thresholdCard = $card('&#9881;&#65039;', $t('sec.threshold'), $thresholdBody);
  92. // -- strategy presets --
  93. $presetRows = '';
  94. foreach ($data['presets'] as $key => $p) {
  95. $is_cur = ((string) $data['current_strategy'] === (string) $key);
  96. $name = $t($p['label']).($is_cur ? ' <span class="wc-pill wc-pill--cur">'.$t('preset.current').'</span>' : '');
  97. $prevUrl = $e((new CUrl('zabbix.php'))
  98. ->setArgument('action', 'wecom.center.view')
  99. ->setArgument('mediatypeid', $data['mediatypeid'])
  100. ->setArgument('tab', 'alert')
  101. ->setArgument('preview_preset', $key)
  102. ->getUrl());
  103. $presetRows .=
  104. '<div class="wc-preset">'
  105. .'<div class="wc-preset__main"><div class="wc-preset__name">'.$name.'</div>'
  106. .'<div class="wc-preset__desc">'.$t($p['desc']).'</div></div>'
  107. .'<div class="wc-preset__act">'
  108. .'<a class="wc-btn wc-btn--ghost wc-btn--sm" href="'.$prevUrl.'">'.$t('btn.preview').'</a>'
  109. .'<form method="post" action="'.$save.'" style="margin:0"><input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="alert">'
  110. .'<button class="wc-btn wc-btn--primary wc-btn--sm" type="submit" name="apply_strategy" value="'.$e($key).'">'.$t('btn.apply').'</button></form>'
  111. .'</div></div>';
  112. }
  113. $strategyBody = '<div class="wc-hint">'.$t('lbl.strategy_hint').'</div>'.$presetRows;
  114. $strategyCard = $card('&#128680;', $t('sec.strategy'), $strategyBody, ' wc-card--wide');
  115. // -- preview --
  116. $previewCard = '';
  117. if ($data['preview'] !== null) {
  118. $pv = $data['preview'];
  119. $pkey = $data['preview_key'];
  120. $rows = '';
  121. foreach ($pv['classes'] as $ck => $c) {
  122. $label = $t($data['classes'][$ck]['label'] ?? $ck);
  123. $rows .= '<tr><td>'.$label.'</td><td><span class="wc-sev wc-sev-'.((int) $c['target'] + 1).'">'.$t('sev.'.($c['target'] + 1)).'</span></td>'
  124. .'<td class="num">'.$e($c['count']).'</td><td class="num">'.$e($c['changes']).'</td></tr>';
  125. }
  126. $rows .= '<tr class="wc-tr-total"><td>'.$t('cls.total').'</td><td></td>'
  127. .'<td class="num">'.$e($pv['total']).'</td><td class="num">'.$e($pv['changes']).'</td></tr>';
  128. $tbl = '<table class="wc-table"><thead><tr><th>'.$t('cls.col.class').'</th><th>'.$t('cls.col.target').'</th>'
  129. .'<th class="num">'.$t('cls.col.count').'</th><th class="num">'.$t('cls.col.changes').'</th></tr></thead><tbody>'.$rows.'</tbody></table>';
  130. $body = '<div class="wc-hint">'.$t('preview.hint').'</div>'.$tbl;
  131. $previewCard = $card('&#128269;', $t('preview.title', [I18n::t($data['presets'][$pkey]['label'] ?? $pkey)]), $body, ' wc-card--wide');
  132. }
  133. $tab_alert = '<div class="wc-grid">'.$robotCard.$thresholdCard.$strategyCard.$previewCard.'</div>';
  134. /* ======================== TAB 2: OPTIMIZATION ========================= */
  135. // -- tuning --
  136. $tuneOpts = '';
  137. foreach (['small', 'medium', 'large'] as $pk) {
  138. $tuneOpts .= '<option value="'.$e($pk).'"'.(($data['tuning_profile'] === $pk) ? ' selected' : '').'>'.$t($pk === 'small' ? 'tuning.small' : ($pk === 'medium' ? 'tuning.medium' : 'tuning.large')).'</option>';
  139. }
  140. $tuneBody =
  141. '<div class="wc-hint">'.$t('lbl.tuning_hint').'</div>'
  142. .'<form method="get" action="zabbix.php"><input type="hidden" name="action" value="wecom.center.view">'
  143. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="opt">'
  144. .'<div class="wc-inline"><div class="wc-field"><label>'.$t('tuning.choose').'</label>'
  145. .'<select class="wc-input" name="tuning_profile">'.$tuneOpts.'</select></div>'
  146. .'<button class="wc-btn wc-btn--primary" type="submit" name="generate" value="1">'.$t('btn.generate').'</button></div></form>'
  147. .'<div class="wc-code__label" style="margin-top:16px">'.$t('tuning.env').'</div>'
  148. .'<div class="wc-code">'.$e($data['tuning']['env']).'</div>'
  149. .'<div class="wc-code__label">'.$t('tuning.cmd').'</div>'
  150. .'<div class="wc-code">'.$e($data['tuning']['cmd']).'</div>';
  151. $tuningCard = $card('&#9881;&#65039;', $t('sec.tuning'), $tuneBody, ' wc-card--wide');
  152. // -- template import --
  153. $tplOpts = '';
  154. if (!$data['templates']) {
  155. $tplOpts = '<option value="">'.$t('tpl.none').'</option>';
  156. }
  157. foreach ($data['templates'] as $tf) {
  158. $tplOpts .= '<option value="'.$e($tf['file']).'">'.$e($tf['name']).'</option>';
  159. }
  160. $importBody =
  161. '<div class="wc-hint">'.$t('lbl.template_hint').'</div>'
  162. .'<form method="post" action="'.$save.'">'
  163. .'<div class="wc-field"><label>'.$t('tpl.choose').'</label><select class="wc-input" name="template_file">'.$tplOpts.'</select></div>'
  164. .'<input type="hidden" name="tab" value="opt">'
  165. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="import_template">'.$t('btn.import').'</button></div>'
  166. .'</form>';
  167. $importCard = $card('&#128230;', $t('sec.template'), $importBody);
  168. // -- map --
  169. if ($data['server_host']) {
  170. $mapBody =
  171. '<div class="wc-hint">'.$t('map.hint').'</div>'
  172. .'<form method="post" action="'.$save.'">'
  173. .'<input type="hidden" name="hostid" value="'.$e($data['server_host']['hostid']).'">'
  174. .'<input type="hidden" name="tab" value="opt">'
  175. .'<div class="wc-field"><label>'.$t('map.host').'</label><div class="wc-muted">'.$e($data['server_host']['name']).'</div></div>'
  176. .'<div class="wc-inline">'
  177. .'<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>'
  178. .'<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>'
  179. .'</div>'
  180. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_map">'.$t('btn.save').'</button></div>'
  181. .'</form>';
  182. }
  183. else {
  184. $mapBody = '<div class="wc-empty">'.$t('msg.no_server_host').'</div>';
  185. }
  186. $mapCard = $card('&#128506;&#65039;', $t('sec.map'), $mapBody);
  187. // -- template switch --
  188. $srcOpts = '<option value="">'.$t('switch.pick').'</option>';
  189. $dstOpts = '<option value="">'.$t('switch.pick').'</option>';
  190. foreach ($data['huawei_tpls'] as $tp) {
  191. $selS = ((string) $data['switch_src'] === (string) $tp['templateid']) ? ' selected' : '';
  192. $selD = ((string) $data['switch_dst'] === (string) $tp['templateid']) ? ' selected' : '';
  193. $srcOpts .= '<option value="'.$e($tp['templateid']).'"'.$selS.'>'.$e($tp['host']).'</option>';
  194. $dstOpts .= '<option value="'.$e($tp['templateid']).'"'.$selD.'>'.$e($tp['host']).'</option>';
  195. }
  196. $switchBody =
  197. '<div class="wc-hint">'.$t('lbl.switch_hint').'</div>'
  198. .'<form method="get" action="zabbix.php"><input type="hidden" name="action" value="wecom.center.view">'
  199. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="opt">'
  200. .'<div class="wc-inline">'
  201. .'<div class="wc-field"><label>'.$t('switch.src').'</label><select class="wc-input" name="switch_src">'.$srcOpts.'</select></div>'
  202. .'<div class="wc-field"><label>'.$t('switch.dst').'</label><select class="wc-input" name="switch_dst">'.$dstOpts.'</select></div>'
  203. .'<button class="wc-btn wc-btn--ghost" type="submit" name="preview" value="1">'.$t('btn.preview').'</button>'
  204. .'</div></form>';
  205. if ($data['switch_preview'] !== null) {
  206. $sp = $data['switch_preview'];
  207. if ((int) $sp['count'] === 0) {
  208. $switchBody .= '<div class="wc-divider"></div><div class="wc-empty">'.$t('switch.none', [$sp['src_name']]).'</div>';
  209. }
  210. else {
  211. $chips = '';
  212. foreach ($sp['hosts'] as $hn) {
  213. $chips .= '<span class="wc-chip">'.$e($hn).'</span>';
  214. }
  215. $switchBody .= '<div class="wc-divider"></div>'
  216. .'<div class="wc-muted">'.$t('switch.result', [$sp['count'], $sp['src_name'], $sp['dst_name']]).'</div>'
  217. .'<div class="wc-chips">'.$chips.'</div>'
  218. .'<form method="post" action="'.$save.'" style="margin-top:14px">'
  219. .'<input type="hidden" name="switch_src" value="'.$e($sp['src']).'">'
  220. .'<input type="hidden" name="switch_dst" value="'.$e($sp['dst']).'">'
  221. .'<input type="hidden" name="tab" value="opt">'
  222. .'<button class="wc-btn wc-btn--danger" type="submit" name="op" value="apply_switch">'.$t('btn.confirm').'</button>'
  223. .'</form>';
  224. }
  225. }
  226. $switchCard = $card('&#128257;', $t('sec.switch'), $switchBody, ' wc-card--wide');
  227. $initBody2 = '<div class="wc-hint">'.$t('lbl.init_hint').'</div>'
  228. .'<form method="post" action="'.$save.'">'
  229. .'<input type="hidden" name="tab" value="opt">'
  230. .'<div class="wc-field"><label>'.$t('lbl.init_key').'</label>'
  231. .'<input class="wc-input wc-input--mono" type="text" name="key" value="'.$e($data['webhook_url']).'"></div>'
  232. .'<div class="wc-actions"><button class="wc-btn wc-btn--ghost" type="submit" name="op" value="init_env">'.$t('btn.init').'</button></div>'
  233. .'</form>';
  234. $initCard2 = $card('&#9889;', $t('sec.init'), $initBody2);
  235. $tab_opt = '<div class="wc-grid">'.$tuningCard.$importCard.$mapCard.$initCard2.$switchCard.'</div>';
  236. /* ======================== TAB 3: MAINTENANCE ========================== */
  237. // -- by class: pick a class + a severity, apply to every trigger in that class --
  238. $classOpts = '';
  239. foreach ($data['classes'] as $ck => $c) {
  240. if ($ck === 'other') {
  241. continue;
  242. }
  243. $classOpts .= '<option value="'.$e($ck).'">'.$t($c['label']).'</option>';
  244. }
  245. $prioOpts = '';
  246. for ($p = 0; $p <= 5; $p++) {
  247. $prioOpts .= '<option value="'.$p.'"'.($p === 5 ? ' selected' : '').'>'.$t('sev.'.($p + 1)).'</option>';
  248. }
  249. $classBody =
  250. '<div class="wc-hint">'.$t('lbl.class_sev_hint').'</div>'
  251. .'<form method="post" action="'.$save.'"><input type="hidden" name="tab" value="maint">'
  252. .'<div class="wc-inline">'
  253. .'<div class="wc-field" style="flex:2;min-width:200px"><label>'.$t('lbl.class_pick').'</label><select class="wc-input" name="class_key">'.$classOpts.'</select></div>'
  254. .'<div class="wc-field" style="flex:1;min-width:140px"><label>'.$t('lbl.set_severity').'</label><select class="wc-input" name="class_priority">'.$prioOpts.'</select></div>'
  255. .'</div>'
  256. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_class_sev">'.$t('btn.apply').'</button></div>'
  257. .'</form>';
  258. $classCard = $card('&#127991;&#65039;', $t('sec.class_sev'), $classBody, ' wc-card--wide');
  259. // -- by single trigger: search, then adjust severity / enable per row --
  260. $searchBody =
  261. '<div class="wc-hint">'.$t('lbl.trig_hint', [$data['trigger_count']]).'</div>'
  262. .'<form method="get" action="zabbix.php"><input type="hidden" name="action" value="wecom.center.view">'
  263. .'<input type="hidden" name="mediatypeid" value="'.$mtid.'"><input type="hidden" name="tab" value="maint">'
  264. .'<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>'
  265. .'<button class="wc-btn wc-btn--primary" type="submit" name="search" value="1">'.$t('btn.search').'</button></div></form>';
  266. $maintBody = $searchBody;
  267. if ($data['trig_search'] !== '') {
  268. if ($data['triggers']) {
  269. $ids = [];
  270. $rows = '';
  271. foreach ($data['triggers'] as $tg) {
  272. $ids[] = $tg['triggerid'];
  273. $host = !empty($tg['hosts']) ? $tg['hosts'][0]['host'] : '';
  274. $chk = ((int) $tg['status'] === 0) ? ' checked' : '';
  275. $sevSel = '';
  276. for ($p = 0; $p <= 5; $p++) {
  277. $sevSel .= '<option value="'.$p.'"'.(((int) $tg['priority'] === $p) ? ' selected' : '').'>'.$t('sev.'.($p + 1)).'</option>';
  278. }
  279. $rows .= '<div class="wc-trow">'
  280. .'<label class="wc-tcheck"><input type="checkbox" name="trig_'.$e($tg['triggerid']).'"'.$chk.'> <span class="wc-tmain">'.($host !== '' ? $e($host).' — ' : '').$e($tg['description']).'</span></label>'
  281. .'<select class="wc-input" name="pse_'.$e($tg['triggerid']).'">'.$sevSel.'</select>'
  282. .'</div>';
  283. }
  284. $maintBody .= '<div class="wc-divider"></div>'
  285. .'<form method="post" action="'.$save.'">'
  286. .'<input type="hidden" name="trig_ids" value="'.$e(implode(',', $ids)).'">'
  287. .'<input type="hidden" name="tab" value="maint">'
  288. .'<input type="hidden" name="trig_search" value="'.$e($data['trig_search']).'">'
  289. .'<div class="wc-hint">'.$t('lbl.trig_edit_hint').'</div>'
  290. .'<div class="wc-field"><label>'.$t('lbl.trig_list').'</label>'.$rows.'</div>'
  291. .'<div class="wc-actions"><button class="wc-btn wc-btn--primary" type="submit" name="op" value="save_triggers">'.$t('btn.save').'</button></div>'
  292. .'</form>';
  293. }
  294. else {
  295. $maintBody .= '<div class="wc-divider"></div><div class="wc-empty">'.$t('msg.no_trig_search').'</div>';
  296. }
  297. }
  298. $trigCard = $card('&#128295;', $t('sec.triggers'), $maintBody, ' wc-card--wide');
  299. $tab_maint = '<div class="wc-grid">'.$classCard.$trigCard.'</div>';
  300. /* ======================== TAB 4: L2 TOPOLOGY CONFIG ================== */
  301. $l2 = $data['l2'];
  302. // templates multi-select
  303. $tplSel = '';
  304. foreach ($data['l2_templates'] as $tn) {
  305. $sel = in_array($tn, (array) $l2['templates'], true) ? ' selected' : '';
  306. $tplSel .= '<option value="'.$e($tn).'"'.$sel.'>'.$e($tn).'</option>';
  307. }
  308. $collectBody =
  309. '<div class="wc-hint">'.$t('lbl.l2_templates_hint').'</div>'
  310. .'<div class="wc-field"><label>'.$t('lbl.l2_templates').'</label>'
  311. .'<select class="wc-input wc-input--multi" name="templates[]" multiple size="8">'.$tplSel.'</select></div>'
  312. .'<div class="wc-inline">'
  313. .'<div class="wc-field"><label>'.$t('lbl.l2_full').'</label><input class="wc-input" type="number" min="1" name="full_interval_min" value="'.$e($l2['full_interval_min']).'"></div>'
  314. .'<div class="wc-field"><label>'.$t('lbl.l2_alarm').'</label><input class="wc-input" type="number" min="1" name="alarm_interval_min" value="'.$e($l2['alarm_interval_min']).'"></div>'
  315. .'</div>';
  316. $collectCard = $card('&#128225;', $t('sec.l2_collect'), $collectBody, ' wc-card--wide');
  317. $displayBody =
  318. '<div class="wc-inline">'
  319. .'<div class="wc-field"><label>'.$t('lbl.l2_refresh').'</label><input class="wc-input" type="number" min="30" step="30" name="refresh_seconds" value="'.$e($l2['refresh_seconds']).'"></div>'
  320. .'<div class="wc-field"><label>'.$t('lbl.l2_root').'</label><input class="wc-input" type="text" name="root_label" value="'.$e($l2['root_label']).'"></div>'
  321. .'<div class="wc-field"><label>'.$t('lbl.l2_other').'</label><input class="wc-input" type="text" name="other_label" value="'.$e($l2['other_label']).'"></div>'
  322. .'</div>';
  323. $displayCard = $card('&#128421;&#65039;', $t('sec.l2_display'), $displayBody);
  324. $netLines = '';
  325. foreach ((array) ($l2['networks'] ?? []) as $nw) {
  326. $netLines .= ($nw['key'] ?? '').' | '.($nw['label'] ?? '').' | '.implode(',', (array) ($nw['keywords'] ?? [])).' | '.($nw['color'] ?? '#607D8B')."\n";
  327. }
  328. $networkBody =
  329. '<div class="wc-hint">'.$t('lbl.l2_networks_hint').'</div>'
  330. .'<div class="wc-field"><label>'.$t('lbl.l2_networks').'</label>'
  331. .'<textarea class="wc-input wc-input--mono" name="networks_text" rows="5" style="min-height:120px">'.$e(trim($netLines)).'</textarea></div>';
  332. $networkCard = $card('&#127760;', $t('sec.l2_networks'), $networkBody, ' wc-card--wide');
  333. $coreRowHtml = function (array $r) use ($e, $t, $l2) {
  334. $netSel = '<select class="wc-input" name="core_net[]" style="max-width:230px">'
  335. .'<option value="">'.$t('lbl.core_any').'</option>';
  336. foreach ((array) $l2['networks'] as $nw) {
  337. $k = (string) ($nw['key'] ?? '');
  338. $netSel .= '<option value="'.$e($k).'"'.((string) ($r['net'] ?? '') === $k ? ' selected' : '').'>'.$e($k).' · '.$e($nw['label'] ?? $k).'</option>';
  339. }
  340. $netSel .= '</select>';
  341. $isRegex = (($r['match'] ?? 'contains') === 'regex');
  342. return '<div class="wc-rule">'.$netSel
  343. .'<select class="wc-input" name="core_match[]" style="max-width:120px">'
  344. .'<option value="contains"'.($isRegex ? '' : ' selected').'>contains</option>'
  345. .'<option value="regex"'.($isRegex ? ' selected' : '').'>regex</option></select>'
  346. .'<input class="wc-input wc-input--mono" name="core_pattern[]" value="'.$e($r['pattern'] ?? '').'" placeholder="'.$t('lbl.core_pattern').'">'
  347. .'<button type="button" class="wc-btn wc-btn--ghost wc-rule-del" title="remove">&times;</button></div>';
  348. };
  349. $coreRows = '';
  350. foreach ((array) ($l2['core_rules'] ?? []) as $r) {
  351. $coreRows .= $coreRowHtml($r);
  352. }
  353. $coreBody =
  354. '<div class="wc-hint">'.$t('lbl.l2_cores_hint').'</div>'
  355. .'<div class="wc-rules" id="wc-cores">'.$coreRows.'</div>'
  356. .'<div class="wc-actions"><button type="button" class="wc-btn wc-btn--ghost" id="wc-core-add">'.$t('btn.add_core').'</button></div>'
  357. .'<script type="text/template" id="wc-core-tpl">'.$coreRowHtml(['net' => '', 'match' => 'contains', 'pattern' => '']).'</script>';
  358. $coreCard = $card('&#128273;', $t('sec.l2_cores'), $coreBody, ' wc-card--wide');
  359. $colorFields = '';
  360. foreach (['100' => 'lbl.color_100', '1000' => 'lbl.color_1000', '10000' => 'lbl.color_10000',
  361. '20000' => 'lbl.color_20000', 'unknown' => 'lbl.color_unknown', 'down' => 'lbl.color_down'] as $ck => $lab) {
  362. $colorFields .= '<div class="wc-field" style="flex:1;min-width:110px"><label>'.$t($lab).'</label>'
  363. .'<input class="wc-input wc-input--color" type="color" name="color_'.$ck.'" value="'.$e($l2['colors'][$ck] ?? '#000000').'"></div>';
  364. }
  365. $colorCard = $card('&#127912;', $t('sec.l2_colors'), '<div class="wc-inline">'.$colorFields.'</div>', ' wc-card--wide');
  366. $latBody =
  367. '<div class="wc-inline">'
  368. .'<div class="wc-field"><label>'.$t('lbl.lat_warn').'</label><input class="wc-input" type="number" step="0.1" min="0" name="latency_warn" value="'.$e($l2['latency_warn']).'"></div>'
  369. .'<div class="wc-field"><label>'.$t('lbl.lat_bad').'</label><input class="wc-input" type="number" step="0.1" min="0" name="latency_bad" value="'.$e($l2['latency_bad']).'"></div>'
  370. .'</div>';
  371. $latCard = $card('&#9201;&#65039;', $t('sec.l2_latency'), $latBody);
  372. $ruleRowHtml = function (array $r) use ($e, $t) {
  373. $isRegex = (($r['match'] ?? 'contains') === 'regex');
  374. $big = !empty($r['big']);
  375. return '<div class="wc-rule">'
  376. .'<select class="wc-input" name="rule_match[]" style="max-width:120px">'
  377. .'<option value="contains"'.($isRegex ? '' : ' selected').'>contains</option>'
  378. .'<option value="regex"'.($isRegex ? ' selected' : '').'>regex</option></select>'
  379. .'<input class="wc-input wc-input--mono" name="rule_pattern[]" value="'.$e($r['pattern'] ?? '').'" placeholder="e.g. 4401">'
  380. .'<input class="wc-input" name="rule_label[]" value="'.$e($r['label'] ?? '').'" placeholder="Frame label">'
  381. .'<select class="wc-input" name="rule_big[]" style="max-width:90px">'
  382. .'<option value="0"'.($big ? '' : ' selected').'>'.$t('lbl.rule_big').' −</option>'
  383. .'<option value="1"'.($big ? ' selected' : '').'>'.$t('lbl.rule_big').' +</option></select>'
  384. .'<button type="button" class="wc-btn wc-btn--ghost wc-rule-del" title="remove">&times;</button>'
  385. .'</div>';
  386. };
  387. $ruleRows = '';
  388. foreach ((array) $l2['group_rules'] as $r) {
  389. $ruleRows .= $ruleRowHtml($r);
  390. }
  391. $groupBody =
  392. '<div class="wc-hint">'.$t('lbl.rule_hint').'</div>'
  393. .'<div class="wc-rules" id="wc-rules">'.$ruleRows.'</div>'
  394. .'<div class="wc-actions"><button type="button" class="wc-btn wc-btn--ghost" id="wc-rule-add">'.$t('btn.add_rule').'</button></div>'
  395. .'<script type="text/template" id="wc-rule-tpl">'.$ruleRowHtml(['match' => 'contains', 'pattern' => '', 'label' => '', 'big' => false]).'</script>';
  396. $groupCard = $card('&#128193;', $t('sec.l2_group'), $groupBody, ' wc-card--wide');
  397. $tab_l2 = '<form method="post" action="'.$save.'" id="wc-l2-form">'
  398. .'<input type="hidden" name="op" value="save_l2"><input type="hidden" name="tab" value="l2">'
  399. .'<input type="hidden" name="core_rules_present" value="1">'
  400. .'<div class="wc-grid">'.$collectCard.$displayCard.$networkCard.$coreCard.$colorCard.$latCard.$groupCard.'</div>'
  401. .'<div class="wc-actions" style="margin-top:18px"><button class="wc-btn wc-btn--primary" type="submit">'.$t('btn.save').'</button></div>'
  402. .'</form>'
  403. .'<script>(function(){function wire(addId,boxId,tplId){var add=document.getElementById(addId),box=document.getElementById(boxId),tpl=document.getElementById(tplId);'
  404. .'if(add&&box&&tpl){add.addEventListener("click",function(){var w=document.createElement("div");w.innerHTML=tpl.innerHTML.trim();box.appendChild(w.firstChild);});}'
  405. .'if(box){box.addEventListener("click",function(ev){if(ev.target.classList.contains("wc-rule-del")){var r=ev.target.closest(".wc-rule");if(r&&box.querySelectorAll(".wc-rule").length>1){r.remove();}}});}}'
  406. .'wire("wc-rule-add","wc-rules","wc-rule-tpl");wire("wc-core-add","wc-cores","wc-core-tpl");'
  407. .'})();</script>';
  408. /* ============================ assemble =============================== */
  409. $hero = '<div class="wc-hero"><div class="wc-hero__title">'.$t('page.title').'</div>'
  410. .'<div class="wc-hero__sub">'.$t('hero.sub').'</div></div>';
  411. $tabbar = '<div class="wc-tabs">'
  412. .'<a class="wc-tab'.($active_tab === 'alert' ? ' is-active' : '').'" href="'.$tabHref('alert').'">'.$t('tab.alert').'</a>'
  413. .'<a class="wc-tab'.($active_tab === 'opt' ? ' is-active' : '').'" href="'.$tabHref('opt').'">'.$t('tab.opt').'</a>'
  414. .'<a class="wc-tab'.($active_tab === 'maint' ? ' is-active' : '').'" href="'.$tabHref('maint').'">'.$t('tab.maint').'</a>'
  415. .'<a class="wc-tab'.($active_tab === 'l2' ? ' is-active' : '').'" href="'.$tabHref('l2').'">'.$t('tab.l2').'</a>'
  416. .'</div>';
  417. $content = ($active_tab === 'opt') ? $tab_opt : (($active_tab === 'maint') ? $tab_maint : (($active_tab === 'l2') ? $tab_l2 : $tab_alert));
  418. $html_page->addItem($raw('<div class="wc-wrap">'.$hero.$tabbar.$content.'</div>'));
  419. $html_page->show();