class EchoCmd(HumanCallableCommandWithArgs): """Echo command""" CMD = "echo" ARGS = (arguments.String("text", "Input text"), ) async def _execute(self): """Your pretty business logic is here ! :)""" await self.send_message(text=f"Your text is: '{self.text}'")
class TestDifferentBehaviorAdmin(HumanCallableCommandWithArgs): """Тестовая команда с разным поведением для админа и юзверя - вы админ""" ARGS = (arguments.String("adminArg", "админский аргумент"),) async def _execute(self): await self.send_message( subject=f"Вы админ. Ваш аргумент: {self.adminArg}", )
class ClassForTesting(HumanCallableCommandWithArgs): """Тестовая команда""" CMD = "test" ARGS = ( arguments.String("arg1", "description 1"), arguments.String("arg2", "description 2"), ) def _execute(self): ... def _validate(self): wrong_arguments = [] if self.arg1 != ARG1_VALID: wrong_arguments.append(self.arg1) if self.arg2 != ARG2_VALID: wrong_arguments.append(self.arg2) return wrong_arguments
class TestTakeUserId(HumanCallableCommandWithArgs): """Возвращает ID пользователя телеграм""" CMD = "giveMeMyId" ARGS = ( arguments.MyUser("user", "ID подписчика данного канала"), arguments.String("string", "какая-то строка"), ) async def _execute(self): await self.send_message(subject=f"Получен ID {self.user}")
class _ClassForTesting(HumanCallableCommandWithArgs): """Тестовая команда""" CMD = "test" ARGS = ( arguments.Integer("arg1", "description 1", default=1), arguments.String("arg2", "description 2", default=2), ) def _execute(self): ...
class TakeListWithArg(HumanCallableCommandWithArgs): """Получает обычный аргумент и список""" CMD = "argAndList" ARGS = ( arguments.String("string", "Какая-то строка"), arguments.ListArg("listarg", "Какой-то список"), ) async def _execute(self): await self.send_message( text=f"Получили такой аргумент: {self.string}\nи такой список: {self.listarg}" )
class TestHumanCallableCommandWithArgsChoose(HumanCallableCommandWithArgs): """Тестовая команда с выбором аргументов""" CMD = "HumanCallableChoose" ARGS = ( arguments.String( "test_arg", "Тестовый аргумент с возможностью выбора", options=["val1", "val2", "val3"] ), ) async def _execute(self): message = f"Вы выбрали test_arg = {self.test_arg}" await self.send_message(text=message)
class GetGraph(ZabbixCallableCommandWithArgs, WebInterfaceCommandMixin): """ Get a graph of the values of an item. """ CMD = 'graph' ARGS = (arguments.String("source", "Data source", example="item, graph", options=["item", "graph"], allow_options=True), arguments.Integer("item_id", "Item ID", example="Integer"), arguments.Integer("period", "Period of interest in hours", example="integer", default=1, options=[1, 2, 4, 8, 12, 24], maximum=100)) async def _execute(self): if self.source == "item": graphs_params = self.prepare_query_params(item_id=self.item_id, period=self.period) template = load_template('get_items.json') template["params"]["itemids"] = self.item_id else: # self.source уже провалидирован на нужные значения graphs_params = self.prepare_query_params(graph_id=self.item_id, period=self.period) template = load_template('get_graph.json') template["params"]["graphids"] = self.item_id # Узнаем название Item и Host element_info = await self.get_data_from_zabbix_api(template) try: element_name = element_info["result"][0]["name"] host_name = element_info["result"][0]["hosts"][0]["host"] subject = f"{host_name} - {element_name}\n" message_body = f">>repeat<< {self.reverse_command(self.__class__, self.source, self.item_id)}" except IndexError: subject = f">>WARNING<< No {self.source} for id {self.item_id}" message_body = "" graph = await self.load_graph(graphs_params, self.source) await self.send_message(subject=subject, text=message_body, images=[ graph, ])
class TestHumanCallableCommandWithArgs(HumanCallableCommandWithArgs): """Тестовая вызываемая команда с аргументами""" CMD = "HumanCallableArgs" ARGS = ( arguments.Integer("arg1", "Тестовый аргумент из чисел", example="123"), arguments.String("arg2", "Тестовый аргумент из букв", example="abc"), ) def _validate(self) -> list: wrong_commands = list() if not self.arg1.isdigit(): wrong_commands.append(self.arg1) if not self.arg2.isalpha(): wrong_commands.append(self.arg2) return wrong_commands async def _execute(self): await self.send_message( subject=">>info<< Test message with args", text=f"Arg with digits: {self.arg1}; Arg with words: {self.arg2}", )
class GetProblemTriggers(ZabbixCallableCommandWithArgs): """ Get information about triggers in a problem state """ JSON_TMPL_FILE = 'get_all_problem_triggers.json' CMD = 'badtriggers' LINES = load_template('host_info.json') MAX_PRIORITY = 5 ARGS = (arguments.String("direction", "Filter condition", default="ge", options=['≤', '=', '≥'], allowed=['≤', '=', '≥', 'le', 'eq', 'ge']), arguments.Integer("priority", "Trigger Priority Lower Bound", example="0 - 5", default="1", options=[i for i in range(MAX_PRIORITY + 1)], allow_options=True), arguments.String("hostname", "Filtering triggers by hostname", example="Line1, Line4, All..", default='All', options=[line for line in LINES] + ['All'])) # Для кнопок нужны красивые символы, # но их невозможно передать в макросах команд directions_translator = { '≤': 'le', '=': 'eq', '≥': 'ge', } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.zabbix_line_host = "" # настоящее имя хоста в Zabbix для обращений по API self.hostname = self.hostname.strip().lower( ) # Зарегистрированное в 'host_info.json' имя # (для красоты кнопочек) self.LINES = { key.strip().lower(): value for key, value in self.LINES.items() } def _set_hostname(self): if self.hostname != 'all': # Отображаемые имена хостов (с большой буквы) и действительные # подгружаем из 'host_info.json' self.zabbix_line_host = self.LINES[self.hostname]['host'] # В шаблон запроса к Zabbix-API подставляем действительное # имя хоста в Zabbix self.template['params']['host'] = self.zabbix_line_host else: # если нужно получить информацию для всех хостов - не трогаем шаблон. # В шаблоне значение - null. Т.е. без фильтрации по хостам. self.zabbix_line_host = self.hostname def _set_direction(self) -> None: # TODO: unittest """ Устанавливает служебное-строковое значение directions и то, которое показывается пользователю """ try: self.direction_str = self.directions_translator[self.direction] except KeyError: directions_translator_back = { value: key for key, value in self.directions_translator.items() } # На этом моменте должна быть пройдена валидация, # и self.direction - либо ключ, либо значение из словаря directions_translator self.direction_str, self.direction = \ self.direction, directions_translator_back[self.direction] def _set_priority(self) -> None: # TODO: unittest priority = int(self.priority) priority_switch = { 'le': lambda: list(range(priority + 1)), 'eq': lambda: [ priority, ], 'ge': lambda: list(range(priority, self.MAX_PRIORITY + 1)), } priorities = priority_switch[self.direction_str]() self.template['params']['filter']['priority'] = priorities def _set_subject(self, host) -> str: subject = f"Problem triggers for priority {self.direction} {self.priority}, " \ f"host: {host if host else 'All'}" return subject async def _execute(self): self._set_hostname() self._set_direction() self._set_priority() problem_triggers_request = await self.get_data_from_zabbix_api( self.template) subject = self._set_subject(self.zabbix_line_host) problem_triggers_message = self.format_triggers_info( problem_triggers_request) await self.send_message(subject=subject, text=problem_triggers_message) def _validate(self) -> list: wrong_arguments = list() if self.hostname.lower() == 'all': return wrong_arguments if self.hostname.lower() not in self.LINES: wrong_arguments.append(self.hostname) return wrong_arguments
class GetHostInfo(ZabbixCallableCommandWithArgs, WebInterfaceCommandMixin): """ Get info about Host """ CMD = "hostinfo" JSON_TMPL_FILE = 'host_info.json' HOSTS = { key.capitalize(): value for key, value in load_json_template( JSON_TMPL_FILE, ZabbixCallableCommand.PATH_TO_FILE).items() } ARGS = (arguments.String("host_name", "Zabbix host name", example="Line1", options=list(HOSTS.keys())), ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Отображать названия линий на кнопках надо с большой буквы, # но работать команда должна при любом регистре атрибутов self.host_name = self.host_name.strip().lower() self.template = { key.strip().lower(): value for key, value in self.template.items() } async def _execute(self): template = self.template[self.host_name] host = template["host"] message_subject = f"Host info for {host}" message_body = "" main_image = None # Есть сработавшие триггеры? Если да - добавляем кнопки triggers_template = load_template('get_all_problem_triggers.json') triggers_template["params"]["host"] = host triggers = await self.get_data_from_zabbix_api(triggers_template) triggers = triggers['result'] info_triggers = list( filter(lambda x: int(x['priority']) <= 2, triggers)) problem_triggers = list( filter(lambda x: int(x['priority']) > 2, triggers)) if problem_triggers: message_body += \ f">>fire<< <i>Problem-triggers: <b>{len(problem_triggers)}</b></i>\n" self.add_inline_button(GetProblemTriggers, ">>fire<< Bad Triggers", "ge", "3", self.host_name) else: message_body += \ f">>OK<< <i>No problems on line</i>\n" message_body += \ f">>info<< <i>Info-triggers: <b>{len(info_triggers)}</b></i>\n" if info_triggers: self.add_inline_button(GetProblemTriggers, ">>info<< Info Triggers", "le", "2", self.host_name) # Zabbix items? Если да - добавляем их в текст if template["items"]: items_template = load_json_template( 'get_items.json', ZabbixCallableCommand.PATH_TO_FILE) items_template["params"]["itemids"] = template["items"] items = await self.get_data_from_zabbix_api(items_template) items = self.format_items_info(items) message_body += "\n<b>Items overview:</b>\n" message_body += items # Изображение в сообщении? if template["main_image"]: image_type = template["main_image"]["source"] element_id = template["main_image"]["id"] if image_type == "graph": graph_params = self.prepare_query_params(graph_id=element_id) main_image = await self.load_graph(graph_params, image_type) elif image_type == "item": graph_params = self.prepare_query_params(item_id=element_id) main_image = await self.load_graph(graph_params, image_type) else: _LOGGER.warning("Wrong image type!") # Типы графиков указываются в шаблоне команды raise BadCommandTemplateException(self.TMPL_FILE) message_body += "\n<b>Graph:</b>\n" message_body += f">>lupa<< 2h:: {self.reverse_command(GetGraph, image_type, element_id, '2')}\n" message_body += f">>lupa<< 4h:: {self.reverse_command(GetGraph, image_type, element_id, '4')}\n" message_body += f"\n>>repeat<< {self.reverse_command(GetHostInfo, self.host_name)}" # Добавляем кнопки для всех графиков for graph_name, graph_id in template["graphs"].items(): self.add_inline_button(GetGraph, f">>graph1<< {graph_name}", "graph", graph_id) for item_name, item_id in template["item_graphs"].items(): self.add_inline_button(GetGraph, f">>graph2<< {item_name}", "item", item_id) await self.send_message(subject=message_subject, text=message_body, images=[ main_image, ], reply_markup=self.inline_buttons) def _validate(self) -> list: wrong_commands = list() if self.host_name.capitalize() not in self.HOSTS: wrong_commands.append(self.host_name) return wrong_commands