Create a collapsable and selectable flow tree html table based on the datapath tree.
Signed-off-by: Adrian Moreno <[email protected]> --- python/ovs/ovs_ofparse/datapath.py | 247 +++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) diff --git a/python/ovs/ovs_ofparse/datapath.py b/python/ovs/ovs_ofparse/datapath.py index 19992982e..7a6e0941b 100644 --- a/python/ovs/ovs_ofparse/datapath.py +++ b/python/ovs/ovs_ofparse/datapath.py @@ -19,6 +19,7 @@ from ovs.ovs_ofparse.console import ( file_header, ) from ovs.ovs_ofparse.dp_tree import FlowTree, FlowElem +from ovs.ovs_ofparse.html import HTMLBuffer, HTMLFormatter factory = ODPFlowFactory() @@ -75,6 +76,15 @@ def logic(opts, heat_map): processor.print(heat_map) [email protected]() [email protected]_obj +def html(opts): + """Print the flows in an HTML list sorted by recirc_id""" + processor = HtmlTreeProcessor(opts, factory) + processor.process() + processor.print() + + class ConsoleTreeProcessor(FlowProcessor): def __init__(self, opts, factory): super().__init__(opts, factory) @@ -173,3 +183,240 @@ class ConsoleTree(FlowTree): self.traverse(self._append_to_tree) with print_context(self.console.console, self.opts): self.console.console.print(self.root.tree) + + +class HtmlTreeProcessor(FlowProcessor): + def __init__(self, opts, factory): + super().__init__(opts, factory) + self.data = dict() + + def start_file(self, name, filename): + self.tree = HTMLTree(name, self.opts) + + def process_flow(self, flow, name): + self.tree.add(flow) + + def process(self): + super().process(False) + + def stop_file(self, name, filename): + self.data[name] = self.tree + + def print(self): + html_obj = "" + for name, tree in self.data.items(): + html_obj += "<div>" + html_obj += "<h2>{}</h2>".format(name) + tree.build() + if self.opts.get("filter"): + tree.filter(self.opts.get("filter")) + html_obj += tree.render() + html_obj += "</div>" + print(html_obj) + + +class HTMLTree(FlowTree): + """HTMLTree is a Flowtree that prints the tree in html format + + Args: + opts(dict): Options dictionary + flows(dict[int, list[DPFlow]): Optional; initial flows + """ + + html_header = """ + <style> + .flow{ + background-color:white; + display: inline-block; + text-align: left; + font-family: monospace; + } + .active{ + border: 2px solid #0008ff; + } + input[type='checkbox'] { display: none; } + .wrap-collabsible { + margin: 1.2rem 0; + } + .lbl-toggle-main { + font-weight: bold; + font-family: monospace; + font-size: 1.5rem; + text-transform: uppercase; + text-align: center; + padding: 1rem; + #cursor: pointer; + border-radius: 7px; + transition: all 0.25s ease-out; + } + .lbl-toggle-flow { + font-family: monospace; + font-size: 1.0rem; + text-transform: uppercase; + text-align: center; + padding: 1rem; + #cursor: pointer; + border-radius: 7px; + transition: all 0.25s ease-out; + } + .lbl-toggle:hover { + color: #0008ff; + } + .lbl-toggle::before { + content: ' '; + display: inline-block; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid currentColor; + vertical-align: middle; + margin-right: .7rem; + transform: translateY(-2px); + transition: transform .2s ease-out; + } + .toggle:checked+.lbl-toggle::before { + transform: rotate(90deg) translateX(-3px); + } + .collapsible-content { + max-height: 0px; + overflow: hidden; + transition: max-height .25s ease-in-out; + } + .toggle:checked + .lbl-toggle + .collapsible-content { + max-height: 350px; + } + .toggle:checked+.lbl-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + .collapsible-content .content-inner { + background: rgba(0, 105, 255, .2); + border-bottom: 1px solid rgba(0, 105, 255, .45); + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + padding: .5rem 1rem; + } + .collapsible-content p { + margin-bottom: 0; + } + </style> + + <script> + function onFlowClick(elem) { + var flows = document.getElementsByClassName("flow"); + for (i = 0; i < flows.length; i++) { + flows[i].classList.remove('active') + } + elem.classList.add("active"); + var my_toggle = document.getElementsByClassName("flow"); + toggleAll(elem, true); + } + function locationHashChanged() { + var elem = document.getElementById(location.hash.substring(1)); + console.log(elem) + if (elem) { + if (elem.classList.contains("flow")) { + onFlowClick(elem); + } + } + } + function toggle_checkbox(elem) { + if (elem.checked == true) { + toggleAll(elem, true) + } else { + toggleAll(elem, false) + } + } + function toggleAll(elem, value) { + var subs = elem.parentElement.querySelectorAll(".toggle:not([id=" + CSS.escape(elem.id) + "])"); + console.log(subs); + console.log(value); + for (i = 0; i < subs.length; ++i) { + subs[i].checked = value; + } + } + window.onhashchange = locationHashChanged; + </script> + """ # noqa: E501 + + class HTMLTreeElem(FlowElem): + """An element within the HTML Tree, + It is composed of a flow and its subflows that can be added by calling + append() + """ + + def __init__(self, parent_name, flow=None, opts=None): + self._parent_name = parent_name + self._formatter = HTMLFormatter(opts) + self._opts = opts + super(HTMLTree.HTMLTreeElem, self).__init__(flow) + + def render(self, item=0): + """Render the HTML Element + Args: + item (int): the item id + + Returns: + (html_obj, items) tuple where html_obj is the html string and + items is the number of subitems rendered in total + """ + parent_name = self._parent_name.replace(" ", "_") + html_obj = "<div>" + if self.flow: + html_text = """ +<input id="collapsible_{name}_{item}" class="toggle" type="checkbox" onclick="toggle_checkbox(this)" checked> +<label for="collapsible_{name}_{item}" class="lbl-toggle lbl-toggle-flow">Flow {id}</label> + """ # noqa: E501 + html_obj += html_text.format( + item=item, id=self.flow.id, name=parent_name + ) + + html_text = '<div class="flow collapsible-content" id="flow_{id}" onfocus="onFlowClick(this)" onclick="onFlowClick(this)" >' # noqa: E501 + html_obj += html_text.format(id=self.flow.id) + buf = HTMLBuffer() + highlighted = None + if self._opts.get("highlight"): + result = self._opts.get("highlight").evaluate(self.flow) + if result: + highlighted = result.kv + self._formatter.format_flow(buf, self.flow, highlighted) + html_obj += buf.text + html_obj += "</div>" + if self.children: + html_obj += "<div>" + html_obj += "<ul style='list-style-type:none;'>" + for sf in self.children: + item += 1 + html_obj += "<li>" + (html_elem, items) = sf.render(item) + html_obj += html_elem + item += items + html_obj += "</li>" + html_obj += "</ul>" + html_obj += "</div>" + html_obj += "</div>" + return html_obj, item + + def __init__(self, name, opts, flows=None): + self.opts = opts + self.name = name + self.root = self.HTMLTreeElem("", flow=None, opts=self.opts) + super(HTMLTree, self).__init__(flows) + + def _new_elem(self, flow, _): + """Override _new_elem to provide HTMLTreeElems""" + return self.HTMLTreeElem(self.name, flow, self.opts) + + def render(self): + """Render the Tree in HTML + Returns: + an html string representing the element + """ + name = self.name.replace(" ", "_") + html_text = """<input id="collapsible_main-{name}" class="toggle" type="checkbox" onclick="toggle_checkbox(this)" checked> +<label for="collapsible_main-{name}" class="lbl-toggle lbl-toggle-main">Flow Table</label>""" # noqa: E501 + html_obj = self.html_header + html_text.format(name=name) + html_obj += "<div id=flow_list-{name}>".format(name=name) + (html_elem, items) = self.root.render() + html_obj += html_elem + html_obj += "</div>" + return html_obj -- 2.31.1 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
