WecomCenterSave.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <?php declare(strict_types = 0);
  2. /*
  3. ** WeCom Center - POST controller. Dispatches actions and redirects back.
  4. */
  5. namespace Modules\WecomCenter\Actions;
  6. use CController;
  7. use CControllerResponseRedirect;
  8. use CUrl;
  9. use CWebUser;
  10. use Exception;
  11. use Modules\WecomCenter\Lib\Helper;
  12. use Modules\WecomCenter\Lib\I18n;
  13. use Modules\WecomCenter\Lib\L2Store;
  14. class WecomCenterSave extends CController {
  15. protected function init() {
  16. $this->disableCsrfValidation();
  17. }
  18. protected function checkInput() {
  19. return true;
  20. }
  21. protected function checkPermissions() {
  22. return CWebUser::getType() == USER_TYPE_SUPER_ADMIN;
  23. }
  24. protected function doAction() {
  25. $mediatypeid = (string) ($_REQUEST['mediatypeid'] ?? '');
  26. try {
  27. // Strategy apply buttons are submitted as name="apply_strategy" value=<preset>.
  28. if (isset($_REQUEST['apply_strategy'])) {
  29. $this->applyStrategy($mediatypeid, (string) $_REQUEST['apply_strategy']);
  30. }
  31. else {
  32. $op = (string) ($_REQUEST['op'] ?? '');
  33. switch ($op) {
  34. case 'save_alert': $this->saveAlert($mediatypeid); break;
  35. case 'save_triggers': $this->saveTriggers(); break;
  36. case 'save_class_sev': $this->saveClassSeverity(); break;
  37. case 'save_robot': $this->saveRobot($mediatypeid); break;
  38. case 'test_robot': $this->testRobot($mediatypeid); break;
  39. case 'import_template': $this->importTemplate(); break;
  40. case 'save_map': $this->saveMap(); break;
  41. case 'apply_switch': $this->applySwitch(); break;
  42. case 'init_env': $this->initEnv(); break;
  43. case 'save_l2': $this->saveL2(); break;
  44. default: error(I18n::t('msg.unknown_op'));
  45. }
  46. }
  47. }
  48. catch (Exception $e) {
  49. error($e->getMessage());
  50. }
  51. $url = (new CUrl('zabbix.php'))->setArgument('action', 'wecom.center.view');
  52. if ($mediatypeid !== '') {
  53. $url->setArgument('mediatypeid', $mediatypeid);
  54. }
  55. $tab = (string) ($_REQUEST['tab'] ?? '');
  56. if (in_array($tab, ['alert', 'opt', 'maint', 'l2'], true)) {
  57. $url->setArgument('tab', $tab);
  58. if ($tab === 'maint') {
  59. $ts = (string) ($_REQUEST['trig_search'] ?? '');
  60. if ($ts !== '') {
  61. $url->setArgument('trig_search', $ts);
  62. }
  63. }
  64. }
  65. $this->setResponse(new CControllerResponseRedirect($url));
  66. }
  67. private function applyStrategy(string $mediatypeid, string $key): void {
  68. if (!isset(Helper::STRATEGY_PRESETS[$key])) {
  69. throw new Exception(I18n::t('msg.bad_scheme'));
  70. }
  71. $tid = Helper::templateHostid();
  72. if ($tid === null) {
  73. throw new Exception(I18n::t('msg.no_template_host'));
  74. }
  75. $preset = Helper::STRATEGY_PRESETS[$key];
  76. // 1) threshold on the media (global "how severe still notifies")
  77. if ($mediatypeid !== '') {
  78. $user = Helper::ensureRobotUser('wecomrobot');
  79. $mask = Helper::severityMaskFromMin((int) $preset['min']);
  80. Helper::applyMedia($user['userid'], $mediatypeid, $mask, 0);
  81. }
  82. // 2) classify + write severities
  83. $res = Helper::applyStrategy($tid, $preset['targets']);
  84. Helper::saveStrategyKey($key);
  85. info(I18n::t('msg.strategy_applied', [
  86. I18n::t($preset['label']),
  87. $res['trigger'],
  88. $res['prototype'],
  89. $res['discovered']
  90. ]));
  91. }
  92. private function saveAlert(string $mediatypeid): void {
  93. $active = isset($_REQUEST['enabled']) ? 0 : 1; // media: 0=enabled, 1=disabled
  94. $indices = [];
  95. for ($i = 1; $i <= 6; $i++) {
  96. if (isset($_REQUEST['sev_'.$i])) {
  97. $indices[] = $i;
  98. }
  99. }
  100. $mask = Helper::severityMask($indices);
  101. if ($mask === 0 && $active === 0) {
  102. $mask = 2;
  103. }
  104. $user = Helper::ensureRobotUser('wecomrobot');
  105. Helper::applyMedia($user['userid'], $mediatypeid, $mask, $active);
  106. info(I18n::t('msg.saved'));
  107. }
  108. private function saveRobot(string $mediatypeid): void {
  109. $url = trim((string) ($_REQUEST['webhook_url'] ?? ''));
  110. if ($url !== '') {
  111. Helper::setWebhookKey($url);
  112. }
  113. $user = Helper::ensureRobotUser('wecomrobot');
  114. $cur = Helper::currentMedia($user, $mediatypeid);
  115. $mask = $cur['severity'] ?? 48;
  116. $active = $cur['active'] ?? 0;
  117. Helper::applyMedia($user['userid'], $mediatypeid, $mask, $active);
  118. info(I18n::t('msg.robot_saved'));
  119. }
  120. private function testRobot(string $mediatypeid): void {
  121. $mt = Helper::getMediatype($mediatypeid);
  122. if (!$mt) {
  123. throw new Exception(I18n::t('msg.no_mediatype'));
  124. }
  125. $url = Helper::webhookUrl($mt);
  126. if ($url === '') {
  127. throw new Exception(I18n::t('msg.no_url'));
  128. }
  129. $text = '['.I18n::t('page.title').'] Test OK - '.date('Y-m-d H:i:s');
  130. $res = Helper::testWebhook($url, $text);
  131. $body = (string) $res['body'];
  132. $ok = (strpos($body, '"errcode":0') !== false) || (strpos($body, '"errcode": 0') !== false);
  133. if ($ok) {
  134. info(I18n::t('msg.tested'));
  135. }
  136. else {
  137. error(I18n::t('msg.test_failed', [$res['status'], $body]));
  138. }
  139. }
  140. private function importTemplate(): void {
  141. $file = (string) ($_REQUEST['template_file'] ?? '');
  142. if ($file === '') {
  143. throw new Exception(I18n::t('msg.no_template'));
  144. }
  145. $module_dir = dirname(__DIR__);
  146. $res = Helper::importTemplate($module_dir, $file);
  147. $n = isset($res['templates']) && is_array($res['templates']) ? count($res['templates']) : 0;
  148. info(I18n::t('msg.imported', [$n]));
  149. }
  150. private function saveTriggers(): void {
  151. $ids = (string) ($_REQUEST['trig_ids'] ?? '');
  152. $ids = array_filter(array_map('trim', explode(',', $ids)));
  153. if (!$ids) {
  154. throw new Exception(I18n::t('msg.no_triggers'));
  155. }
  156. $changed = 0;
  157. $sev_changed = 0;
  158. foreach ($ids as $id) {
  159. $id = (int) $id;
  160. if ($id <= 0) {
  161. continue;
  162. }
  163. $status = isset($_REQUEST['trig_'.$id]) ? 0 : 1; // checkbox = enabled
  164. $priority = null;
  165. if (isset($_REQUEST['pse_'.$id]) && $_REQUEST['pse_'.$id] !== '') {
  166. $priority = (int) $_REQUEST['pse_'.$id];
  167. $sev_changed++;
  168. }
  169. Helper::setTriggerState((string) $id, $priority, $status);
  170. $changed++;
  171. }
  172. info(I18n::t('msg.triggers_saved', [$changed]));
  173. }
  174. private function saveClassSeverity(): void {
  175. $ck = (string) ($_REQUEST['class_key'] ?? '');
  176. $p = (int) ($_REQUEST['class_priority'] ?? -1);
  177. if ($ck === '' || $p < 0 || $p > 5) {
  178. throw new Exception(I18n::t('msg.bad_class_sev'));
  179. }
  180. $r = Helper::setClassSeverity($ck, $p);
  181. info(I18n::t('msg.class_sev_saved', [$r['trigger'] + $r['prototype'], $r['discovered']]));
  182. }
  183. private function saveMap(): void {
  184. $hostid = (string) ($_REQUEST['hostid'] ?? '');
  185. $lat = trim((string) ($_REQUEST['lat'] ?? ''));
  186. $lon = trim((string) ($_REQUEST['lon'] ?? ''));
  187. if ($hostid === '') {
  188. throw new Exception(I18n::t('msg.no_host'));
  189. }
  190. Helper::setHostLocation($hostid, $lat, $lon);
  191. info(I18n::t('msg.map_saved'));
  192. }
  193. private function applySwitch(): void {
  194. $src = (string) ($_REQUEST['switch_src'] ?? '');
  195. $dst = (string) ($_REQUEST['switch_dst'] ?? '');
  196. if ($src === '' || $dst === '') {
  197. throw new Exception(I18n::t('msg.need_both'));
  198. }
  199. if ($src === $dst) {
  200. throw new Exception(I18n::t('msg.same_tpl'));
  201. }
  202. $n = Helper::switchApply($src, $dst);
  203. info(I18n::t('msg.switch_applied', [$n]));
  204. }
  205. private function initEnv(): void {
  206. $key = trim((string) ($_REQUEST['key'] ?? $_REQUEST['webhook_url'] ?? ''));
  207. $proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
  208. $host = (string) ($_SERVER['HTTP_HOST'] ?? '');
  209. $zbxUrl = $host !== '' ? $proto.'://'.$host : '';
  210. $r = Helper::initializeEnvironment($key, $zbxUrl);
  211. $parts = [];
  212. $parts[] = $r['created_mediatype'] ? I18n::t('msg.init_mediatype_new') : I18n::t('msg.init_mediatype_ok');
  213. $parts[] = $r['created_user'] ? I18n::t('msg.init_user_new') : I18n::t('msg.init_user_ok');
  214. $parts[] = $r['created_action'] ? I18n::t('msg.init_action_new') : I18n::t('msg.init_action_ok');
  215. if ($r['key_set']) {
  216. $parts[] = I18n::t('msg.init_key_set');
  217. }
  218. info(I18n::t('msg.init_done', [implode(' · ', $parts)]));
  219. }
  220. private function saveL2(): void {
  221. $cfg = L2Store::getConfig();
  222. // templates (multi-select)
  223. $tpls = $_REQUEST['templates'] ?? [];
  224. if (is_array($tpls)) {
  225. $tpls = array_values(array_filter(array_map('strval', $tpls), function ($s) { return trim($s) !== ''; }));
  226. $cfg['templates'] = $tpls;
  227. }
  228. $cfg['refresh_seconds'] = max(30, (int) ($_REQUEST['refresh_seconds'] ?? $cfg['refresh_seconds']));
  229. $cfg['full_interval_min'] = max(1, (int) ($_REQUEST['full_interval_min'] ?? $cfg['full_interval_min']));
  230. $cfg['alarm_interval_min'] = max(1, (int) ($_REQUEST['alarm_interval_min'] ?? $cfg['alarm_interval_min']));
  231. $cfg['history_interval_min'] = max(5, min(1440, (int) ($_REQUEST['history_interval_min'] ?? $cfg['history_interval_min'])));
  232. $cfg['history_days'] = max(1, min(30, (int) ($_REQUEST['history_days'] ?? $cfg['history_days'])));
  233. $cfg['root_label'] = trim((string) ($_REQUEST['root_label'] ?? $cfg['root_label']));
  234. if ($cfg['root_label'] === '') { $cfg['root_label'] = 'COBCO'; }
  235. $cfg['other_label'] = trim((string) ($_REQUEST['other_label'] ?? $cfg['other_label']));
  236. if ($cfg['other_label'] === '') { $cfg['other_label'] = 'Other'; }
  237. // Networks: one line per network, pipe separated:
  238. // KEY | display label | keyword1,keyword2 | #RRGGBB
  239. $netText = trim((string) ($_REQUEST['networks_text'] ?? ''));
  240. if ($netText !== '') {
  241. $networks = [];
  242. foreach (preg_split('/\R/u', $netText) as $line) {
  243. $line = trim($line);
  244. if ($line === '') { continue; }
  245. $p = array_map('trim', explode('|', $line));
  246. $key = strtoupper((string) ($p[0] ?? ''));
  247. $label = (string) ($p[1] ?? $key);
  248. if ($key === '' || $label === '') { continue; }
  249. $kws = array_values(array_filter(array_map('trim', preg_split('/[,,;;\s]+/u', (string) ($p[2] ?? $key))), 'strlen'));
  250. $color = (string) ($p[3] ?? '#607D8B');
  251. if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) { $color = '#607D8B'; }
  252. $networks[] = ['key' => $key, 'label' => $label, 'keywords' => $kws, 'color' => $color];
  253. }
  254. if ($networks) { $cfg['networks'] = $networks; }
  255. }
  256. // Core hierarchy: optional upstream roles (empty pattern = hidden) and
  257. // the sub-core definitions (monitoring / office / future production).
  258. if (isset($_REQUEST['core_hierarchy_present'])) {
  259. $netKeys = array_map(function ($n) { return (string) ($n['key'] ?? ''); }, (array) $cfg['networks']);
  260. $hier = [];
  261. foreach (['firewall', 'behavior', 'total_core'] as $role) {
  262. $match = (string) ($_REQUEST['hier_'.$role.'_match'] ?? 'contains');
  263. if ($match !== 'regex') { $match = 'contains'; }
  264. $hier[$role] = [
  265. 'match' => $match,
  266. 'pattern' => trim((string) ($_REQUEST['hier_'.$role.'_pattern'] ?? ''))
  267. ];
  268. }
  269. $subNet = (array) ($_REQUEST['sub_net'] ?? []);
  270. $subKey = (array) ($_REQUEST['sub_key'] ?? []);
  271. $subLabel = (array) ($_REQUEST['sub_label'] ?? []);
  272. $subMatch = (array) ($_REQUEST['sub_match'] ?? []);
  273. $subPattern = (array) ($_REQUEST['sub_pattern'] ?? []);
  274. $subs = [];
  275. $coreRules = [];
  276. foreach ($subKey as $i => $key) {
  277. $pattern = trim((string) ($subPattern[$i] ?? ''));
  278. $net = strtoupper(trim((string) ($subNet[$i] ?? '')));
  279. if ($net !== '' && !in_array($net, $netKeys, true)) { $net = ''; }
  280. $match = (string) ($subMatch[$i] ?? 'contains');
  281. if ($match !== 'regex') { $match = 'contains'; }
  282. $key = preg_replace('/[^a-z0-9_]/i', '', (string) $key);
  283. if ($key === '') { $key = 'sub'.($i + 1); }
  284. $label = trim((string) ($subLabel[$i] ?? ''));
  285. $subs[] = [
  286. 'key' => $key,
  287. 'label' => $label !== '' ? $label : $key,
  288. 'net' => $net,
  289. 'match' => $match,
  290. 'pattern' => $pattern
  291. ];
  292. if ($pattern !== '') {
  293. $coreRules[] = ['net' => $net, 'match' => $match, 'pattern' => $pattern];
  294. }
  295. }
  296. $hier['sub_cores'] = $subs;
  297. $cfg['core_hierarchy'] = $hier;
  298. // Keep the old flat key in sync for collectors/frontends that
  299. // still read core_rules. Empty future-core rows are retained in
  300. // core_hierarchy as visible placeholder configuration but are not
  301. // emitted as active matching rules.
  302. $cfg['core_rules'] = $coreRules;
  303. }
  304. elseif (isset($_REQUEST['core_rules_present'])) {
  305. $coreNets = (array) ($_REQUEST['core_net'] ?? []);
  306. $coreMatch = (array) ($_REQUEST['core_match'] ?? []);
  307. $corePattern = (array) ($_REQUEST['core_pattern'] ?? []);
  308. $netKeys = array_map(function ($n) { return (string) ($n['key'] ?? ''); }, (array) $cfg['networks']);
  309. $coreRules = [];
  310. foreach ($coreNets as $i => $net) {
  311. $net = strtoupper(trim((string) $net));
  312. if ($net !== '' && !in_array($net, $netKeys, true)) { $net = ''; }
  313. $match = (string) ($coreMatch[$i] ?? 'contains');
  314. if ($match !== 'regex') { $match = 'contains'; }
  315. $pattern = trim((string) ($corePattern[$i] ?? ''));
  316. if ($pattern === '') { continue; }
  317. $coreRules[] = ['net' => $net, 'match' => $match, 'pattern' => $pattern];
  318. }
  319. $cfg['core_rules'] = $coreRules;
  320. }
  321. $cfg['latency_warn'] = (float) ($_REQUEST['latency_warn'] ?? $cfg['latency_warn']);
  322. $cfg['latency_bad'] = (float) ($_REQUEST['latency_bad'] ?? $cfg['latency_bad']);
  323. $colors = $cfg['colors'];
  324. foreach (['100', '1000', '2500', '10000', '20000', 'unknown', 'down'] as $k) {
  325. $v = trim((string) ($_REQUEST['color_'.$k] ?? ''));
  326. if ($v !== '' && preg_match('/^#[0-9a-fA-F]{6}$/', $v)) {
  327. $colors[$k] = $v;
  328. }
  329. }
  330. $cfg['colors'] = $colors;
  331. // grouping rules (parallel arrays)
  332. $rm = $_REQUEST['rule_match'] ?? [];
  333. $rp = $_REQUEST['rule_pattern'] ?? [];
  334. $rl = $_REQUEST['rule_label'] ?? [];
  335. $rb = $_REQUEST['rule_big'] ?? [];
  336. $rules = [];
  337. if (is_array($rm)) {
  338. $n = count($rm);
  339. for ($i = 0; $i < $n; $i++) {
  340. $pat = trim((string) ($rp[$i] ?? ''));
  341. $lab = trim((string) ($rl[$i] ?? ''));
  342. if ($pat === '' || $lab === '') { continue; }
  343. $rules[] = [
  344. 'match' => ((string) ($rm[$i] ?? 'contains') === 'regex') ? 'regex' : 'contains',
  345. 'pattern' => $pat,
  346. 'label' => $lab,
  347. 'big' => !empty($rb[$i])
  348. ];
  349. }
  350. }
  351. $cfg['group_rules'] = $rules;
  352. L2Store::saveConfig($cfg);
  353. info(I18n::t('msg.l2_saved'));
  354. }
  355. }