def _chart_ospf(self, layout: dict, ns_list: List[str]) -> None: '''Chart OSPF status''' ospf_df = gui_get_df('ospf', namespace=ns_list, columns=['default']) if ospf_df.empty: return ospf_df['state'] = np.where(ospf_df.ifState == "adminDown", "adminDown", ospf_df.adjState) ospf_status = ospf_df.groupby(by=['namespace', 'state'])['hostname'] \ .count() \ .reset_index() \ .rename({'hostname': 'count'}, axis=1) ospf_chart = alt.Chart(ospf_status, title='OSPF') \ .mark_bar(tooltip=True) \ .encode(y='state', x='count:Q', row='namespace', color=alt.Color( 'state', scale=alt.Scale( domain=['full', 'fail', 'adminDown', 'passive'], range=['green', 'red', 'orange', 'peach'])) ) layout['ospf_chart'].altair_chart(ospf_chart)
def _get_failed_data(self, namespace: str, pgbar) -> None: '''Get interface/mlag/routing protocol states that are failed''' progress = 40 for i, entry in enumerate([{ 'name': 'device', 'query': 'status == "dead"' }, { 'name': 'interfaces', 'query': 'state == "down"' }, { 'name': 'mlag', 'query': 'mlagSinglePortsCnt != 0 or mlagErrorPortsCnt != 0' }, { 'name': 'ospf', 'query': 'adjState == "other"' }, { 'name': 'bgp', 'query': 'state == "NotEstd"' }]): df = gui_get_df(entry['name'], namespace=[namespace]) if not df.empty and (entry.get('query', '')): df = df.query(entry['query']).reset_index(drop=True) self._failed_dfs[entry['name']] = df pgbar.progress(progress + i * 10)
def _chart_interface(self, layout: dict, ns_list: List[str]) -> None: '''Chart the interfaces table status''' if_df = gui_get_df('interfaces', namespace=ns_list, columns=['default']) if if_df.empty: layout['if_chart'].warning('No Interface info found') return if_df['state'] = np.where((if_df.state == "down") & ( if_df.adminState == "down"), "adminDown", if_df.state) if_status = if_df.groupby(by=['namespace', 'state'])['hostname'] \ .count() \ .reset_index() \ .rename({'hostname': 'count'}, axis=1) if_chart = alt.Chart(if_status, title='Interfaces') \ .mark_bar(tooltip=True) \ .encode(y='state', x='count:Q', row='namespace', color=alt.Color( 'state', scale=alt.Scale(domain=['up', 'adminDown', 'down'], range=['green', 'orange', 'red'])) ) layout['if_chart'].altair_chart(if_chart)
def _create_sidebar(self) -> None: state = self._state devdf = gui_get_df('device', columns=['namespace', 'hostname']) if devdf.empty: st.error('Unable to retrieve any namespace info') st.stop() namespaces = [''] + sorted(devdf.namespace.unique().tolist()) if not state.namespace: nsidx = 0 else: nsidx = namespaces.index(state.namespace) namespace = st.sidebar.selectbox('Namespace', namespaces, key='search_ns', index=nsidx, on_change=self._sync_state) st.sidebar.markdown("""Displays last 5 search results. You can use search to find specific objects. You can qualify what you're searching for by qualifying the search term with the type. We support: - __addresses__: You can qualify a specific table to look for the address. The search string can start with one of the following keywords: __route, mac, arpnd__, to specify which table you want the search to be performed in . If you don't specify a table name, we assume ```network find``` to search for the network attach point for the address. For example, ```arpnd 172.16.1.101``` searches for entries with 172.16.1.101 in the IP address column of the arpnd table. Similarly, ```10.0.0.21``` searches for where in the network that IP address is attached to. - __ASN__: Start the search with the string ```asn``` followed by the ASN number. Typing ```asns``` will show you the list of unique ASNs across the specified namespaces. - __VTEP__: Start the search with the string ```vtep``` followed by the VTEP IP address. Typing ```vteps``` will show you the list of unique VTEPs across the specified namespaces. - __VNI__: Start the search with the string ```vni``` followed by the VNI number. Typing ```mtus``` will show you the list of unique MTUs across the specified namespaces. When specifying a table, you can specify multiple addresses to look for by providing the addresses as a space separated values such as ```"172.16.1.101 10.0.0.11"``` or ```mac "00:01:02:03:04:05 00:21:22:23:24:25"``` and so on. A combination of mac and IP address can also be specified with the address table. Support for more sophisticated search will be added in the next few releases. """) if namespace != state.namespace: state.namespace = namespace
def _create_sidebar(self) -> None: state = self._state devdf = gui_get_df('device', columns=['namespace']) if devdf.empty: st.error('Unable to retrieve any namespace info') st.stop() namespaces = [''] + sorted(devdf.namespace.unique().tolist()) if self._state.namespace: nsidx = namespaces.index(self._state.namespace) else: nsidx = 0 url = '&'.join([ f'{get_base_url()}?page=Help&session={get_session_id()}', 'help=yes', 'help_on=Path', ]) display_help_icon(url) with st.sidebar: with st.form(key='trace'): state.namespace = st.selectbox('Namespace', namespaces, key='path_namespace', index=nsidx) state.source = st.text_input('Source IP', key='path_source', value=state.source) state.dest = st.text_input('Dest IP', value=state.dest, key='path_dest') state.vrf = st.text_input('VRF', value=state.vrf, key='path_vrf') state.start_time = st.text_input('Start Time', value=state.start_time, key='path_start_time') state.end_time = st.text_input('End Time', value=state.end_time, key='path_end_time') _ = st.form_submit_button('Trace', on_click=self._sync_state) state.show_ifnames = st.checkbox('Show in/out interface names', value=state.show_ifnames, key='path_show_ifnames', on_change=self._sync_state) _ = st.button('Source <-> Dest', key='path_swap', on_click=self._sync_state)
def _render(self, layout: dict) -> None: state = self._state if not state.table: return sqobj = get_sqobject(state.table) try: df = gui_get_df(state.table, namespace=state.namespace.split(), start_time=state.start_time, end_time=state.end_time, view=state.view, columns=state.columns) except Exception as e: # pylint: disable=broad-except st.error(e) st.stop() if not df.empty: if 'error' in df.columns: st.error(df.iloc[0].error) st.experimental_set_query_params(**asdict(state)) st.stop() return else: st.info('No data returned by the table') st.experimental_set_query_params(**asdict(state)) st.stop() return if state.query: try: show_df = df.query(state.query).reset_index(drop=True) query_str = state.query except Exception as ex: # pylint: disable=broad-except st.error(f'Invalid query string: {ex}') st.stop() query_str = '' else: show_df = df query_str = '' if not show_df.empty: self._draw_summary_df(layout, sqobj, query_str) self._draw_assert_df(layout, sqobj) self._draw_table_df(layout, show_df)
def _chart_poller(self, layout: dict, ns_list: List[str]) -> None: '''Display poller status''' sqdf = gui_get_df('sqPoller', columns=['namespace', 'hostname', 'timestamp'], service='device', namespace=ns_list) if sqdf.empty: st.warning('No Poller information found') return hosts = sqdf.groupby(by=['namespace'])['hostname'] \ .nunique() \ .reset_index() \ .rename({'hostname': '#devices'}, axis=1) times = sqdf.groupby(by=['namespace'])['timestamp'] \ .max().reset_index() \ .rename({'timestamp': 'lastPolledTime'}, axis=1) pstats = times.merge(hosts, on=['namespace']) layout['poller'].subheader('Poller Status') layout['poller'].dataframe(pstats)
def _create_sidebar(self) -> None: '''Draw appropriate sidebar for the page''' do_refresh = st.sidebar.button('Refresh') devdf = gui_get_df('device', columns=['namespace', 'hostname']) if devdf.empty: st.error('Unable to retrieve any namespace info') st.stop() namespaces = [''] + sorted(devdf.namespace.unique().tolist()) if self._state.namespace: nsidx = namespaces.index(self._state.namespace) else: nsidx = 0 namespace = st.sidebar.selectbox('Namespace', namespaces, index=nsidx, key='status_ns', on_change=self._sync_state) if do_refresh: st.caching.clear_memo_cache() st.sidebar.markdown( '''This page provides an overview of the overall network status Select one of the following pages from the Page menu to investigate further. * __Xplore__: Look at all the data, look at summaries, run asserts, queries and more * __Path__: Trace the paths between destinations in a given namespace * __Search__: Search for addresses in various tables. Just type in any address you want to search. You can specify multiple addresses, space separated. See the search page for more help. __Caching is enabled by default for 90 secs on all pages__. You can clear the cache by hitting the refresh button on this page or selecting "Clear Cache" option from the drop down menu on the top right hand corner ''') if namespace != self._state.namespace: self._state.namespace = namespace
def _chart_bgp(self, layout: dict, ns_list: List[str]) -> None: '''Chart the BGP table status''' bgp_df = gui_get_df('bgp', namespace=ns_list, columns=['default']) if bgp_df.empty: return bgp_status = bgp_df.groupby(by=['namespace', 'state'])['hostname'] \ .count() \ .reset_index() \ .rename({'hostname': 'count'}, axis=1) bgp_chart = alt.Chart(bgp_status, title='BGP') \ .mark_bar(tooltip=True) \ .encode(y='state', x='count:Q', row='namespace', color=alt.Color( 'state', scale=alt.Scale( domain=['Established', 'NotEstd', 'dynamic'], range=['green', 'red', 'orange'])) ) layout['bgp_chart'].altair_chart(bgp_chart)
def _chart_dev(self, layout: dict, ns_list: List[str]) -> None: '''Chart the devices table status''' dev_df = gui_get_df('device', namespace=ns_list, columns=['*']) if dev_df.empty: layout['dev_chart'].warning('No device info found') return dev_status = dev_df \ .groupby(by=['namespace', 'status'])['hostname'].count() \ .reset_index() \ .rename({'hostname': 'count'}, axis=1) dev_chart = alt.Chart(dev_status, title='Devices') \ .mark_bar(tooltip=True) \ .encode(y='status', x='count:Q', row='namespace', color=alt.Color( 'status', scale=alt.Scale( domain=[ 'alive', 'dead', 'neverpoll'], range=['green', 'red', 'darkred'])) ) layout['dev_chart'].altair_chart(dev_chart)
def _render(self, layout) -> None: state = self._state search_text = st.session_state.search or state.search_text query_str, uniq_dict, columns = '', {}, [] df = DataFrame() try: query_str, uniq_dict, columns = self._build_query(search_text) except ValueError as ve: expander = layout['current'].expander(f'Search for {search_text}', expanded=True) df = DataFrame({'error': [ve]}) self._draw_aggrid_df(expander, df) if state.namespace: query_ns = [state.namespace] else: query_ns = [] if query_str: if state.table == "network": df = gui_get_df(state.table, verb='find', namespace=query_ns, view="latest", columns=columns, address=query_str.split()) else: df = gui_get_df(state.table, namespace=query_ns, query_str=query_str, view="latest", columns=columns) if not df.empty: df = df.query(query_str) \ .drop_duplicates() \ .reset_index(drop=True) expander = layout['current'].expander(f'Search for {search_text}', expanded=True) self._draw_aggrid_df(expander, df) elif uniq_dict: columns = ['namespace'] + uniq_dict['column'] df = gui_get_df(uniq_dict['table'], namespace=query_ns, view='latest', columns=columns) if not df.empty: df = df.groupby(by=columns).first().reset_index() expander = layout['current'].expander(f'Search for {search_text}', expanded=True) self._draw_aggrid_df(expander, df) elif len(state.prev_results) == 0: st.info('Enter a search string to see results, ' 'see sidebar for examples') prev_searches = [search_text] for psrch, prev_df in reversed(state.prev_results): if psrch in prev_searches: continue prev_searches.append(psrch) expander = st.expander(f'Search for {psrch}', expanded=True) self._draw_aggrid_df(expander, prev_df) is_error = (not df.empty) and all(df.columns == 'error') if ((query_str or uniq_dict) and st.session_state.search and (st.session_state.search != state.search_text) and not is_error): state.prev_results.append((search_text, df)) state.search_text = st.session_state.search
def _create_sidebar(self) -> None: state = self._state stime = state.start_time etime = state.end_time tables = filter( lambda x: x not in ['path', 'tables', 'ospfIf', 'ospfNbr', 'devconfig', 'topmem', 'topcpu', 'ifCounters', 'time'], get_tables() ) table_vals = [''] + sorted(tables) if state.table: if isinstance(state.table, list): tblidx = table_vals.index(state.table[0]) else: tblidx = table_vals.index(state.table) else: tblidx = table_vals.index('network') # Default starting table view_idx = 1 if state.view == 'all' else 0 devdf = gui_get_df('device', columns=['namespace', 'hostname']) if devdf.empty: st.error('Unable to retrieve any namespace info') st.stop() namespaces = [""] namespaces.extend(sorted(devdf.namespace.unique().tolist())) if state.namespace: nsidx = namespaces.index(state.namespace) else: nsidx = 0 with st.sidebar: with st.form('Xplore'): namespace = st.selectbox('Namespace', namespaces, key='xplore_namespace', index=nsidx) state.start_time = st.text_input('Start time', value=stime, key='xplore_stime') state.end_time = st.text_input('End time', value=etime, key='xplore_etime') table = st.selectbox( 'Select Table to View', tuple(table_vals), key='xplore_table', index=tblidx) if table != state.table: # We need to reset the specific variables state.query = '' state.assert_clicked = False state.uniq_clicked = '-' state.columns = ['default'] state.table = table view_vals = ('latest', 'all') if state.start_time and state.end_time: # Show everything thats happened if both times are given view_idx = 1 state.view = st.radio("View of Data", view_vals, index=view_idx, key='xplore_view') st.form_submit_button('Get', on_click=self._fetch_data) if namespace != state.namespace: state.namespace = namespace if state.table: tables_obj = get_sqobject('tables')(start_time=state.start_time, end_time=state.end_time, view=state.view) fields = tables_obj.describe(table=state.table) colist = sorted((filter(lambda x: x not in ['index', 'sqvers'], fields.name.tolist()))) columns = st.sidebar.multiselect('Pick columns', ['default', 'all'] + colist, key='xplore_columns', default=state.columns) col_sel_val = (('default' in columns or 'all' in columns) and len(columns) == 1) col_ok = st.sidebar.checkbox('Column Selection Done', key='xplore_col_done', value=col_sel_val) if not col_ok: columns = ['default'] else: col_ok = True columns = ['default'] if not columns: columns = ['default'] state.columns = columns state.experimental_ok = st.sidebar.checkbox( 'Enable Experimental Features', key='xplore_exp', on_change=self._sync_state) if state.table in ['interfaces', 'ospf', 'bgp', 'evpnVni']: state.assert_clicked = st.sidebar.checkbox( 'Run Assert', key='xplore_assert', on_change=self._sync_state) else: state.assert_clicked = False state.query = st.sidebar.text_input( 'Filter results with pandas query', value=state.query, key='xplore_query', on_change=self._sync_state) st.sidebar.markdown( "[query syntax help]" "(https://suzieq.readthedocs.io/en/latest/pandas-query-examples/)") if columns == ['all']: columns = ['*'] if state.table: col_expander = st.sidebar.expander('Column Names', expanded=False) with col_expander: st.subheader(f'{state.table} column names') st.table(tables_obj.describe(table=state.table) .query('name != "sqvers"') .reset_index(drop=True).astype(str).style) if not col_ok: st.experimental_set_query_params(**asdict(state)) st.stop() if ('default' in columns or 'all' in columns) and len(columns) != 1: st.error('Cannot select default/all with any other columns') st.experimental_set_query_params(**asdict(state)) st.stop() elif not columns: st.error('Columns cannot be empty') st.experimental_set_query_params(**asdict(state)) st.stop()