def get_table(object_type, schema, embedded=False, identifier=None, credentials=None): """ Build the object_type table and get data to populate the table """ datamgr = request.environ['beaker.session']['datamanager'] # Pagination and search where = {'saved_filters': True} if request.query.get('search') is not None: where = Helper.decode_search(request.query.get('search', '')) # Get total elements count # total = datamgr.get_objects_count(object_type, search=where, refresh=True) # Build table structure dt = Datatable(object_type, datamgr, schema) # Build page title title = dt.title if '%d' in title: title = title % dt.records_total return { 'object_type': object_type, 'dt': dt, 'where': where, 'title': request.query.get('title', title), 'embedded': embedded, 'identifier': identifier, 'credentials': credentials }
def get_worldmap(): """ Get the hosts list to build a worldmap """ user = request.environ['beaker.session']['current_user'] datamgr = request.environ['beaker.session']['datamanager'] target_user = request.environ['beaker.session']['target_user'] username = user.get_username() if not target_user.is_anonymous(): username = target_user.get_username() # Fetch elements per page preference for user, default is 25 elts_per_page = datamgr.get_user_preferences(username, 'elts_per_page', 25) elts_per_page = elts_per_page['value'] # Pagination and search start = int(request.query.get('start', '0')) count = int(request.query.get('count', elts_per_page)) where = Helper.decode_search(request.query.get('search', '')) search = { 'page': start // (count + 1), 'max_results': count, 'sort': '-_id', 'where': where } # Get valid hosts valid_hosts = get_valid_elements(search) # Get last total elements count total = len(valid_hosts) count = min(len(valid_hosts), total) return { 'mapId': 'hostsMap', 'params': worldmap_parameters, 'hosts': valid_hosts, 'pagination': webui.helper.get_pagination_control('/worldmap', total, start, count), 'title': request.query.get('title', _('Hosts worldmap')) }
def get_service_timeline(self, element_id, widget_id='timeline', embedded=False, identifier=None, credentials=None): # pylint: disable=unused-argument, too-many-locals """Display a service timeline widget""" user = request.environ['beaker.session']['current_user'] datamgr = request.app.datamgr # Get service service = datamgr.get_service(element_id) if not service: return self.webui.response_invalid_parameters( _('Service does not exist')) # Search the required widget widget_place = request.params.get('widget_place', 'service') widget_template = request.params.get('widget_template', 'service_widget') # Search in the application widgets (all plugins widgets) for widget in self.webui.get_widgets_for(widget_place): if widget_id.startswith(widget['id']): widget_template = widget['template'] logger.info("Widget found, template: %s", widget_template) break else: logger.info( "Widget identifier not found: using default template and no options" ) logger.debug("get_service_timeline: found template: %s", widget_template) # Fetch elements per page preference for user, default is 25 elts_per_page = datamgr.get_user_preferences(user, 'elts_per_page', 25) # service history pagination and search parameters start = int(request.params.get('start', '0')) count = int(request.params.get('count', elts_per_page)) where = {'service': service.id} # Find known history types history_plugin = self.webui.find_plugin('Histories') if history_plugin: decoded_search = Helper.decode_search( request.params.get('search', ''), history_plugin.table) logger.info("Decoded search: %s", decoded_search) if decoded_search: where.update(decoded_search) search = { 'page': (start // count) + 1, 'max_results': count, 'where': where } # Get service history history = datamgr.get_history(search=search) if history is None: history = [] total = 0 if history: total = history[0]['_total'] # Search filters used for the timeline widget events_search_filters = { '01': (_('Web UI comments'), 'type:webui.comment'), '02': (_('Check results'), 'type:check.'), '03': (_('Alerts'), 'type:monitoring.alert'), '04': (_('Acknowledges'), 'type:monitoring.ack'), '05': (_('Downtimes'), 'type:monitoring.downtime'), '06': (_('Notifications'), 'type:monitoring.notification'), '07': ('', ''), } # Render the widget return template( '_widget', { 'widget_id': widget_id, 'widget_name': widget_template, 'widget_place': 'user', 'widget_template': widget_template, 'widget_uri': request.urlparts.path, 'options': {}, 'plugin_parameters': self.plugin_parameters, 'search_engine': True, 'search_filters': events_search_filters, 'service': service, 'history': history, 'pagination': self.webui.helper.get_pagination_control( '/service/%s#service_%s' % (service.id, widget_id), total, start, count), 'title': None, 'embedded': embedded, 'identifier': identifier, 'credentials': credentials })
def show_minemap(self): # Yes, but that's how it is made, and it suits ;) # pylint: disable=too-many-locals """Get the hosts / services list to build a minemap""" user = request.environ['beaker.session']['current_user'] datamgr = request.app.datamgr # Fetch elements per page preference for user, default is 25 elts_per_page = datamgr.get_user_preferences(user, 'elts_per_page', 25) # Minemap search engine is based upon host table plugin = self.webui.find_plugin('host') # Pagination and search start = int(request.params.get('start', '0')) count = int(request.params.get('count', elts_per_page)) if count < 1: count = elts_per_page search = Helper.decode_search(request.params.get('search', ''), plugin.table) logger.info("Decoded search pattern: %s", search) for key, pattern in search.items(): logger.info("global search pattern '%s' / '%s'", key, pattern) search = { 'page': (start // count) + 1, 'max_results': count, 'where': search, 'sort': '-_overall_state_id' } # Get elements from the data manager # Do not include the embedded fields to improve the loading time... hosts = datamgr.get_hosts(search, embedded=False) minemap = [] columns = [] for host in hosts: # Each item contains the total number of records matching the search filter total = host['_total'] minemap_row = {'host_check': host} # Get all host services # Do not include the embedded fields to improve the loading time... services = datamgr.get_services( search={ 'where': {'host': host.id}, 'sort': '-_overall_state_id' }, all_elements=True, embedded=False ) for service in services: columns.append(service.name) minemap_row.update({service.name: service}) minemap.append(minemap_row) # Sort column names by most frequent ... count_columns = collections.Counter(columns) columns = [c for c, dummy in count_columns.most_common()] return { 'search_engine': self.search_engine, 'search_filters': self.search_filters, 'params': self.plugin_parameters, 'minemap': minemap, 'columns': columns, 'pagination': self.webui.helper.get_pagination_control('/minemap', total, start, count), 'title': request.query.get('title', _('Hosts minemap')) }
def get_host_timeline(self, element_id, widget_id='timeline', embedded=False, identifier=None, credentials=None): # pylint: disable=unused-argument, too-many-locals """Display an host timeline widget""" user = request.environ['beaker.session']['current_user'] datamgr = request.app.datamgr # Get the host host = datamgr.get_host(element_id) if not host: # Test if we got a name instead of an id host = datamgr.get_host(search={'max_results': 1, 'where': {'name': element_id}}) if not host: return self.webui.response_invalid_parameters(_('Host does not exist')) # Search the required widget widget_place = request.params.get('widget_place', 'host') widget_template = request.params.get('widget_template', 'host_widget') # Search in the application widgets (all plugins widgets) for widget in self.webui.get_widgets_for(widget_place): if widget_id.startswith(widget['id']): widget_template = widget['template'] logger.debug("Widget found, template: %s", widget_template) break else: logger.info("Widget identifier not found: using default template and no options") logger.debug("get_host_timeline: found template: %s", widget_template) # Fetch elements per page preference for user, default is 25 elts_per_page = datamgr.get_user_preferences(user, 'elts_per_page', 25) # Host history pagination and search parameters start = int(request.params.get('start', '0')) count = int(request.params.get('count', elts_per_page)) where = {'host': host.id} # Find known history types history_plugin = self.webui.find_plugin('Histories') if history_plugin: decoded_search = Helper.decode_search(request.params.get('search', ''), history_plugin.table) logger.info("Decoded search: %s", decoded_search) if decoded_search: where.update(decoded_search) search = { 'page': (start // count) + 1, 'max_results': count, 'where': where } # Get host history history = datamgr.get_history(search=search) if history is None: history = [] total = 0 if history: total = history[0]['_total'] # Search filters used for the timeline widget events_search_filters = { '01': (_('Web UI comments'), 'type:webui.comment'), '02': (_('Check results'), 'type:check.'), '03': (_('Alerts'), 'type:monitoring.alert'), '04': (_('Acknowledges'), 'type:monitoring.ack'), '05': (_('Downtimes'), 'type:monitoring.downtime'), '06': (_('Notifications'), 'type:monitoring.notification'), '07': ('', ''), } # Render the widget return template('_widget', { 'widget_id': widget_id, 'widget_name': widget_template, 'widget_place': 'user', 'widget_template': widget_template, 'widget_uri': request.urlparts.path, 'options': {}, 'plugin_parameters': self.plugin_parameters, 'search_engine': True, 'search_filters': events_search_filters, 'host': host, 'history': history, 'pagination': self.webui.helper.get_pagination_control( '/host/%s#host_%s' % (host.id, widget_id), total, start, count ), 'title': None, 'embedded': embedded, 'identifier': identifier, 'credentials': credentials })
def show_worldmap(self, for_my_widget=False): """Get the hosts list to build a worldmap Get the list of the valid hosts t display onthe map If `for_my_widget` is True this function returns the list of the concerned hosts else it returns the worldmap view. :param for_my_widget: defaults to False :return: """ user = request.environ['beaker.session']['current_user'] datamgr = request.app.datamgr # Fetch elements per page preference for user, default is 25 elts_per_page = datamgr.get_user_preferences(user, 'elts_per_page', 25) # Pagination and search start = int(request.query.get('start', '0')) count = int(request.query.get('count', elts_per_page)) where = Helper.decode_search(request.query.get('search', ''), self.table) search = { 'page': (start // count) + 1, 'max_results': count, 'sort': '-_id', 'where': where } if self.plugin_parameters.get('hosts_business_impacts'): # Only get hosts which business impact is configured... logger.debug("worldmap, only hosts with BI in: %s", self.plugin_parameters['hosts_business_impacts']) search['where'].update( {'business_impact': {'$in': self.plugin_parameters['hosts_business_impacts']}}) if self.plugin_parameters.get('hosts_included'): # Include some hosts... logger.debug("worldmap, included hosts: %s", self.plugin_parameters['hosts_included']) search['where'].update({'name': { "$regex": self.plugin_parameters['hosts_included']}}) if self.plugin_parameters.get('hosts_excluded'): # Exclude some hosts... logger.debug("worldmap, excluded hosts: %s", self.plugin_parameters['hosts_excluded']) search['where'].update({'name': { "$regex": "^((?!%s).)*$" % self.plugin_parameters['hosts_excluded']}}) # Do not include the embedded fields to improve the loading time... hosts = datamgr.get_hosts(search, embedded=False) # Get positioned and not-positioned hosts positioned_hosts = self.get_map_elements(hosts) # Get last total elements count total = len(hosts) if hosts: total = hosts[0]['_total'] logger.info("worldmap, total %d hosts", total) if for_my_widget: return [h for h in positioned_hosts if h['positioned']] map_style = "width: %s; height: %s;" % (self.plugin_parameters.get('map_width', "100%"), self.plugin_parameters.get('map_height', "100%")) return { 'search_engine': self.search_engine, 'search_filters': self.search_filters, 'mapId': 'hostsMap', 'mapStyle': map_style, 'params': self.plugin_parameters, 'hosts': positioned_hosts, 'pagination': self.webui.helper.get_pagination_control( '/worldmap', total, start, count), 'title': request.query.get('title', _('Hosts worldmap')) }
def show_minemap(self): # Yes, but that's how it is made, and it suits ;) # pylint: disable=too-many-locals """Get the hosts / services list to build a minemap""" user = request.environ['beaker.session']['current_user'] datamgr = request.app.datamgr # Fetch elements per page preference for user, default is 25 elts_per_page = datamgr.get_user_preferences(user, 'elts_per_page', 25) # Minemap search engine is based upon host table plugin = self.webui.find_plugin('host') # Pagination and search start = int(request.params.get('start', '0')) count = int(request.params.get('count', elts_per_page)) if count < 1: count = elts_per_page search = Helper.decode_search(request.params.get('search', ''), plugin.table) logger.info("Decoded search pattern: %s", search) for key, pattern in search.items(): logger.info("global search pattern '%s' / '%s'", key, pattern) search = { 'page': (start // count) + 1, 'max_results': count, 'where': search, 'sort': '-_overall_state_id' } # Get elements from the data manager # Do not include the embedded fields to improve the loading time... hosts = datamgr.get_hosts(search, embedded=False) minemap = [] columns = [] for host in hosts: # Each item contains the total number of records matching the search filter total = host['_total'] minemap_row = {'host_check': host} # Get all host services # Do not include the embedded fields to improve the loading time... services = datamgr.get_services(search={ 'where': { 'host': host.id }, 'sort': '-_overall_state_id' }, all_elements=True, embedded=False) for service in services: columns.append(service.name) minemap_row.update({service.name: service}) minemap.append(minemap_row) # Sort column names by most frequent ... count_columns = collections.Counter(columns) columns = [c for c, dummy in count_columns.most_common()] return { 'search_engine': self.search_engine, 'search_filters': self.search_filters, 'params': self.plugin_parameters, 'minemap': minemap, 'columns': columns, 'pagination': self.webui.helper.get_pagination_control('/minemap', total, start, count), 'title': request.query.get('title', _('Hosts minemap')) }
def table_data(self, plugin_table): # Because there are many locals needed :) # pylint: disable=too-many-locals """Return elements data in json format as of Datatables SSP protocol More info: https://datatables.net/manual/server-side Example URL:: POST /? draw=1& columns[0][data]=alias& columns[0][name]=& columns[0][searchable]=true& columns[0][orderable]=true& columns[0][search][value]=& columns[0][search][regex]=false& ... order[0][column]=0& order[0][dir]=asc& start=0& length=10& search[value]=& search[regex]=false& Request parameters are Json formatted Request Parameters: - object_type: object type managed by the datatable - links: url prefix to be used by the links in the table - embedded: true / false whether the table is embedded by an external application **Note**: those three first parameters are not datatable specific parameters :) - draw, index parameter to be returned in the response Pagination: - start / length, for pagination Searching: - search (value or regexp) search[value]: Global search value. To be applied to all columns which are searchable search[regex]: true if search[value] is a regex Sorting: - order[i][column] / order[i][dir] index of the columns to order and sort direction (asc/desc) Columns: - columns[i][data]: Column's data source, as defined by columns.data. - columns[i][name]: Column's name, as defined by columns.name. - columns[i][searchable]: Flag to indicate if this column is searchable (true). - columns[i][orderable]: Flag to indicate if this column is orderable (true). - columns[i][search][value]: Search value to apply to this specific column. - columns[i][search][regex]: Flag to indicate if the search term for this column is a regex. Response data: - draw - recordsTotal: total records, before filtering (i.e. total number of records in the database) - recordsFiltered: Total records, after filtering (i.e. total number of records after filtering has been applied - not just the number of records being returned for this page of data). - data: The data to be displayed in the table. an array of data source objects, one for each row, which will be used by DataTables. - error (optional): Error message if an error occurs Not included if there is no error. """ # Manage request parameters ... logger.info("request data for table: %s, templates: %s", request.forms.get('object_type'), self.templates) # Because of specific datatables parameters name (eg. columns[0] ...) # ... some parameters have been json.stringify on client side ! params = {} for key in list(request.params.keys()): if key in ['columns', 'order', 'search']: params[key] = json.loads(request.params.get(key)) else: params[key] = request.params.get(key) # params now contains 'valid' query parameters as we should have found them ... logger.debug("table request parameters: %s", params) parameters = {} # Manage page number ... # start is the first requested row and we must transform to a page count ... first_row = int(params.get('start', '0')) # length is the number of requested rows rows_count = int(params.get('length', '25')) parameters['page'] = (first_row // rows_count) + 1 parameters['max_results'] = rows_count logger.debug("get %d rows from row #%d -> page: %d", rows_count, first_row, parameters['page']) # Columns ordering # order:[{"column":2,"dir":"desc"}] if 'order' in params and 'columns' in params and params['order']: sorted_columns = [] for order in params['order']: idx = int(order['column']) if params['columns'][idx] and params['columns'][idx]['data']: logger.debug( "sort by column %d (%s), order: %s ", idx, params['columns'][idx]['data'], order['dir'] ) if order['dir'] == 'desc': sorted_columns.append('-' + params['columns'][idx]['data']) else: sorted_columns.append(params['columns'][idx]['data']) if sorted_columns: parameters['sort'] = ','.join(sorted_columns) logger.info("backend order request parameters: %s", parameters) # Individual column search parameter s_columns = [] if 'columns' in params and params['columns']: for column in params['columns']: if 'searchable' not in column or 'search' not in column: # pragma: no cover continue if 'value' not in column['search'] or not column['search']['value']: continue logger.debug("search column '%s' for '%s'", column['data'], column['search']['value']) for field in self.table_columns: if field['data'] != column['data']: continue # Some specific types... if field['type'] == 'boolean': s_columns.append( {column['data']: column['search']['value'] == 'true'} ) elif field['type'] == 'integer': s_columns.append( {column['data']: int(column['search']['value'])} ) elif field['format'] == 'select': values = column['search']['value'].split(',') if len(values) > 1: s_columns.append( { column['data']: { "$in": values } } ) else: s_columns.append( {column['data']: values[0]} ) # ... the other fields :) else: # Do not care about 'smart' and 'caseInsensitive' boolean parameters ... if column['search']['regex']: s_columns.append( { column['data']: { "$regex": ".*" + column['search']['value'] + ".*" } } ) else: s_columns.append( {column['data']: column['search']['value']} ) break logger.info("backend search individual columns parameters: %s", s_columns) # Global search parameter # search:{"value":"test","regex":false} s_global = {} # pylint: disable=too-many-nested-blocks # Will be too complex else ... if 'search' in params and 'columns' in params and params['search']: # params['search'] contains something like: {u'regex': False, u'value': u'name:pi1'} # global regex is always ignored ... in favor of the column declared regex logger.info("global search requested: %s ", params['search']) if 'value' in params['search'] and params['search']['value']: # There is something to search for... logger.debug("search requested, value: %s ", params['search']['value']) # New strategy: decode search patterns... search = Helper.decode_search(params['search']['value'], plugin_table) logger.info("decoded search pattern: %s", search) # Old strategy: search for the value in all the searchable columns... # if not search: # logger.info("applying datatable search pattern...") # for column in params['columns']: # if not column['searchable']: # continue # logger.debug("search global '%s' for '%s'", # column['data'], params['search']['value']) # if 'regex' in params['search']: # if params['search']['regex']: # s_global.update( # {column['data']: { # "$regex": ".*" + params['search']['value'] + ".*"}}) # else: # s_global.update({column['data']: params['search']['value']}) s_global = search logger.info("backend search global parameters: %s", s_global) # Specific hack to filter the log check results that are not dated! if self.object_type == 'logcheckresult': s_columns.append({"last_check": {"$ne": 0}}) if s_columns and s_global: parameters['where'] = {"$and": [{"$and": s_columns}, s_global]} elif s_columns: parameters['where'] = {"$and": s_columns} elif s_global: parameters['where'] = s_global # Embed linked resources / manage templated resources parameters['embedded'] = {} for field in self.embedded: parameters['embedded'].update({field: 1}) logger.debug("backend embedded parameters: %s", parameters['embedded']) # Count total elements excluding templates if necessary if self.is_templated: if self.ui_visibility: self.records_total = self.datamgr.my_backend.count( self.object_type, params={'where': {'_is_template': self.templates, 'webui_visible': True}} ) else: self.records_total = self.datamgr.my_backend.count( self.object_type, params={'where': {'_is_template': self.templates}} ) if 'where' in parameters: parameters['where'].update({'_is_template': self.templates}) else: parameters['where'] = {'_is_template': self.templates} else: if self.ui_visibility: self.records_total = self.datamgr.my_backend.count( self.object_type, params={'where': {'webui_visible': True}} ) else: self.records_total = self.datamgr.my_backend.count(self.object_type) if self.ui_visibility: if 'where' in parameters: parameters['where'].update({'webui_visible': True}) else: parameters['where'] = {'webui_visible': True} # Request objects from the backend ... logger.info("table data get parameters: %s", parameters) items = self.datamgr.my_backend.get(self.object_type, params=parameters) logger.info("table data got %d items", len(items)) if not items: logger.info("No backend elements match search criteria: %s", parameters) # Empty response return json.dumps({ # draw is the request number ... "draw": int(params.get('draw', '0')), "recordsTotal": 0, "recordsFiltered": 0, "data": [] }) # Create an object ... object_class = [kc for kc in self.datamgr.known_classes if kc.get_type() == self.object_type] if not object_class: # pragma: no cover, should never happen! logger.warning("datatable, unknown object type: %s", self.object_type) # Empty response return json.dumps({ # draw is the request number ... "draw": int(params.get('draw', '0')), "recordsTotal": 0, "recordsFiltered": 0, "data": [] }) # Update table inner properties with the object class defined properties object_class = object_class[0] self.id_property = '_id' if hasattr(object_class, 'id_property'): self.id_property = object_class.id_property self.name_property = 'name' if hasattr(object_class, 'name_property'): self.name_property = object_class.name_property self.status_property = 'status' if hasattr(object_class, 'status_property'): self.status_property = object_class.status_property logger.debug("datatable, object type: '%s' and properties: %s / %s / %s", object_class, self.id_property, self.name_property, self.status_property) # Change item content... rows = [] # Total number of filtered records self.records_filtered = self.records_total for item in items: bo_object = object_class(item) if not bo_object.ui_visible: logger.debug("Not UI visible object: %s", bo_object) continue logger.info("table data object: %s", bo_object) logger.debug("table data object: %s", bo_object) # This is an awful hack that allows to update the objects filtered for a table. # Two main interests: # - update the backend because some massive modifications are necessary for testing # - prepare a massive update feature in the Web UI :) # ----- # if self.is_templated and self.templates and self.object_type == 'service': # logger.warning("service template: %s for host: %s", # bo_object.name, bo_object['host']) # if bo_object['host'].name.startswith('fdj'): # logger.info("To be updated...") # data = { # "check_freshness": True, # "freshness_threshold": 86400, # "passive_checks_enabled": True, # "active_checks_enabled": False # } # result = self.datamgr.update_object(element=bo_object, data=data) # if result is True: # logger.info("updated.") # else: # logger.error("update failed!") # ----- # Each item contains the total number of records matching the search filter self.records_filtered = item['_total'] row = {} row['DT_RowData'] = {} row['_id'] = bo_object.id for field in self.table_columns: logger.debug(" - field: %s", field) # Specific fields if field['data'] == self.name_property: # Create a link to navigate to the item page row[self.name_property] = bo_object.html_link # Store the item name in a specific field of the row. # The value will be retrieved by the table actions (ack, recheck, ...) row['DT_RowData'].update({"object_%s" % self.object_type: bo_object.name}) continue if field['data'] == self.status_property: # Replace the text status with the specific item HTML state row[self.status_property] = bo_object.get_html_state(text=None, title=bo_object.status) # Use the item status to specify the table row class # row['DT_RowClass'] = "table-row-%s" % (bo_object.status.lower()) continue if field['data'] in ['_overall_state_id', 'overall_state', 'overall_status']: # Get the item overall state from the data manager f_get_overall_state = getattr(self.datamgr, 'get_%s_overall_state' % self.object_type) if f_get_overall_state: (dummy, overall_status) = f_get_overall_state(bo_object) # Get element state configuration row[field['data']] = ElementState().get_html_state( self.object_type, bo_object, text=None, title=bo_object.overall_state_to_title[bo_object.overall_state], use_status=overall_status ) # Use the item overall state to specify the table row class row['DT_RowClass'] = "table-row-%s" % (overall_status) else: # pragma: no cover, should never happen! logger.warning("Missing get_overall_state method for: %s", self.object_type) row[field['data']] = 'XxX' continue if "business_impact" in field['data']: # Replace the BI count with the specific item HTML formatting row[field['data']] = Helper.get_html_business_impact(bo_object.business_impact) continue # Specific fields type if field['type'] == 'datetime' or field['format'] == 'datetime': # Replace the timestamp with the formatted date row[field['data']] = bo_object.get_date(bo_object[field['data']]) continue if field['type'] == 'boolean': # Replace the boolean vaue with the specific item HTML formatting row[field['data']] = Helper.get_on_off(bo_object[field['data']]) continue if field['type'] == 'list': # Replace the list with the specific list HTML formatting if hasattr(bo_object, field['data']): row[field['data']] = Helper.get_html_item_list( bo_object.id, field['data'], getattr(bo_object, field['data']), title=field['title'] ) else: row[field['data']] = 'Unknown' continue if field['type'] == 'dict': # Replace the dictionary with the specific dict HTML formatting row[field['data']] = Helper.get_html_item_list( bo_object.id, field['data'], getattr(bo_object, field['data']), title=field['title'] ) continue if field['type'] == 'objectid': if isinstance(bo_object[field['data']], BackendElement): row[field['data']] = bo_object[field['data']].get_html_link( prefix=request.params.get('links') ) # row['DT_RowData'].update({ # "object_%s" % field['data']: bo_object[field['data']].name # }) else: logger.warning("Table field is supposed to be an object: %s, %s = %s", bo_object.name, field['data'], getattr(bo_object, field['data'])) row[field['data']] = getattr(bo_object, field['data']) if row[field['data']] == field['resource']: row[field['data']] = '...' continue # For any non-specific fields, send the field value to the table row[field['data']] = getattr(bo_object, field['data'], 'unset') logger.debug(" -> field: %s", field) logger.debug("table data row: %s", row) # logger.debug("Table row: %s", row) rows.append(row) logger.debug("filtered records: %d out of total: %d", self.records_filtered, self.records_total) # Send response return json.dumps({ "draw": int(float(params.get('draw', '0'))), "recordsTotal": self.records_total, "recordsFiltered": self.records_filtered, "data": rows })
def show_worldmap(self, for_my_widget=False): """Get the hosts list to build a worldmap Get the list of the valid hosts t display onthe map If `for_my_widget` is True this function returns the list of the concerned hosts else it returns the worldmap view. :param for_my_widget: defaults to False :return: """ user = request.environ['beaker.session']['current_user'] datamgr = request.app.datamgr # Fetch elements per page preference for user, default is 25 elts_per_page = datamgr.get_user_preferences(user, 'elts_per_page', 25) # Pagination and search start = int(request.query.get('start', '0')) count = int(request.query.get('count', elts_per_page)) where = Helper.decode_search(request.query.get('search', ''), self.table) search = { 'page': (start // count) + 1, 'max_results': count, 'sort': '-_id', 'where': where } if self.plugin_parameters.get('hosts_business_impacts'): # Only get hosts which business impact is configured... logger.debug("worldmap, only hosts with BI in: %s", self.plugin_parameters['hosts_business_impacts']) search['where'].update({ 'business_impact': { '$in': self.plugin_parameters['hosts_business_impacts'] } }) if self.plugin_parameters.get('hosts_included'): # Include some hosts... logger.debug("worldmap, included hosts: %s", self.plugin_parameters['hosts_included']) search['where'].update( {'name': { "$regex": self.plugin_parameters['hosts_included'] }}) if self.plugin_parameters.get('hosts_excluded'): # Exclude some hosts... logger.debug("worldmap, excluded hosts: %s", self.plugin_parameters['hosts_excluded']) search['where'].update({ 'name': { "$regex": "^((?!%s).)*$" % self.plugin_parameters['hosts_excluded'] } }) # Do not include the embedded fields to improve the loading time... hosts = datamgr.get_hosts(search, embedded=False) # Get positioned and not-positioned hosts positioned_hosts = self.get_map_elements(hosts) # Get last total elements count total = len(hosts) if hosts: total = hosts[0]['_total'] logger.info("worldmap, total %d hosts", total) if for_my_widget: return [h for h in positioned_hosts if h['positioned']] map_style = "width: %s; height: %s;" % (self.plugin_parameters.get( 'map_width', "100%"), self.plugin_parameters.get('map_height', "100%")) return { 'search_engine': self.search_engine, 'search_filters': self.search_filters, 'mapId': 'hostsMap', 'mapStyle': map_style, 'params': self.plugin_parameters, 'hosts': positioned_hosts, 'pagination': self.webui.helper.get_pagination_control('/worldmap', total, start, count), 'title': request.query.get('title', _('Hosts worldmap')) }
def table_data(self, plugin_table): # Because there are many locals needed :) # pylint: disable=too-many-locals """Return elements data in json format as of Datatables SSP protocol More info: https://datatables.net/manual/server-side Example URL:: POST /? draw=1& columns[0][data]=alias& columns[0][name]=& columns[0][searchable]=true& columns[0][orderable]=true& columns[0][search][value]=& columns[0][search][regex]=false& ... order[0][column]=0& order[0][dir]=asc& start=0& length=10& search[value]=& search[regex]=false& Request parameters are Json formatted Request Parameters: - object_type: object type managed by the datatable - links: url prefix to be used by the links in the table - embedded: true / false whether the table is embedded by an external application **Note**: those three first parameters are not datatable specific parameters :) - draw, index parameter to be returned in the response Pagination: - start / length, for pagination Searching: - search (value or regexp) search[value]: Global search value. To be applied to all columns which are searchable search[regex]: true if search[value] is a regex Sorting: - order[i][column] / order[i][dir] index of the columns to order and sort direction (asc/desc) Columns: - columns[i][data]: Column's data source, as defined by columns.data. - columns[i][name]: Column's name, as defined by columns.name. - columns[i][searchable]: Flag to indicate if this column is searchable (true). - columns[i][orderable]: Flag to indicate if this column is orderable (true). - columns[i][search][value]: Search value to apply to this specific column. - columns[i][search][regex]: Flag to indicate if the search term for this column is a regex. Response data: - draw - recordsTotal: total records, before filtering (i.e. total number of records in the database) - recordsFiltered: Total records, after filtering (i.e. total number of records after filtering has been applied - not just the number of records being returned for this page of data). - data: The data to be displayed in the table. an array of data source objects, one for each row, which will be used by DataTables. - error (optional): Error message if an error occurs Not included if there is no error. """ # Manage request parameters ... logger.info("request data for table: %s, templates: %s", request.forms.get('object_type'), self.templates) # Because of specific datatables parameters name (eg. columns[0] ...) # ... some parameters have been json.stringify on client side ! params = {} for key in list(request.params.keys()): if key in ['columns', 'order', 'search']: params[key] = json.loads(request.params.get(key)) else: params[key] = request.params.get(key) # params now contains 'valid' query parameters as we should have found them ... logger.debug("table request parameters: %s", params) parameters = {} # Manage page number ... # start is the first requested row and we must transform to a page count ... first_row = int(params.get('start', '0')) # length is the number of requested rows rows_count = int(params.get('length', '25')) parameters['page'] = (first_row // rows_count) + 1 parameters['max_results'] = rows_count logger.debug("get %d rows from row #%d -> page: %d", rows_count, first_row, parameters['page']) # Columns ordering # order:[{"column":2,"dir":"desc"}] if 'order' in params and 'columns' in params and params['order']: sorted_columns = [] for order in params['order']: idx = int(order['column']) if params['columns'][idx] and params['columns'][idx]['data']: logger.debug("sort by column %d (%s), order: %s ", idx, params['columns'][idx]['data'], order['dir']) if order['dir'] == 'desc': sorted_columns.append('-' + params['columns'][idx]['data']) else: sorted_columns.append(params['columns'][idx]['data']) if sorted_columns: parameters['sort'] = ','.join(sorted_columns) logger.info("backend order request parameters: %s", parameters) # Individual column search parameter s_columns = [] if 'columns' in params and params['columns']: for column in params['columns']: if 'searchable' not in column or 'search' not in column: # pragma: no cover continue if 'value' not in column[ 'search'] or not column['search']['value']: continue logger.debug("search column '%s' for '%s'", column['data'], column['search']['value']) for field in self.table_columns: if field['data'] != column['data']: continue # Some specific types... if field['type'] == 'boolean': s_columns.append({ column['data']: column['search']['value'] == 'true' }) elif field['type'] == 'integer': s_columns.append( {column['data']: int(column['search']['value'])}) elif field['format'] == 'select': values = column['search']['value'].split(',') if len(values) > 1: s_columns.append({column['data']: {"$in": values}}) else: s_columns.append({column['data']: values[0]}) # ... the other fields :) else: # Do not care about 'smart' and 'caseInsensitive' boolean parameters ... if column['search']['regex']: s_columns.append({ column['data']: { "$regex": ".*" + column['search']['value'] + ".*" } }) else: s_columns.append( {column['data']: column['search']['value']}) break logger.info("backend search individual columns parameters: %s", s_columns) # Global search parameter # search:{"value":"test","regex":false} s_global = {} # pylint: disable=too-many-nested-blocks # Will be too complex else ... if 'search' in params and 'columns' in params and params['search']: # params['search'] contains something like: {u'regex': False, u'value': u'name:pi1'} # global regex is always ignored ... in favor of the column declared regex logger.info("global search requested: %s ", params['search']) if 'value' in params['search'] and params['search']['value']: # There is something to search for... logger.debug("search requested, value: %s ", params['search']['value']) # New strategy: decode search patterns... search = Helper.decode_search(params['search']['value'], plugin_table) logger.info("decoded search pattern: %s", search) # Old strategy: search for the value in all the searchable columns... # if not search: # logger.info("applying datatable search pattern...") # for column in params['columns']: # if not column['searchable']: # continue # logger.debug("search global '%s' for '%s'", # column['data'], params['search']['value']) # if 'regex' in params['search']: # if params['search']['regex']: # s_global.update( # {column['data']: { # "$regex": ".*" + params['search']['value'] + ".*"}}) # else: # s_global.update({column['data']: params['search']['value']}) s_global = search logger.info("backend search global parameters: %s", s_global) # Specific hack to filter the log check results that are not dated! if self.object_type == 'logcheckresult': s_columns.append({"last_check": {"$ne": 0}}) if s_columns and s_global: parameters['where'] = {"$and": [{"$and": s_columns}, s_global]} elif s_columns: parameters['where'] = {"$and": s_columns} elif s_global: parameters['where'] = s_global # Embed linked resources / manage templated resources parameters['embedded'] = {} for field in self.embedded: parameters['embedded'].update({field: 1}) logger.debug("backend embedded parameters: %s", parameters['embedded']) # Count total elements excluding templates if necessary if self.is_templated: if self.ui_visibility: self.records_total = self.datamgr.my_backend.count( self.object_type, params={ 'where': { '_is_template': self.templates, 'webui_visible': True } }) else: self.records_total = self.datamgr.my_backend.count( self.object_type, params={'where': { '_is_template': self.templates }}) if 'where' in parameters: parameters['where'].update({'_is_template': self.templates}) else: parameters['where'] = {'_is_template': self.templates} else: if self.ui_visibility: self.records_total = self.datamgr.my_backend.count( self.object_type, params={'where': { 'webui_visible': True }}) else: self.records_total = self.datamgr.my_backend.count( self.object_type) if self.ui_visibility: if 'where' in parameters: parameters['where'].update({'webui_visible': True}) else: parameters['where'] = {'webui_visible': True} # Request objects from the backend ... logger.info("table data get parameters: %s", parameters) items = self.datamgr.my_backend.get(self.object_type, params=parameters) logger.info("table data got %d items", len(items)) if not items: logger.info("No backend elements match search criteria: %s", parameters) # Empty response return json.dumps({ # draw is the request number ... "draw": int(params.get('draw', '0')), "recordsTotal": 0, "recordsFiltered": 0, "data": [] }) # Create an object ... object_class = [ kc for kc in self.datamgr.known_classes if kc.get_type() == self.object_type ] if not object_class: # pragma: no cover, should never happen! logger.warning("datatable, unknown object type: %s", self.object_type) # Empty response return json.dumps({ # draw is the request number ... "draw": int(params.get('draw', '0')), "recordsTotal": 0, "recordsFiltered": 0, "data": [] }) # Update table inner properties with the object class defined properties object_class = object_class[0] self.id_property = '_id' if hasattr(object_class, 'id_property'): self.id_property = object_class.id_property self.name_property = 'name' if hasattr(object_class, 'name_property'): self.name_property = object_class.name_property self.status_property = 'status' if hasattr(object_class, 'status_property'): self.status_property = object_class.status_property logger.debug( "datatable, object type: '%s' and properties: %s / %s / %s", object_class, self.id_property, self.name_property, self.status_property) # Change item content... rows = [] # Total number of filtered records self.records_filtered = self.records_total for item in items: bo_object = object_class(item) if not bo_object.ui_visible: logger.debug("Not UI visible object: %s", bo_object) continue logger.info("table data object: %s", bo_object) logger.debug("table data object: %s", bo_object) # This is an awful hack that allows to update the objects filtered for a table. # Two main interests: # - update the backend because some massive modifications are necessary for testing # - prepare a massive update feature in the Web UI :) # ----- # if self.is_templated and self.templates and self.object_type == 'service': # logger.warning("service template: %s for host: %s", # bo_object.name, bo_object['host']) # if bo_object['host'].name.startswith('fdj'): # logger.info("To be updated...") # data = { # "check_freshness": True, # "freshness_threshold": 86400, # "passive_checks_enabled": True, # "active_checks_enabled": False # } # result = self.datamgr.update_object(element=bo_object, data=data) # if result is True: # logger.info("updated.") # else: # logger.error("update failed!") # ----- # Each item contains the total number of records matching the search filter self.records_filtered = item['_total'] row = {} row['DT_RowData'] = {} row['_id'] = bo_object.id for field in self.table_columns: logger.debug(" - field: %s", field) # Specific fields if field['data'] == self.name_property: # Create a link to navigate to the item page row[self.name_property] = bo_object.html_link # Store the item name in a specific field of the row. # The value will be retrieved by the table actions (ack, recheck, ...) row['DT_RowData'].update( {"object_%s" % self.object_type: bo_object.name}) continue if field['data'] == self.status_property: # Replace the text status with the specific item HTML state row[self.status_property] = bo_object.get_html_state( text=None, title=bo_object.status) # Use the item status to specify the table row class # row['DT_RowClass'] = "table-row-%s" % (bo_object.status.lower()) continue if field['data'] in [ '_overall_state_id', 'overall_state', 'overall_status' ]: # Get the item overall state from the data manager f_get_overall_state = getattr( self.datamgr, 'get_%s_overall_state' % self.object_type) if f_get_overall_state: (dummy, overall_status) = f_get_overall_state(bo_object) # Get element state configuration row[field['data']] = ElementState().get_html_state( self.object_type, bo_object, text=None, title=bo_object.overall_state_to_title[ bo_object.overall_state], use_status=overall_status) # Use the item overall state to specify the table row class row['DT_RowClass'] = "table-row-%s" % (overall_status) else: # pragma: no cover, should never happen! logger.warning( "Missing get_overall_state method for: %s", self.object_type) row[field['data']] = 'XxX' continue if "business_impact" in field['data']: # Replace the BI count with the specific item HTML formatting row[field['data']] = Helper.get_html_business_impact( bo_object.business_impact) continue # Specific fields type if field['type'] == 'datetime' or field['format'] == 'datetime': # Replace the timestamp with the formatted date row[field['data']] = bo_object.get_date( bo_object[field['data']]) continue if field['type'] == 'boolean': # Replace the boolean vaue with the specific item HTML formatting row[field['data']] = Helper.get_on_off( bo_object[field['data']]) continue if field['type'] == 'list': # Replace the list with the specific list HTML formatting if hasattr(bo_object, field['data']): row[field['data']] = Helper.get_html_item_list( bo_object.id, field['data'], getattr(bo_object, field['data']), title=field['title']) else: row[field['data']] = 'Unknown' continue if field['type'] == 'dict': # Replace the dictionary with the specific dict HTML formatting row[field['data']] = Helper.get_html_item_list( bo_object.id, field['data'], getattr(bo_object, field['data']), title=field['title']) continue if field['type'] == 'objectid': if isinstance(bo_object[field['data']], BackendElement): row[field['data']] = bo_object[ field['data']].get_html_link( prefix=request.params.get('links')) # row['DT_RowData'].update({ # "object_%s" % field['data']: bo_object[field['data']].name # }) else: logger.warning( "Table field is supposed to be an object: %s, %s = %s", bo_object.name, field['data'], getattr(bo_object, field['data'])) row[field['data']] = getattr(bo_object, field['data']) if row[field['data']] == field['resource']: row[field['data']] = '...' continue # For any non-specific fields, send the field value to the table row[field['data']] = getattr(bo_object, field['data'], 'unset') logger.debug(" -> field: %s", field) logger.debug("table data row: %s", row) # logger.debug("Table row: %s", row) rows.append(row) logger.debug("filtered records: %d out of total: %d", self.records_filtered, self.records_total) # Send response return json.dumps({ "draw": int(float(params.get('draw', '0'))), "recordsTotal": self.records_total, "recordsFiltered": self.records_filtered, "data": rows })