[ovs-dev] [RFC PATCH 10/10] python:ovs-ofparse: add datapath html format
Adrian Moreno
amorenoz at redhat.com
Mon Nov 22 15:01:57 UTC 2021
Create a collapsable and selectable flow tree html table based on the
datapath tree.
Signed-off-by: Adrian Moreno <amorenoz at redhat.com>
---
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)
+ at datapath.command()
+ at click.pass_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
More information about the dev
mailing list