Add rendering collect bundle files to report tool

This update causes report to automatically generate an index.html file
in the report_analysis folder.
Point browser to index.html to render the report_analysis graphically.
Both Correlated Results and Plugin Results have its sub-items.
Clicking items will show corresponding content in the content area.

Test Plan:
PASS: Verify the current report tool functions is not affected
PASS: Verify the index.html is generated in the collect bundle folder
PASS: Verify System Info can be read correctly
PASS: Verify users can add/remove items in left panel of System Info
PASS: Verify Correlated Results and Plugin Results are shown
PASS: Verify the subitems of the Results are clickable and shown
PASS: Verify the menus are expandable/collapsible
PASS: Verify vertical scrollbar is applied when overflow occurs
PASS: Verify system info for a selectable list of hosts are displayed
PASS: Verify handling of long hostnames
PASS: Verify handling of long list of hosts
PASS: Verify controller-0 is printed when loading
PASS: Verify correlated summary is printed when loading
PASS: Verify correlated summary is printed when 'Correlated Results'
      menu title is selected
PASS: Verify correlated results file contents are displayed by clicking
      on the 'Correlated Results' groups
PASS: Verify plugin results summary is printed when 'Plugin Results'
      menu title is selected
PASS: Verify selecting additional hosts adds to that list while
      deselecting them removes
PASS: Verify plugin results file contents can be displayed by clicking
      on the 'Plugin Results group.
PASS: Verify page is refreshed when System Information is clicked
PASS: Verify index.html is created for all report bundle pointer
      options ; -b , -d , -f
PASS: Verify index.html is created and included in the report analysis
      when report is run as part of collect
PASS: Verify the generated html and css content has no error in console

Story: 2010533
Task: 49191
Change-Id: I3303a9f0f138777f16be0ea34b1f633bdd394ee0
Signed-off-by: Lance Xu <lance.xu@windriver.com>
This commit is contained in:
Lance Xu 2023-12-05 11:38:42 -05:00
parent 944f847b43
commit 260d56ad9a
1 changed files with 126 additions and 16 deletions

View File

@ -6,17 +6,19 @@
# #
######################################################################## ########################################################################
# #
# This file contains the Render function # This file contains the Render function.
# The Rendering tool visualizes the collect bundle and generates index.html file # The Rendering tool visualizes the collect bundle and generates
# an index.html file
# #
######################################################################## ########################################################################
from datetime import datetime from datetime import datetime
from pathlib import Path
import os import os
def extract_section(log_contents, start_phrase): def extract_section(log_contents, start_phrase):
"""extract the correlated or plugin content of the summary """Extract the correlated or plugin content of the summary
Parameters: Parameters:
log_contents (string): content of the log log_contents (string): content of the log
@ -32,7 +34,7 @@ def extract_section(log_contents, start_phrase):
def remove_timestamp(text): def remove_timestamp(text):
"""remove timestamp of summary message """Remove timestamp of summary message
Parameters: Parameters:
text (string): the summary message text (string): the summary message
@ -51,7 +53,7 @@ def remove_timestamp(text):
def remove_emptyinfo(text): def remove_emptyinfo(text):
""" remove 'INFO' text of summary message """Remove 'INFO' text of summary message
Parameters: Parameters:
text (string): the summary message text (string): the summary message
@ -66,7 +68,7 @@ def remove_emptyinfo(text):
def process_section(section, title): def process_section(section, title):
"""return text with timestamp and INFO: removed """Return text with timestamp and INFO: removed
Parameters: Parameters:
section (string): the message of the correlated/plugins section section (string): the message of the correlated/plugins section
@ -79,7 +81,7 @@ def process_section(section, title):
def classify_node(data): def classify_node(data):
"""classify node type in system_info summary """Classify node type in system_info summary
Parameters: Parameters:
data (string): the summary of system_info data (string): the summary of system_info
@ -92,7 +94,7 @@ def classify_node(data):
def controller_sort(x): def controller_sort(x):
"""sort the controller, place the controller-0 first """Sort the controller, place the controller-0 first
Parameters: Parameters:
x (list): list of controller info x (list): list of controller info
@ -101,7 +103,7 @@ def controller_sort(x):
def html_css(): def html_css():
"""static css code of the rendering tool """Static css code of the rendering tool
iframe, textarea: the content panel showing information iframe, textarea: the content panel showing information
#show-worker: the show more worker button #show-worker: the show more worker button
@ -193,7 +195,7 @@ def html_css():
margin-top: 10px; margin-top: 10px;
}} }}
.content-item, .content-itemtwo {{ .content-item, .content-itemtwo, .content-itemthree {{
display: none; display: none;
}} }}
@ -218,6 +220,31 @@ def html_css():
color: #2F4F4F; color: #2F4F4F;
}} }}
.caret {{
cursor: pointer;
user-select: none;
color: #00857e;
font-weight: bold;
}}
.caret::before {{
content: '+';
color: #2F4F4F;
margin-right: 6px;
}}
.caret-down::before {{
color: #2F4F4F;
content: '-';
}}
.text-color {{
color: #00ada4;
}}
.nested {{ display: none; }}
.active {{ display: block; }}
</style> </style>
</head> </head>
""" """
@ -225,7 +252,7 @@ def html_css():
def html_script(): def html_script():
"""static script code """Static script code
Functions: Functions:
toggleContent: show content in System Info section toggleContent: show content in System Info section
@ -234,6 +261,7 @@ def html_script():
showContentStorage: display content of selected storage item showContentStorage: display content of selected storage item
showContentWorker: display content of selected worker item showContentWorker: display content of selected worker item
showContentTwo: display content of result section showContentTwo: display content of result section
toggleTree: show the collect bundle
""" """
html_content_script = """ html_content_script = """
<script> <script>
@ -323,6 +351,20 @@ def html_script():
}} }}
}} }}
function showContentThree(event, contentId) {{
event.preventDefault();
const contentItems = document.querySelectorAll('.content-itemthree');
contentItems.forEach(item => {{
item.style.display = 'none';
}});
const selectedContent = document.getElementById(contentId);
if (selectedContent) {{
selectedContent.style.display = 'block';
}}
}}
function toggleContent(option, menuItem) {{ function toggleContent(option, menuItem) {{
const contentDiv = document.getElementById(option); const contentDiv = document.getElementById(option);
const icon = menuItem.querySelector('.icon'); const icon = menuItem.querySelector('.icon');
@ -395,6 +437,28 @@ def html_script():
}} }}
}} }}
function toggleTree() {{
var toggler = document.getElementsByClassName('caret');
for (var i = 0; i < toggler.length; i++) {{
var nested = toggler[i].parentElement.querySelector('.nested');
var isEmpty = nested.querySelectorAll('li').length === 0;
if (!isEmpty) {{
toggler[i].addEventListener('click', function() {{
this.parentElement.querySelector('.nested').classList.toggle('active');
this.classList.toggle('caret-down');
this.parentElement.classList.toggle('text-color');
}});
}} else {{
toggler[i].style.color = '#808080';
}}
}}
}}
// Call the function when the page loads to initialize the tree behavior
toggleTree();
</script> </script>
</html> </html>
""" """
@ -402,7 +466,7 @@ def html_script():
def html_info(sys_section): def html_info(sys_section):
"""system info part generation """System info part generation
reads from plugin/system_info and show by different types reads from plugin/system_info and show by different types
order: controller, storage(if there exists), worker(if there exists) order: controller, storage(if there exists), worker(if there exists)
@ -505,7 +569,7 @@ def html_info(sys_section):
def html_result(log_contents, output_dir): def html_result(log_contents, output_dir):
"""result part generation in the menu-content style """Result part generation in the menu-content style
generates correlated results, plugin results, and the items under them generates correlated results, plugin results, and the items under them
subitems for plugins and correlated results under separate menus subitems for plugins and correlated results under separate menus
@ -575,13 +639,58 @@ def html_result(log_contents, output_dir):
html_content_two += f'<div class="content-itemtwo" id="content-item-plugin_results"><h2>Plugin Results</h2><textarea>{plugin_section}</textarea></div>' html_content_two += f'<div class="content-itemtwo" id="content-item-plugin_results"><h2>Plugin Results</h2><textarea>{plugin_section}</textarea></div>'
html_content_two += """ html_content_two += """
</div> </div>
</div> </div><br>
</body>
""" """
return html_content_two return html_content_two
def html_collect(output_dir):
os.chdir('../../')
current_directory = Path('.')
tree_html = ""
content_html = "<div class='content'>"
target_dir = current_directory.resolve()
newtree_html, newcontent_html = generate_directory_tree(current_directory, target_dir, True)
tree_html += newtree_html
content_html += newcontent_html
content_html += "</div>"
html_content_three = """<div class="container-menu"><div class="menu">
""" + tree_html + "</div>" + content_html + "</div></body>"
return html_content_three
def generate_directory_tree(directory_path, target_dir, is_top_level=False):
directory_name = directory_path.name
tree_html = ""
content_html = ""
approved_list = ['.log', '.conf', '.info', '.json', '.alarm', '.pid', '.list', '.lock', '.txt']
if not is_top_level:
tree_html = f'<li><span class="caret">{directory_name}</span><ul class="nested">'
for item in directory_path.iterdir():
try:
if item.is_dir() and item.name != "report_analysis":
nested_tree_html, nested_content_html = generate_directory_tree(item, target_dir)
tree_html += nested_tree_html
content_html += nested_content_html
elif item.is_file():
if os.path.getsize(item) == 0:
tree_html += f'<li><a style="color: #808080">{item}</a></li>'
else:
if item.name.endswith(tuple(approved_list)):
tree_html += f'<li><a href="#" class="toggle-sign" onclick="showContentThree(event, \'{item.name}\')">{item.name}</a></li>'
content_html += f'<div class="content-itemthree" id="{item.name}"><h2>{item.name}</h2><iframe src="{target_dir}/{item}"></iframe></div>'
else:
if not item.name.endswith(".tgz"):
tree_html += f'<li><a href="../{item}" target="_blank">{item}</a></li>'
except PermissionError as e:
continue
if not is_top_level:
tree_html += '</ul></li>'
return tree_html, content_html
# main # main
def main(input_dir, output_dir): def main(input_dir, output_dir):
reportlog_path = os.path.join(output_dir, 'report.log') reportlog_path = os.path.join(output_dir, 'report.log')
@ -596,8 +705,9 @@ def main(input_dir, output_dir):
html_file = os.path.abspath(os.path.join(output_dir, 'index.html')) html_file = os.path.abspath(os.path.join(output_dir, 'index.html'))
sys_section = sysinfo_contents.strip().split("\n\n") sys_section = sysinfo_contents.strip().split("\n\n")
html_content = html_css() + html_info(sys_section) + html_result(log_contents, output_dir) + html_script() html_content = html_css() + html_info(sys_section) + html_result(log_contents, output_dir)
html_content = html_content.format() html_content = html_content.format()
html_content += html_collect(output_dir) + html_script()
# write the HTML content to file # write the HTML content to file
with open(html_file, "w") as file: with open(html_file, "w") as file: