Преглед на файлове

feat(web-modules): add per-trigger alerting switch (search + checkboxes) to WeCom Center

FlyingRome преди 4 дни
родител
ревизия
a39e33d81e

+ 3 - 2
web-modules/README.md

@@ -3,6 +3,7 @@
 一个自包含、多语言的 Zabbix 7.4 前端模块(custom module / plugin)。加载后会在 **Administration → WeCom Center** 增加一个定制页面,集中处理:
 
 - **告警优化(核心)**:以勾选方式一键切换“是否告警”与通知严重度;内置 5 套告警方案(标准 / 警告以上 / 全部 / 仅灾难 / 关闭),点一下立即生效。
+- **触发器告警开关**:按名称搜索触发器,勾选 = 启用告警、取消勾选 = 停用该触发器(写 `trigger.status`,0 启用 / 1 停用)。由于单套环境常有数万个触发器,需先输入关键字搜索(最多显示 200 条)。
 - **机器人与账号**:在同一页编辑企业微信 Webhook 地址 / 机器人 key、机器人账号、接收目标,并可“发送测试”验证(直接调用企微接口,返回 errcode)。
 - **模板导入**:把放在 `templates/` 目录里的模板文件(yaml/xml/json)一键导入 Zabbix。
 - **地图坐标**:批量/单个设置主机的 `location_lat` / `location_lon`(主机资产清单)。
@@ -67,8 +68,8 @@ WecomCenter/
 - `manifest.json` 用 `manifest_version: 2.0`;action 的控制器类位于 `actions/`(自动加载器会把子命名空间段转小写)。
 - 视图目录必须在 `Module::init()` 里 `CView::registerDirectory($this->getRelativePath().'/views')`。
 - 菜单注入:`APP::Component()->get('menu.main')->findOrAdd(_('Administration'))->getSubmenu()->add(...)`。
-- 关键 API 注意:`mediatype.get` **不支持 `sortfield=name`**(会整体返回 false);`configuration.import` 的规则键是复数 `templates`(不是 `template`)。
-- 告警“开关”落在用户媒介的 `active` 字段:**0=启用、1=停用**;严重度为位掩码(未分类=1…灾难=32)。
+- 关键 API 注意:`mediatype.get` **不支持 `sortfield=name`**(会整体返回 false);`configuration.import` 的规则键是复数 `templates`(不是 `template`);`trigger.update` **不接受 `triggers` 数组**,只能逐条用 `triggerid` 更新。
+- 告警“开关”落在用户媒介的 `active` 字段:**0=启用、1=停用**;严重度为位掩码(未分类=1…灾难=32)。触发器“是否告警”落在 `trigger.status`:**0=启用、1=停用**。
 
 ## 告警媒介说明
 

+ 22 - 0
web-modules/WecomCenter/actions/WecomCenterSave.php

@@ -48,6 +48,9 @@ class WecomCenterSave extends CController {
 					case 'save_alert':
 						$this->saveAlert($mediatypeid);
 						break;
+					case 'save_triggers':
+						$this->saveTriggers();
+						break;
 					case 'save_robot':
 						$this->saveRobot($mediatypeid);
 						break;
@@ -158,6 +161,25 @@ class WecomCenterSave extends CController {
 		info(I18n::t('msg.imported', [$n]));
 	}
 
+	private function saveTriggers(): void {
+		$ids = (string) ($_REQUEST['trig_ids'] ?? '');
+		$ids = array_filter(array_map('trim', explode(',', $ids)));
+		if (!$ids) {
+			throw new Exception(I18n::t('msg.no_triggers'));
+		}
+		$map = [];
+		foreach ($ids as $id) {
+			$id = (string) (int) $id;
+			if ($id === '' || $id === '0') {
+				continue;
+			}
+			// checked => alerting enabled => trigger status 0; unchecked => disabled => 1
+			$map[$id] = isset($_REQUEST['trig_'.$id]) ? 0 : 1;
+		}
+		$n = Helper::setTriggersStatus($map);
+		info(I18n::t('msg.triggers_saved', [$n]));
+	}
+
 	private function saveMap(): void {
 		$hostid = (string) ($_REQUEST['hostid'] ?? '');
 		$lat = trim((string) ($_REQUEST['lat'] ?? ''));

+ 8 - 0
web-modules/WecomCenter/actions/WecomCenterView.php

@@ -70,6 +70,11 @@ class WecomCenterView extends CController {
 			];
 		}
 
+		// triggers (per-trigger alerting switch, requires a search term)
+		$trig_search = trim((string) ($_REQUEST['trig_search'] ?? ''));
+		$triggers = Helper::triggers($trig_search);
+		$trigger_count = Helper::triggerCount();
+
 		$data = [
 			'title'        => I18n::t('page.title'),
 			'mediatypes'   => $mediatypes,
@@ -87,6 +92,9 @@ class WecomCenterView extends CController {
 			'hosts'        => Helper::hosts(),
 			'selected_hostid' => $selected_hostid,
 			'host_loc'     => $host_loc,
+			'trig_search'  => $trig_search,
+			'triggers'     => $triggers,
+			'trigger_count' => $trigger_count,
 			'lang'         => I18n::lang()
 		];
 

+ 58 - 0
web-modules/WecomCenter/lib/Helper.php

@@ -341,4 +341,62 @@ class Helper {
 		}
 		return $mask;
 	}
+
+	/* ------------------------------------------------------------------ */
+	/*  Triggers (per-trigger alerting switch)                            */
+	/* ------------------------------------------------------------------ */
+
+	/**
+	 * Search triggers by description. An empty search returns nothing on
+	 * purpose - the instance has tens of thousands of triggers, listing all
+	 * is pointless. Results are capped.
+	 */
+	public static function triggers(string $search, int $limit = 200): array {
+		$search = trim($search);
+		if ($search === '') {
+			return [];
+		}
+		try {
+			$res = API::Trigger()->get([
+				'output'      => ['triggerid', 'description', 'status', 'priority'],
+				'selectHosts' => ['host'],
+				'search'      => ['description' => $search],
+				'limit'       => $limit
+			]);
+			return is_array($res) ? $res : [];
+		}
+		catch (\Throwable $e) {
+			return [];
+		}
+	}
+
+	public static function triggerCount(): int {
+		try {
+			$n = API::Trigger()->get(['countOutput' => true]);
+			return (int) $n;
+		}
+		catch (\Throwable $e) {
+			return 0;
+		}
+	}
+
+	/**
+	 * Set trigger status. $map = [triggerid => status(0 enabled,1 disabled)].
+	 * NB: this Zabbix version's trigger.update does NOT accept a "triggers"
+	 * array, so we update one trigger at a time.
+	 */
+	public static function setTriggersStatus(array $map): int {
+		$changed = 0;
+		foreach ($map as $tid => $status) {
+			$res = API::Trigger()->update([
+				'triggerid' => (string) $tid,
+				'status'    => (string) ((int) $status)
+			]);
+			if ($res === false) {
+				throw new Exception('trigger.update failed for '.$tid);
+			}
+			$changed++;
+		}
+		return $changed;
+	}
 }

+ 8 - 0
web-modules/WecomCenter/locale/en.php

@@ -47,4 +47,12 @@ return [
 	'msg.no_host'     => 'Please choose a host.',
 	'msg.bad_scheme'  => 'Unknown scheme.',
 	'msg.unknown_op'  => 'Unknown operation.',
+	'tab.triggers'    => 'Trigger Alerting Switch',
+	'lbl.trig_search' => 'Search triggers by description (e.g. CPU, disk)',
+	'lbl.trig_list'   => 'Triggers (checked = alerting enabled)',
+	'lbl.trig_hint'   => 'There are {1} triggers. Type a keyword above to search (max 200 shown).',
+	'btn.search'      => 'Search',
+	'msg.no_triggers' => 'No trigger selected.',
+	'msg.triggers_saved' => 'Trigger alerting updated: {1} trigger(s).',
+	'msg.no_trig_search' => 'No triggers matched your search.',
 ];

+ 8 - 0
web-modules/WecomCenter/locale/zh_CN.php

@@ -47,4 +47,12 @@ return [
 	'msg.no_host'     => '请选择一台主机。',
 	'msg.bad_scheme'  => '未知方案。',
 	'msg.unknown_op'  => '未知操作。',
+	'tab.triggers'    => '触发器告警开关',
+	'lbl.trig_search' => '按触发器名称搜索(如 CPU、磁盘)',
+	'lbl.trig_list'   => '触发器列表(勾选 = 启用告警)',
+	'lbl.trig_hint'   => '共 {1} 个触发器。请输入关键字搜索(最多显示 200 条)。',
+	'btn.search'      => '搜索',
+	'msg.no_triggers' => '未选择任何触发器。',
+	'msg.triggers_saved' => '触发器告警已更新:{1} 个。',
+	'msg.no_trig_search' => '没有匹配的触发器。',
 ];

+ 52 - 0
web-modules/WecomCenter/views/wecom.center.view.php

@@ -209,4 +209,56 @@ $html_page->addItem(
 		->addItem($map_form)
 );
 
+/* ------------------------------------------------------------------ */
+/*  5) Per-trigger alerting switch (search + checkboxes)              */
+/* ------------------------------------------------------------------ */
+$trig_search_form = (new CForm('get',
+		(new CUrl('zabbix.php'))->setArgument('action', 'wecom.center.view')->getUrl()))
+	->addItem((new CTextBox('trig_search', $data['trig_search']))
+		->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH)
+		->setAttribute('placeholder', I18n::t('lbl.trig_search')))
+	->addItem((new CSubmitButton(I18n::t('btn.search'), 'search', '1'))->addClass(ZBX_STYLE_BTN_ALT))
+	->setId('trig-search-form');
+
+$trig_div = (new CDiv())
+	->addItem((new CTag('h3'))->addItem(I18n::t('tab.triggers')))
+	->addItem(new CDiv(new CTag('p'))->addItem(I18n::t('lbl.trig_hint', [$data['trigger_count']])))
+	->addItem($trig_search_form);
+
+if ($data['trig_search'] !== '') {
+	if ($data['triggers']) {
+		$trig_list = new CList();
+		$ids = [];
+		foreach ($data['triggers'] as $t) {
+			$ids[] = $t['triggerid'];
+			$host = !empty($t['hosts']) ? $t['hosts'][0]['host'] : '';
+			$sev_name = I18n::t('sev.'.(((int) $t['priority']) + 1));
+			$label = '['.$sev_name.'] '.$host.' — '.$t['description'];
+			$trig_list->addItem(
+				(new CCheckBox('trig_'.$t['triggerid']))
+					->setLabel($label)
+					->setChecked(((int) $t['status']) === 0)
+			);
+		}
+		$trig_list_wrap = (new CFormList())->addRow(I18n::t('lbl.trig_list'), $trig_list);
+		$trig_save_form = (new CForm())
+			->addItem((new CVar('trig_ids', implode(',', $ids)))->removeId())
+			->addItem(
+				(new CTabView())->addTab('trig', I18n::t('tab.triggers'), $trig_list_wrap)
+					->setFooter(makeFormFooter(
+						(new CSubmitButton(I18n::t('btn.save'), 'op', 'save_triggers'))->setId('trig-save'),
+						[]
+					))
+			)
+			->setAction($save_url)
+			->setId('wecom-trig-form');
+		$trig_div->addItem($trig_save_form);
+	}
+	else {
+		$trig_div->addItem(new CDiv(new CTag('p'))->addItem(I18n::t('msg.no_trig_search')));
+	}
+}
+
+$html_page->addItem($trig_div);
+
 $html_page->show();