WecomCenterSave.php 14 KB

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