From 9171f5f5a41d6c2b63f93a7b4b25feef0b67b0c9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Fri, 16 Aug 2024 18:43:59 +0200 Subject: [PATCH 1/8] jinja2 dependencies --- framework/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/requirements.txt b/framework/requirements.txt index 0484905ee..ea5386f39 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -39,3 +39,6 @@ paho-mqtt==2.1.0 # Requirements for background tasks APScheduler==3.10.4 + +# Requirements for reports generation +Jinja2==3.1.4 \ No newline at end of file From 7bcbb866f4ed7ac46d8a9be3a52707861d16b8e8 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Fri, 16 Aug 2024 18:45:59 +0200 Subject: [PATCH 2/8] risk report styles file --- resources/report/styles.css | 208 ++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 resources/report/styles.css diff --git a/resources/report/styles.css b/resources/report/styles.css new file mode 100644 index 000000000..05bb67878 --- /dev/null +++ b/resources/report/styles.css @@ -0,0 +1,208 @@ +* Set some global variables */ + :root { + --header-height: .75in; + --header-width: 8.5in; + --header-pos-x: 0in; + --header-pos-y: 0in; + --page-width: 8.5in; + } + + @font-face { + font-family: 'Google Sans'; + font-style: normal; + src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } + + @font-face { + font-family: 'Roboto Mono'; + font-style: normal; + src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } + + /* Define some common body formatting*/ + body { + font-family: 'Google Sans', sans-serif; + margin: 0px; + padding: 0px; + } + + /* Sets proper page size during print to pdf for weasyprint */ + @page { + size: Letter; + width: 8.5in; + height: 11in; + } + + .page { + position: relative; + margin: 0 20px; + width: 8.5in; + height: 11in; + } + + /* Define the header related css elements*/ + .header { + position: relative; + } + + h1 { + margin: 0 0 8px 0; + font-size: 20px; + font-weight: 400; + } + + h2 { + margin: 0px; + font-size: 48px; + font-weight: 700; + } + + h3 { + font-size: 24px; + margin-bottom: 10px; + margin-top: 15px; + } + + h4 { + font-size: 12px; + font-weight: 500; + color: #5F6368; + margin-bottom: 0; + margin-top: 0; + } + + /* CSS for the footer */ + .footer { + position: absolute; + height: 30px; + width: 8.5in; + bottom: 0in; + border-top: 1px solid #D3D3D3; + } + + .footer-label { + color: #3C4043; + position: absolute; + top: 5px; + font-size: 12px; + } + + @media print { + @page { + size: Letter; + width: 8.5in; + height: 11in; + } + } + + .risk-banner { + min-height: 120px; + padding: 5px 40px 0 40px; + margin-top: 30px; + } + + .risk-banner-limited { + background-color: #E4F7FB; + color: #007B83; + } + + .risk-banner-high { + background-color: #FCE8E6; + color: #C5221F; + } + + .risk-banner-title { + text-transform: uppercase; + font-weight: bold; + } + + .risk-table { + width: 100%; + margin-top: 40px; + text-align: left; + color: #3C4043; + font-size: 14px; + } + + .risk-table-head { + margin-bottom: 15px; + } + + .risk-table-head-question { + display: inline-block; + margin-left: 70px; + font-weight: bold; + } + + .risk-table-head-answer { + display: inline-block; + margin-left: 325px; + font-weight: bold; + } + + .risk-table-row { + margin-bottom: 8px; + background-color: #F8F9FA; + display: flex; + align-items: stretch; + overflow: hidden; + } + + .risk-question-no { + padding: 15px 20px; + width: 10px; + display: inline-block; + vertical-align: top; + position: relative; + } + + .risk-question { + padding: 15px 20px; + display: inline-block; + width: 350px; + vertical-align: top; + position: relative; + height: 100%; + } + + .risk-answer { + background-color: #E8F0FE; + padding: 15px 20px; + display: inline-block; + width: 340px; + position: relative; + height: 100%; + } + + ul { + margin-top: 0; + } + + .risk-label{ + position: absolute; + top: 0px; + right: 0px; + width: 52px; + height: 16px; + font-family: 'Google Sans', sans-serif; + font-size: 8px; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.64px; + text-align: center; + font-weight: bold; + border-radius: 3px; + } + + .risk-label-high{ + background-color: #FCE8E6; + color: #C5221F; + } + + .risk-label-limited{ + width: 65px; + background-color:#E4F7FB; + color: #007B83; + } \ No newline at end of file From 45eeb4b35575c3c6168780348c5f294bcb204298 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 20 Aug 2024 16:56:36 +0200 Subject: [PATCH 3/8] generating resk profile report using jinja --- framework/python/src/common/risk_profile.py | 114 +++++++++++++++++--- resources/report/risk_report.html | 75 +++++++++++++ 2 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 resources/report/risk_report.html diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index f50dffdde..45636c1f8 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -20,10 +20,14 @@ from common import logger import json import os +from jinja2 import Template +from copy import deepcopy PROFILES_PATH = 'local/risk_profiles' LOGGER = logger.get_logger('risk_profile') RESOURCES_DIR = 'resources/report' +TEMPLATE_FILE = 'risk_report.html' +TEMPLATE_STYLES = 'styles.css' # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -43,6 +47,12 @@ class RiskProfile(): def __init__(self, profile_json=None, profile_format=None): + # Jinja template + with open(os.path.join(report_resource_dir, TEMPLATE_FILE), 'r', encoding='UTF-8') as template_file: + self._template = Template(template_file.read()) + with open(os.path.join(report_resource_dir, TEMPLATE_STYLES), 'r', encoding='UTF-8') as style_file: + self._template_styles = style_file.read() + if profile_json is None or profile_format is None: return @@ -55,9 +65,11 @@ def __init__(self, profile_json=None, profile_format=None): self._device = None self._profile_format = profile_format + self._validate(profile_json, profile_format) self.update_risk(profile_format) + # Load a profile without modifying the created date # but still validate the profile def load(self, profile_json, profile_format): @@ -295,21 +307,42 @@ def to_json(self, pretty=False): def to_html(self, device): self._device = device - - return f''' - - - {self._generate_head()} - -
- {self._generate_header()} - {self._generate_risk_banner()} - {self._generate_risk_questions()} - {self._generate_footer()} -
- - - ''' + high_risk_message = '''The device has been assessed to be high + risk due to the nature of the answers provided + about the device functionality.''' + limited_risk_message = '''The device has been assessed to be limited risk + due to the nature of the answers provided about + the device functionality.''' + with open(test_run_img_file, 'rb') as f: + logo_img_b64 = base64.b64encode(f.read()).decode('utf-8') + pages = self._generate_pages() + return self._template.render( + styles=self._template_styles, + manufacturer=self._device.manufacturer, + model=self._device.model, + logo=logo_img_b64, + risk=self.risk, + high_risk_message=high_risk_message, + limited_risk_message=limited_risk_message, + pages=pages, + version=self.version, + created_at=self.created.strftime('%d.%m.%Y') + ) + + # return f''' + # + # + # {self._generate_head()} + # + #
+ # {self._generate_header()} + # {self._generate_risk_banner()} + # {self._generate_risk_questions()} + # {self._generate_footer()} + #
+ # + # + # ''' def _generate_head(self): @@ -355,6 +388,57 @@ def _generate_risk_banner(self): ''' + + + def _generate_pages(self): + max_page_height = 350 + height = 0 + pages = [] + current_page = [] + index = 1 + + for question in self.questions: + + if height > max_page_height: + pages.append(current_page) + height = 0 + current_page = [] + + page_item = deepcopy(question) + + if isinstance(page_item['answer'], str): + + if len(page_item['answer']) > 400: + height += 160 + elif len(page_item['answer']) > 300: + height += 140 + elif len(page_item['answer']) > 200: + height += 120 + elif len(page_item['answer']) > 100: + height += 70 + else: + height += 53 + + # Select multiple answers + elif isinstance(page_item['answer'], list): + text_answers = [] + + options = self._get_format_question( + question=page_item['question'], + profile_format=self._profile_format)['options'] + + options_dict = dict(enumerate(options)) + + for answer_index in page_item['answer']: + height += 40 + text_answers.append(options_dict[answer_index]['text']) + page_item['answer'] = text_answers + page_item['index'] = index + index += 1 + current_page.append(page_item) + pages.append(current_page) + + return pages def _generate_risk_questions(self): diff --git a/resources/report/risk_report.html b/resources/report/risk_report.html new file mode 100644 index 000000000..24774b2c8 --- /dev/null +++ b/resources/report/risk_report.html @@ -0,0 +1,75 @@ + + + + + + + Risk Assessment + + + + + {% for page in pages %} +
+
+

Risk assessment

+

{{ manufacturer }} {{ model }}

+ Testrun +
+ {# Risk banner #} + + {% if loop.index == 1 and risk is not none %} +
+
+

{{ risk.lower() }} Risk

+
+
+ {% if risk == "High" %} + {{ high_risk_message }} + {% else %} + {{ limited_risk_message }} + {% endif %} +
+
+ {% endif %} + + + {# Risk table #} +
+
+
Question
+
Answer
+
+ {% for question in page %} +
+
{{ question['index'] }}.
+
{{ question['question'] }}
+
+ {% if question['answer'] is string %} + {{ question['answer'] }} + {% elif question['answer'] is sequence %} +
    + {% for answer in question['answer'] %} +
  • {{ answer }}
  • + {% endfor %} +
+ {% endif %} + {% if 'risk' in question %} +
{{ question['risk'].upper()}} RISK
+ {% endif %} +
+
+ + + {% endfor %} + + +
+
+ {% endfor %} + + + \ No newline at end of file From ecc36051584bd58a64927dea4cc582a4d78b6bf5 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 20 Aug 2024 17:09:01 +0200 Subject: [PATCH 4/8] remove unused code --- framework/python/src/common/risk_profile.py | 398 +------------------- 1 file changed, 14 insertions(+), 384 deletions(-) diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index 45636c1f8..e8ca2d229 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -48,10 +48,17 @@ class RiskProfile(): def __init__(self, profile_json=None, profile_format=None): # Jinja template - with open(os.path.join(report_resource_dir, TEMPLATE_FILE), 'r', encoding='UTF-8') as template_file: - self._template = Template(template_file.read()) - with open(os.path.join(report_resource_dir, TEMPLATE_STYLES), 'r', encoding='UTF-8') as style_file: - self._template_styles = style_file.read() + with open(os.path.join(report_resource_dir, TEMPLATE_FILE), + 'r', + encoding='UTF-8' + ) as template_file: + self._template = Template(template_file.read()) + with open(os.path.join(report_resource_dir, + TEMPLATE_STYLES), + 'r', + encoding='UTF-8' + ) as style_file: + self._template_styles = style_file.read() if profile_json is None or profile_format is None: return @@ -315,7 +322,7 @@ def to_html(self, device): the device functionality.''' with open(test_run_img_file, 'rb') as f: logo_img_b64 = base64.b64encode(f.read()).decode('utf-8') - pages = self._generate_pages() + pages = self._generate_report_pages() return self._template.render( styles=self._template_styles, manufacturer=self._device.manufacturer, @@ -329,68 +336,7 @@ def to_html(self, device): created_at=self.created.strftime('%d.%m.%Y') ) - # return f''' - # - # - # {self._generate_head()} - # - #
- # {self._generate_header()} - # {self._generate_risk_banner()} - # {self._generate_risk_questions()} - # {self._generate_footer()} - #
- # - # - # ''' - - def _generate_head(self): - - return f''' - - - - Risk Assessment - - - ''' - - def _generate_header(self): - with open(test_run_img_file, 'rb') as f: - tr_img_b64 = base64.b64encode(f.read()).decode('utf-8') - header = f''' -
-

Risk assessment

-

- {self._device.manufacturer} - {self._device.model} -

''' - header += f'''Testrun -
- ''' - return header - - def _generate_risk_banner(self): - return f''' -
-
-

{'high' if self.risk == 'High' else 'limited'} Risk

-
-
- { - 'The device has been assessed to be high risk due to the nature of the answers provided about the device functionality.' - if self.risk == 'High' else - 'The device has been assessed to be limited risk due to the nature of the answers provided about the device functionality.' - } -
-
- ''' - - - def _generate_pages(self): + def _generate_report_pages(self): max_page_height = 350 height = 0 pages = [] @@ -417,7 +363,7 @@ def _generate_pages(self): elif len(page_item['answer']) > 100: height += 70 else: - height += 53 + height += 53 # Select multiple answers elif isinstance(page_item['answer'], list): @@ -440,322 +386,6 @@ def _generate_pages(self): return pages - def _generate_risk_questions(self): - - max_page_height = 350 - content = '' - - content += self._generate_table_head() - - index = 1 - height = 0 - - for question in self.questions: - - if height > max_page_height: - content += self._generate_new_page() - height = 0 - - content += f''' -
-
{index}.
-
{question['question']}
-
''' - - # String answers (one line) - if isinstance(question['answer'], str): - content += question['answer'] - - if len(question['answer']) > 400: - height += 160 - elif len(question['answer']) > 300: - height += 140 - elif len(question['answer']) > 200: - height += 120 - elif len(question['answer']) > 100: - height += 70 - else: - height += 53 - - # Select multiple answers - elif isinstance(question['answer'], list): - content += '
    ' - - options = self._get_format_question( - question=question['question'], - profile_format=self._profile_format)['options'] - - for answer_index in question['answer']: - height += 40 - content += f''' -
  • - {self._get_option_from_index(options, answer_index)['text']}
  • ''' - - content += '
' - - # Question risk label - if 'risk' in question: - if question['risk'] == 'High': - content += '
HIGH RISK
' - elif question['risk'] == 'Limited': - content += '''
- LIMITED RISK
''' - - content += '''
''' - - index += 1 - - return content - - def _generate_table_head(self): - return ''' -
-
-
Question
-
Answer
-
''' - - def _generate_new_page(self): - - # End the current table - content = ''' -
''' - - # End the page - content += self._generate_footer() - content += '' - - # Start a new page - content += ''' -
- ''' - - content += self._generate_header() - - content += self._generate_table_head() - - return content - - def _generate_footer(self): - footer = f''' - - ''' - return footer - - def _generate_css(self): - return ''' - /* Set some global variables */ - :root { - --header-height: .75in; - --header-width: 8.5in; - --header-pos-x: 0in; - --header-pos-y: 0in; - --page-width: 8.5in; - } - - @font-face { - font-family: 'Google Sans'; - font-style: normal; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - @font-face { - font-family: 'Roboto Mono'; - font-style: normal; - src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - /* Define some common body formatting*/ - body { - font-family: 'Google Sans', sans-serif; - margin: 0px; - padding: 0px; - } - - /* Sets proper page size during print to pdf for weasyprint */ - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - - .page { - position: relative; - margin: 0 20px; - width: 8.5in; - height: 11in; - } - - /* Define the header related css elements*/ - .header { - position: relative; - } - - h1 { - margin: 0 0 8px 0; - font-size: 20px; - font-weight: 400; - } - - h2 { - margin: 0px; - font-size: 48px; - font-weight: 700; - } - - h3 { - font-size: 24px; - margin-bottom: 10px; - margin-top: 15px; - } - - h4 { - font-size: 12px; - font-weight: 500; - color: #5F6368; - margin-bottom: 0; - margin-top: 0; - } - - /* CSS for the footer */ - .footer { - position: absolute; - height: 30px; - width: 8.5in; - bottom: 0in; - border-top: 1px solid #D3D3D3; - } - - .footer-label { - color: #3C4043; - position: absolute; - top: 5px; - font-size: 12px; - } - - @media print { - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - } - - .risk-banner { - min-height: 120px; - padding: 5px 40px 0 40px; - margin-top: 30px; - } - - .risk-banner-limited { - background-color: #E4F7FB; - color: #007B83; - } - - .risk-banner-high { - background-color: #FCE8E6; - color: #C5221F; - } - - .risk-banner-title { - text-transform: uppercase; - font-weight: bold; - } - - .risk-table { - width: 100%; - margin-top: 40px; - text-align: left; - color: #3C4043; - font-size: 14px; - } - - .risk-table-head { - margin-bottom: 15px; - } - - .risk-table-head-question { - display: inline-block; - margin-left: 70px; - font-weight: bold; - } - - .risk-table-head-answer { - display: inline-block; - margin-left: 325px; - font-weight: bold; - } - - .risk-table-row { - margin-bottom: 8px; - background-color: #F8F9FA; - display: flex; - align-items: stretch; - overflow: hidden; - } - - .risk-question-no { - padding: 15px 20px; - width: 10px; - display: inline-block; - vertical-align: top; - position: relative; - } - - .risk-question { - padding: 15px 20px; - display: inline-block; - width: 350px; - vertical-align: top; - position: relative; - height: 100%; - } - - .risk-answer { - background-color: #E8F0FE; - padding: 15px 20px; - display: inline-block; - width: 340px; - position: relative; - height: 100%; - } - - ul { - margin-top: 0; - } - - .risk-label{ - position: absolute; - top: 0px; - right: 0px; - width: 52px; - height: 16px; - font-family: 'Google Sans', sans-serif; - font-size: 8px; - font-weight: 500; - line-height: 16px; - letter-spacing: 0.64px; - text-align: center; - font-weight: bold; - border-radius: 3px; - } - - .risk-label-high{ - background-color: #FCE8E6; - color: #C5221F; - } - - .risk-label-limited{ - width: 65px; - background-color:#E4F7FB; - color: #007B83; - } - ''' - def to_pdf(self, device): # Resolve the data as html first From 57a94b426dfb921eb2ac9621448dd50b7699c051 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Wed, 21 Aug 2024 11:40:50 +0200 Subject: [PATCH 5/8] rename templates --- framework/python/src/common/risk_profile.py | 4 ++-- resources/report/{styles.css => risk_report_styles.css} | 0 .../report/{risk_report.html => risk_report_template.html} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename resources/report/{styles.css => risk_report_styles.css} (100%) rename resources/report/{risk_report.html => risk_report_template.html} (100%) diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index e8ca2d229..557ddef3c 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -26,8 +26,8 @@ PROFILES_PATH = 'local/risk_profiles' LOGGER = logger.get_logger('risk_profile') RESOURCES_DIR = 'resources/report' -TEMPLATE_FILE = 'risk_report.html' -TEMPLATE_STYLES = 'styles.css' +TEMPLATE_FILE = 'risk_report_template.html' +TEMPLATE_STYLES = 'risk_report_styles.css' # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) diff --git a/resources/report/styles.css b/resources/report/risk_report_styles.css similarity index 100% rename from resources/report/styles.css rename to resources/report/risk_report_styles.css diff --git a/resources/report/risk_report.html b/resources/report/risk_report_template.html similarity index 100% rename from resources/report/risk_report.html rename to resources/report/risk_report_template.html From ec94ee23af6ec8fe8b873b381111ef9d244be0d3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Fri, 23 Aug 2024 14:54:31 +0200 Subject: [PATCH 6/8] jinja2 test report --- framework/python/src/common/testreport.py | 960 ++------------------- resources/report/risk_report_template.html | 2 +- resources/report/test_report_styles.css | 450 ++++++++++ resources/report/test_report_template.html | 186 ++++ 4 files changed, 729 insertions(+), 869 deletions(-) create mode 100644 resources/report/test_report_styles.css create mode 100644 resources/report/test_report_template.html diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 88a25a2b1..60acb05f9 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -20,11 +20,15 @@ import base64 import os from test_orc.test_case import TestCase +from jinja2 import Template +from collections import OrderedDict DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S' RESOURCES_DIR = 'resources/report' TESTS_FIRST_PAGE = 11 TESTS_PER_PAGE = 20 +TEST_REPORT_STYLES = 'test_report_styles.css' +TEST_REPORT_TEMPLATE = 'test_report_template.html' # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -172,35 +176,62 @@ def to_pdf(self): return pdf_bytes def to_html(self): - json_data = self.to_json() - return f''' - - - {self.generate_head()} - - {self.generate_body(json_data)} - - - ''' - - def generate_test_sections(self, json_data): - results = json_data['tests']['results'] - sections = '' - for result in results: - sections += self.generate_test_section(result) - return sections - - def generate_test_section(self, result): - section_content = '
\n' - for key, value in result.items(): - if value is not None: # Check if the value is not None - # Replace underscores and capitalize - formatted_key = key.replace('_', ' ').title() - section_content += f'

{formatted_key}: {value}

\n' - section_content += '
\n
\n' - return section_content - - def generate_pages(self, json_data): + + # Jinja template + with open(os.path.join(report_resource_dir, TEST_REPORT_TEMPLATE), + 'r', + encoding='UTF-8' + ) as template_file: + template = Template(template_file.read()) + with open(os.path.join(report_resource_dir, + TEST_REPORT_STYLES), + 'r', + encoding='UTF-8' + ) as style_file: + styles = style_file.read() + with open(test_run_img_file, 'rb') as f: + logo = base64.b64encode(f.read()).decode('utf-8') + json_data=self.to_json() + + # Convert the timestamp strings to datetime objects + start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') + end_time = datetime.strptime(json_data['finished'], '%Y-%m-%d %H:%M:%S') + # Calculate the duration + duration = end_time - start_time + + successful_tests = 0 + for test in json_data['tests']['results']: + if test['result'] != 'Error': + successful_tests += 1 + + steps_to_resolve = self._get_steps_to_resolve(json_data) + + module_reports = self._get_module_pages() + pages_num = self._pages_num(json_data) + total_pages = pages_num + len(module_reports) + if len(steps_to_resolve) > 0: + total_pages += 1 + + return template.render(styles=styles, + logo=logo, + version=self._version, + json_data=json_data, + device=json_data['device'], + modules=self._device_modules(json_data['device']), + test_status=json_data['status'], + duration=str(duration), + successful_tests=successful_tests, + total_tests=self._total_tests, + test_results=json_data['tests']['results'], + steps_to_resolve=steps_to_resolve, + module_reports=module_reports, + pages_num=pages_num, + total_pages=total_pages, + tests_first_page=TESTS_FIRST_PAGE, + tests_per_page=TESTS_PER_PAGE, + ) + + def _pages_num(self, json_data): # Calculate pages test_count = len(json_data['tests']['results']) @@ -208,136 +239,52 @@ def generate_pages(self, json_data): # Multiple pages required if test_count > TESTS_FIRST_PAGE: # First page - full_page = 1 + pages = 1 - # Remaining tests + # Remaining testsgenerate test_count -= TESTS_FIRST_PAGE - full_page += (int)(test_count / TESTS_PER_PAGE) - partial_page = 1 if test_count % TESTS_PER_PAGE > 0 else 0 + pages += (int)(test_count / TESTS_PER_PAGE) + pages = pages + 1 if test_count % TESTS_PER_PAGE > 0 else pages # 1 page required - elif test_count == TESTS_FIRST_PAGE: - full_page = 1 - partial_page = 0 - # Less than 1 page required else: - full_page = 0 - partial_page = 1 - - num_pages = full_page + partial_page + pages = 1 - pages = '' - for _ in range(num_pages): - self._cur_page += 1 - pages += self.generate_results_page(json_data=json_data, - page_num=self._cur_page) return pages - def generate_results_page(self, json_data, page_num): - page = '
' - page += self.generate_header(json_data, (page_num == 1)) - if page_num == 1: - page += self.generate_summary(json_data) - page += self.generate_results(json_data, page_num) - page += self.generate_footer(page_num) - page += '
' - page += '
' - return page - - def generate_module_page(self, json_data, module_report): - self._cur_page += 1 - page = '
' - page += self.generate_header(json_data, False) - page += f''' -
- {module_report} -
''' - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - return page - - def generate_steps_to_resolve(self, json_data): - - steps_so_far = 0 + def _device_modules(self, device): + sorted_modules = {} + + if 'test_modules' in device: + + for test_module in device['test_modules']: + if 'enabled' in device['test_modules'][test_module]: + sorted_modules[ + util.get_module_display_name(test_module)] = device['test_modules'][ + test_module]['enabled'] + + # Sort the modules by enabled first + sorted_modules = OrderedDict(sorted(sorted_modules.items(), + key=lambda x:x[1], + reverse=True) + ) + return sorted_modules + + def _get_steps_to_resolve(self, json_data): tests_with_recommendations = [] - index = 1 # Collect all tests with recommendations for test in json_data['tests']['results']: if 'recommendations' in test: tests_with_recommendations.append(test) - # Check if test has recommendations - if len(tests_with_recommendations) == 0: - return '' - - # Start new page - self._cur_page += 1 - page = '
' - page += self.generate_header(json_data, False) - - # Add title - page += '

Steps to Resolve

' - - for test in tests_with_recommendations: - - # Generate new page - if steps_so_far == 4 and ( - len(tests_with_recommendations) - (index-1) > 0): - - # Reset steps counter - steps_so_far = 0 - - # Render footer - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - - # Render new header - self._cur_page += 1 - page += '
' - page += self.generate_header(json_data, False) - - # Render test recommendations - page += f''' -
-
- {index}. -
- Name
{test["name"]} -
-
- Description
{test["description"]} -
-
-
- Steps to resolve - ''' - - step_number = 1 - for recommendation in test['recommendations']: - page += f''' -
{ - step_number}. {recommendation}''' - step_number += 1 - - page += '
' - - index += 1 - steps_so_far += 1 - - # Render final footer - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - - return page - - def generate_module_pages(self, json_data): - pages = '' + return tests_with_recommendations + + def _get_module_pages(self): content_max_size = 913 + reports = [] + for module_reports in self._module_reports: # ToDo: Figure out how to make this dynamic # Padding values from CSS @@ -391,8 +338,7 @@ def generate_module_pages(self, json_data): # If in the middle of a table, close the table if data_rows_active: page_content += '' - page = self.generate_module_page(json_data, page_content) - pages += page + '\n' + reports.append(page_content) content_size = 0 # If in the middle of a data table, restart # it for the rest of the rows @@ -400,727 +346,5 @@ def generate_module_pages(self, json_data): if data_rows_active else '') page_content += line + '\n' if len(page_content) > 0: - page = self.generate_module_page(json_data, page_content) - pages += page + '\n' - return pages - - def generate_body(self, json_data): - self._num_pages = 0 - self._cur_page = 0 - body = f''' - - {self.generate_pages(json_data)} - {self.generate_steps_to_resolve(json_data)} - {self.generate_module_pages(json_data)} - - ''' - # Set the max pages after all pages have been generated - return body.replace('MAX_PAGE', str(self._cur_page)) - - def generate_footer(self, page_num): - footer = f''' - - ''' - return footer - - def generate_results(self, json_data, page_num): - - successful_tests = 0 - for test in json_data['tests']['results']: - if test['result'] != 'Error': - successful_tests += 1 - - result_list = f''' -
-

Results List ({successful_tests}/{self._total_tests})

-
-
Name
-
Description
-
Result
-
''' - if page_num == 1: - start = 0 - elif page_num == 2: - start = TESTS_FIRST_PAGE - else: - start = (page_num - 2) * TESTS_PER_PAGE + TESTS_FIRST_PAGE - results_on_page = TESTS_FIRST_PAGE if page_num == 1 else TESTS_PER_PAGE - result_end = min(start + results_on_page, - len(json_data['tests']['results'])) - for ix in range(result_end - start): - result = json_data['tests']['results'][ix + start] - result_list += self.generate_result(result) - result_list += '
' - return result_list - - def generate_result(self, result): - if result['result'] == 'Non-Compliant': - result_class = 'result-test-result-non-compliant' - elif result['result'] == 'Compliant': - result_class = 'result-test-result-compliant' - elif result['result'] == 'Error': - result_class = 'result-test-result-error' - elif result['result'] == 'Feature Not Detected': - result_class = 'result-test-result-feature-not-detected' - elif result['result'] == 'Informational': - result_class = 'result-test-result-informational' - else: - result_class = 'result-test-result-skipped' - - result_html = f''' -
-
{result['name']}
-
{result['description']}
-
{result['result']}
-
- ''' - return result_html - - def generate_header(self, json_data, first_page): - with open(test_run_img_file, 'rb') as f: - tr_img_b64 = base64.b64encode(f.read()).decode('utf-8') - header = '' - - if first_page: - header += f''' -
-

Testrun report

-

- {json_data["device"]["manufacturer"]} - {json_data["device"]["model"]} -

''' - else: - header += f''' -
-

Testrun report

-

- {json_data["device"]["manufacturer"]} - {json_data["device"]["model"]} -

''' - header += f'''Testrun -
- ''' - return header - - def generate_summary(self, json_data): - # Generate the basic content section layout - summary = ''' -
- ''' - # Add the device information - manufacturer = (json_data['device']['manufacturer'] - if 'manufacturer' in json_data['device'] else 'Undefined') - model = (json_data['device']['model'] - if 'model' in json_data['device'] else 'Undefined') - fw = (json_data['device']['firmware'] - if 'firmware' in json_data['device'] else 'Undefined') - mac = (json_data['device']['mac_addr'] - if 'mac_addr' in json_data['device'] else 'Undefined') - - summary += '''
-
''' - - summary += self.generate_device_summary_label('Manufacturer', manufacturer) - summary += self.generate_device_summary_label('Model', model) - summary += self.generate_device_summary_label('Firmware', fw) - summary += self.generate_device_summary_label('MAC Address', - mac, - trailing_space=False) - - summary += '
' - - # Add device configuration - summary += ''' -
-
-

Device Configuration

-
- ''' - - if 'test_modules' in json_data['device']: - - sorted_modules = {} - - for test_module in json_data['device']['test_modules']: - if 'enabled' in json_data['device']['test_modules'][test_module]: - sorted_modules[test_module] = json_data['device']['test_modules'][ - test_module]['enabled'] - - # Sort the modules by enabled first - sorted_modules = sorted(sorted_modules.items(), - key=lambda x:x[1], - reverse=True) - - for module in sorted_modules: - summary += self.generate_device_module_label( - module[0], - module[1] - ) - - summary += '
' - - # Add device configuration - summary += ''' -
-
-

Device Configuration

-
- ''' - - if 'test_modules' in json_data['device']: - - sorted_modules = {} - - for test_module in json_data['device']['test_modules']: - if 'enabled' in json_data['device']['test_modules'][test_module]: - sorted_modules[test_module] = json_data['device']['test_modules'][ - test_module]['enabled'] - - # Sort the modules by enabled first - sorted_modules = sorted(sorted_modules.items(), - key=lambda x:x[1], - reverse=True) - - for module in sorted_modules: - summary += self.generate_device_module_label( - module[0], - module[1] - ) - - summary += '
' - - # Add the result summary - summary += self.generate_result_summary(json_data) - - summary += '\n
' - return summary - - def generate_device_module_label(self, module, enabled): - - # Do not render deleted modules - if module == 'nmap': - return '' - - label = '
' - if enabled: - label += '' - else: - label += '' - label += util.get_module_display_name(module) - label += '
' - return label - - def generate_result_summary(self, json_data): - if json_data['status'] == 'Compliant': - result_summary = '''
''' - else: - result_summary = '''
''' - result_summary += self.generate_result_summary_item('Test status', - 'Complete') - result_summary += self.generate_result_summary_item( - 'Test result', - json_data['status'], - style='color: white; font-size:24px; font-weight: 700;') - result_summary += self.generate_result_summary_item('Started', - json_data['started']) - - # Convert the timestamp strings to datetime objects - start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') - end_time = datetime.strptime(json_data['finished'], '%Y-%m-%d %H:%M:%S') - # Calculate the duration - duration = end_time - start_time - result_summary += self.generate_result_summary_item('Duration', - str(duration)) - - result_summary += '\n
' - return result_summary - - def generate_result_summary_item(self, key, value, style=None): - summary_item = f'''
{key}
''' - if style is not None: - summary_item += f'''
{value}
''' - else: - summary_item += f'''
{value}
''' - return summary_item - - def generate_device_summary_label(self, key, value, trailing_space=True): - label = f''' -

{key}

-
{value}
- ''' - if trailing_space: - label += '''
''' - return label - - def generate_head(self): - return f''' - - - - Testrun Report - - - ''' - - def generate_css(self): - return ''' - /* Set some global variables */ - :root { - --header-height: .75in; - --header-width: 8.5in; - --header-pos-x: 0in; - --header-pos-y: 0in; - --page-width: 8.5in; - --summary-height: 2.8in; - --vertical-line-height: calc(var(--summary-height)-.2in); - --vertical-line-pos-x: 25%; - } - - @font-face { - font-family: 'Google Sans'; - font-style: normal; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - @font-face { - font-family: 'Roboto Mono'; - font-style: normal; - src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - /* Define some common body formatting*/ - body { - font-family: 'Google Sans', sans-serif; - margin: 0px; - padding: 0px; - } - - /* Use this for various section breaks*/ - .gradient-line { - position: relative; - background-image: linear-gradient(to right, red, blue, green, yellow, orange); - height: 1px; - /* Adjust the height as needed */ - width: 100%; - /* To span the entire width */ - display: block; - /* Ensures it's a block-level element */ - } - - /* Sets proper page size during print to pdf for weasyprint */ - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - - .page { - position: relative; - margin: 0 20px; - width: 8.5in; - height: 11in; - } - - /* Define the header related css elements*/ - .header { - position: relative; - } - - h1 { - margin: 0 0 8px 0; - font-size: 20px; - font-weight: 400; - } - - h2 { - margin: 0px; - font-size: 48px; - font-weight: 700; - } - - h3 { - font-size: 24px; - } - - h4 { - font-size: 12px; - font-weight: 500; - color: #5F6368; - margin-bottom: 0; - margin-top: 0; - } - - .module-summary { - background-color: #F8F9FA; - width: 100%; - margin-bottom: 25px; - margin-top: 25px; - } - - .module-summary thead tr th { - text-align: left; - padding-top: 15px; - padding-left: 15px; - font-weight: 500; - color: #5F6368; - font-size: 14px; - } - - .module-summary tbody tr td { - padding-bottom: 15px; - padding-left: 15px; - font-size: 24px; - } - - .module-data { - border: 1px solid #DADCE0; - border-radius: 3px; - border-spacing: 0; - } - - .module-data thead tr th { - text-align: left; - padding: 12px 25px; - color: #3C4043; - font-size: 14px; - font-weight: 700; - } - - .module-data tbody tr td { - text-align: left; - padding: 12px 25px; - color: #3C4043; - font-size: 14px; - font-weight: 400; - border-top: 1px solid #DADCE0; - font-family: 'Roboto Mono', monospace; - } - - div.steps-to-resolve { - background-color: #F8F9FA; - margin-bottom: 30px; - width: 756px; - padding: 20px 30px; - vertical-align: top; - } - - .steps-to-resolve-row { - vertical-align: top; - } - - .steps-to-resolve-test-name { - display: inline-block; - margin-left: 70px; - margin-bottom: 20px; - width: 250px; - vertical-align: top; - } - - .steps-to-resolve-description { - display: inline-block; - } - - .steps-to-resolve.subtitle { - text-align: left; - padding-top: 15px; - font-weight: 500; - color: #5F6368; - font-size: 14px; - } - - .steps-to-resolve-index { - font-size: 40px; - position: absolute; - } - - .callout-container.info { - background-color: #e8f0fe; - } - - .callout-container.info .icon { - width: 22px; - height: 22px; - margin-right: 5px; - background-size: contain; - background-image: url('data:image/png;base64,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'); - } - - .callout-container { - display: flex; - box-sizing: border-box; - height: auto; - min-height: 48px; - padding: 6px 24px; - border-radius: 8px; - align-items: center; - gap: 10px; - color: #3c4043; - font-size: 14px; - } - - .device-information { - padding-top: 0.2in; - padding-left: 0.2in; - background-color: #F8F9FA; - width: 250px; - height: 100.4%; - } - - /* Define the summary related css elements*/ - .summary-content { - position: relative; - width: var(--page-width); - height: var(--summary-height); - margin-top: 19px; - margin-bottom: 19px; - background-color: #E8EAED; - padding-bottom: 20px; - } - - .summary-item-label { - position: relative; - } - - .summary-item-value { - position: relative; - font-size: 20px; - font-weight: 400; - color: #202124; - } - - .summary-item-space { - position: relative; - padding-bottom: 15px; - margin: 0; - } - - .summary-device-modules { - position: absolute; - left: 3.2in; - top: .3in; - } - - .summary-device-module-label { - font-size: 16px; - font-weight: 500; - color: #202124; - width: fit-content; - margin-bottom: 0.1in; - } - - .summary-vertical-line { - width: 1px; - height: var(--vertical-line-height); - background-color: #80868B; - position: absolute; - top: .3in; - bottom: .1in; - left: 3in; - } - - /* CSS for the color box */ - .summary-color-box { - position: absolute; - right: 0in; - top: 0in; - width: 2.6in; - height: 100%; - } - - .summary-box-compliant { - background-color: rgb(24, 128, 56); - } - - .summary-box-non-compliant { - background-color: #b31412; - } - - .summary-box-label { - font-size: 14px; - margin-top: 5px; - color: #DADCE0; - position: relative; - top: 10px; - left: 20px; - font-weight: 500; - } - - .summary-box-value { - font-size: 18px; - margin: 0 0 10px 0; - color: #ffffff; - position: relative; - top: 10px; - left: 20px; - } - - .result-list-title { - font-size: 24px; - } - - .result-list { - position: relative; - margin-top: .2in; - font-size: 18px; - } - - .result-line { - border: 1px solid #D3D3D3; - /* Light Gray border*/ - height: .4in; - width: 8.5in; - } - - .result-line-result { - border-top: 0px; - } - - .result-list-header-label { - font-weight: 500; - position: absolute; - font-size: 12px; - font-weight: bold; - height: 40px; - display: flex; - align-items: center; - } - - .result-test-label { - position: absolute; - font-size: 12px; - margin-top: 12px; - max-width: 300px; - font-weight: normal; - align-items: center; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - - .result-test-description { - max-width: 380px; - } - - .result-test-result-error { - background-color: #FCE8E6; - color: #C5221F; - left: 7.3in; - } - - .result-test-result-feature-not-detected { - background-color: #e3e3e3; - left: 6.85in; - } - - .result-test-result-informational { - background-color: #d9f0ff; - color: #0b5c8d; - left: 7.08in; - } - - .result-test-result-non-compliant { - background-color: #FCE8E6; - color: #C5221F; - left: 7.01in; - } - - .result-test-result { - position: absolute; - font-size: 12px; - width: fit-content; - height: 12px; - margin-top: 8px; - padding: 4px 4px 7px 5px; - border-radius: 2px; - } - - .result-test-result-compliant { - background-color: #E6F4EA; - color: #137333; - left: 7.16in; - } - - .result-test-result-skipped { - background-color: #e3e3e3; - color: #393939; - left: 7.24in; - } - - /* CSS for the footer */ - .footer { - position: absolute; - height: 30px; - width: 8.5in; - bottom: 0in; - border-top: 1px solid #D3D3D3; - } - - .footer-label { - color: #3C4043; - position: absolute; - top: 5px; - font-size: 12px; - } - - /*CSS for the markdown tables */ - .markdown-table { - border-collapse: collapse; - margin-left: 20px; - background-color: #F8F9FA; - } - - .markdown-table th, .markdown-table td { - border: none; - text-align: left; - padding: 8px; - } - - .markdown-header-h1 { - margin-top:20px; - margin-bottom:20px; - margin-right:0px; - font-size: 2em; - } - - .markdown-header-h2 { - margin-top:20px; - margin-bottom:20px; - margin-right:0px; - font-size: 1.5em; - } - - .module-page-content { - /*Page height minus header(93px), footer(30px), - and a 20px bottom padding.*/ - height: calc(11in - 93px - 30px - 20px); - - /* In case we mess something up in our calculations - we'll cut off the content of the page so - the header, footer and line break work - as expected - */ - overflow: hidden; - } - - .module-page-content h1 { - font-size: 32px; - } - - @media print { - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - }''' + reports.append(page_content) + return reports diff --git a/resources/report/risk_report_template.html b/resources/report/risk_report_template.html index 24774b2c8..685070031 100644 --- a/resources/report/risk_report_template.html +++ b/resources/report/risk_report_template.html @@ -19,7 +19,7 @@

{{ manufacturer }} {{ model }}

{# Risk banner #} - {% if loop.index == 1 and risk is not none %} + {% if loop.first and risk is not none %}

{{ risk.lower() }} Risk

diff --git a/resources/report/test_report_styles.css b/resources/report/test_report_styles.css new file mode 100644 index 000000000..eb9e26ed6 --- /dev/null +++ b/resources/report/test_report_styles.css @@ -0,0 +1,450 @@ +/* Set some global variables */ + :root { + --header-height: .75in; + --header-width: 8.5in; + --header-pos-x: 0in; + --header-pos-y: 0in; + --page-width: 8.5in; + --summary-height: 2.8in; + --vertical-line-height: calc(var(--summary-height)-.2in); + --vertical-line-pos-x: 25%; + } + + @font-face { + font-family: 'Google Sans'; + font-style: normal; + src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } + + @font-face { + font-family: 'Roboto Mono'; + font-style: normal; + src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } + + /* Define some common body formatting*/ + body { + font-family: 'Google Sans', sans-serif; + margin: 0px; + padding: 0px; + } + + /* Use this for various section breaks*/ + .gradient-line { + position: relative; + background-image: linear-gradient(to right, red, blue, green, yellow, orange); + height: 1px; + /* Adjust the height as needed */ + width: 100%; + /* To span the entire width */ + display: block; + /* Ensures it's a block-level element */ + } + + /* Sets proper page size during print to pdf for weasyprint */ + @page { + size: Letter; + width: 8.5in; + height: 11in; + } + + .page { + position: relative; + margin: 0 20px; + width: 8.5in; + height: 11in; + } + + /* Define the header related css elements*/ + .header { + position: relative; + } + + h1 { + margin: 0 0 8px 0; + font-size: 20px; + font-weight: 400; + } + + h2 { + margin: 0px; + font-size: 48px; + font-weight: 700; + } + + h3 { + font-size: 24px; + } + + h4 { + font-size: 12px; + font-weight: 500; + color: #5F6368; + margin-bottom: 0; + margin-top: 0; + } + + .module-summary { + background-color: #F8F9FA; + width: 100%; + margin-bottom: 25px; + margin-top: 25px; + } + + .module-summary thead tr th { + text-align: left; + padding-top: 15px; + padding-left: 15px; + font-weight: 500; + color: #5F6368; + font-size: 14px; + } + + .module-summary tbody tr td { + padding-bottom: 15px; + padding-left: 15px; + font-size: 24px; + } + + .module-data { + border: 1px solid #DADCE0; + border-radius: 3px; + border-spacing: 0; + } + + .module-data thead tr th { + text-align: left; + padding: 12px 25px; + color: #3C4043; + font-size: 14px; + font-weight: 700; + } + + .module-data tbody tr td { + text-align: left; + padding: 12px 25px; + color: #3C4043; + font-size: 14px; + font-weight: 400; + border-top: 1px solid #DADCE0; + font-family: 'Roboto Mono', monospace; + } + + div.steps-to-resolve { + background-color: #F8F9FA; + margin-bottom: 30px; + width: 756px; + padding: 20px 30px; + vertical-align: top; + } + + .steps-to-resolve-row { + vertical-align: top; + } + + .steps-to-resolve-test-name { + display: inline-block; + margin-left: 70px; + margin-bottom: 20px; + width: 250px; + vertical-align: top; + } + + .steps-to-resolve-description { + display: inline-block; + } + + .steps-to-resolve.subtitle { + text-align: left; + padding-top: 15px; + font-weight: 500; + color: #5F6368; + font-size: 14px; + } + + .steps-to-resolve-index { + font-size: 40px; + position: absolute; + } + + .callout-container.info { + background-color: #e8f0fe; + } + + .callout-container.info .icon { + width: 22px; + height: 22px; + margin-right: 5px; + background-size: contain; + background-image: url('data:image/png;base64,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'); + } + + .callout-container { + display: flex; + box-sizing: border-box; + height: auto; + min-height: 48px; + padding: 6px 24px; + border-radius: 8px; + align-items: center; + gap: 10px; + color: #3c4043; + font-size: 14px; + } + + .device-information { + padding-top: 0.2in; + padding-left: 0.2in; + background-color: #F8F9FA; + width: 250px; + height: 100.4%; + } + + /* Define the summary related css elements*/ + .summary-content { + position: relative; + width: var(--page-width); + height: var(--summary-height); + margin-top: 19px; + margin-bottom: 19px; + background-color: #E8EAED; + padding-bottom: 20px; + } + + .summary-item-label { + position: relative; + } + + .summary-item-value { + position: relative; + font-size: 20px; + font-weight: 400; + color: #202124; + } + + .summary-item-space { + position: relative; + padding-bottom: 15px; + margin: 0; + } + + .summary-device-modules { + position: absolute; + left: 3.2in; + top: .3in; + } + + .summary-device-module-label { + font-size: 16px; + font-weight: 500; + color: #202124; + width: fit-content; + margin-bottom: 0.1in; + } + + .summary-vertical-line { + width: 1px; + height: var(--vertical-line-height); + background-color: #80868B; + position: absolute; + top: .3in; + bottom: .1in; + left: 3in; + } + + /* CSS for the color box */ + .summary-color-box { + position: absolute; + right: 0in; + top: 0in; + width: 2.6in; + height: 100%; + } + + .summary-box-compliant { + background-color: rgb(24, 128, 56); + } + + .summary-box-non-compliant { + background-color: #b31412; + } + + .summary-box-label { + font-size: 14px; + margin-top: 5px; + color: #DADCE0; + position: relative; + top: 10px; + left: 20px; + font-weight: 500; + } + + .summary-box-value { + font-size: 18px; + margin: 0 0 10px 0; + color: #ffffff; + position: relative; + top: 10px; + left: 20px; + } + + .result-list-title { + font-size: 24px; + } + + .result-list { + position: relative; + margin-top: .2in; + font-size: 18px; + } + + .result-line { + border: 1px solid #D3D3D3; + /* Light Gray border*/ + height: .4in; + width: 8.5in; + } + + .result-line-result { + border-top: 0px; + } + + .result-list-header-label { + font-weight: 500; + position: absolute; + font-size: 12px; + font-weight: bold; + height: 40px; + display: flex; + align-items: center; + } + + .result-test-label { + position: absolute; + font-size: 12px; + margin-top: 12px; + max-width: 300px; + font-weight: normal; + align-items: center; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .result-test-description { + max-width: 380px; + } + + .result-test-result-error { + background-color: #FCE8E6; + color: #C5221F; + left: 7.3in; + } + + .result-test-result-feature-not-detected { + background-color: #e3e3e3; + left: 6.85in; + } + + .result-test-result-informational { + background-color: #d9f0ff; + color: #0b5c8d; + left: 7.08in; + } + + .result-test-result-non-compliant { + background-color: #FCE8E6; + color: #C5221F; + left: 7.01in; + } + + .result-test-result { + position: absolute; + font-size: 12px; + width: fit-content; + height: 12px; + margin-top: 8px; + padding: 4px 4px 7px 5px; + border-radius: 2px; + } + + .result-test-result-compliant { + background-color: #E6F4EA; + color: #137333; + left: 7.16in; + } + + .result-test-result-skipped { + background-color: #e3e3e3; + color: #393939; + left: 7.24in; + } + + /* CSS for the footer */ + .footer { + position: absolute; + height: 30px; + width: 8.5in; + bottom: 0in; + border-top: 1px solid #D3D3D3; + } + + .footer-label { + color: #3C4043; + position: absolute; + top: 5px; + font-size: 12px; + } + + /*CSS for the markdown tables */ + .markdown-table { + border-collapse: collapse; + margin-left: 20px; + background-color: #F8F9FA; + } + + .markdown-table th, .markdown-table td { + border: none; + text-align: left; + padding: 8px; + } + + .markdown-header-h1 { + margin-top:20px; + margin-bottom:20px; + margin-right:0px; + font-size: 2em; + } + + .markdown-header-h2 { + margin-top:20px; + margin-bottom:20px; + margin-right:0px; + font-size: 1.5em; + } + + .module-page-content { + /*Page height minus header(93px), footer(30px), + and a 20px bottom padding.*/ + height: calc(11in - 93px - 30px - 20px); + /* In case we mess something up in our calculations + we'll cut off the content of the page so + the header, footer and line break work + as expected + */ + overflow: hidden; + } + + .module-page-content h1 { + font-size: 32px; + } + + @media print { + @page { + size: Letter; + width: 8.5in; + height: 11in; + } + } \ No newline at end of file diff --git a/resources/report/test_report_template.html b/resources/report/test_report_template.html new file mode 100644 index 000000000..04aa927d5 --- /dev/null +++ b/resources/report/test_report_template.html @@ -0,0 +1,186 @@ + + + + + + + + Testrun Report + + + + + {% set page_index = namespace(value=0) %} + {# Test Results #} + {% for page in range(pages_num) %} + {% set page_index.value = page_index.value+1 %} +
+ {% if loop.first %} +
+

Testrun report

+

+ {{ device['manufacturer'] }} + {{ device['model']}} +

+ {% else %} +
+

Testrun report

+

+ {{ device['manufacturer'] }} + {{ device['model']}} +

+ {% endif %} + Testrun +
+ {% if loop.first %} +
+
+
+

Manufacturer

+
{{ device['manufacturer']}}
+
+

Model

+
{{ device['model'] }}
+
+

Firmware

+
{{ device['firmware']}}
+
+

MAC Address

+
{{ device['mac_addr'] }}
+
+
+
+
+

Device Configuration

+
+ {% for module, enabled in modules.items() %} +
+ {% if enabled %} + + {% else %} + + {% endif %} + {{ module }} +
+ {% endfor %} +
+ {% if test_status == 'Compliant' %} +
+ {% else %} +
+ {% endif %} +
Test status
+
Complete
+
Test result
+
{{ test_status }}
+
Started
+
{{ json_data['started']}}
+
Duration
+
{{ duration }}
+
+
+ {% endif %} + {% if loop.first %} + {% set results_from = 0 %} + {% set results_to = [tests_first_page, test_results|length]|min %} + {% else %} + {% set results_from = tests_first_page + (loop.index0 - 1) * tests_per_page %} + {% set results_to = [results_from + tests_per_page, test_results|length]|min %} + {% endif %} +
+

Results List ({{ successful_tests }}/{{ total_tests }})

+
+
Name
+
Description
+
Result
+
+ {% for i in range(results_from, results_to) %} +
+
{{ test_results[i]['name'] }}
+
{{ test_results[i]['description'] }}
+ {% if test_results[i]['result'] == 'Non-Compliant' %} +
+ {% elif test_results[i]['result'] == 'Compliant' %} +
+ {% elif test_results[i]['result'] == 'Error' %} +
+ {% elif test_results[i]['result'] == 'Feature Not Detected' %} +
+ {% elif test_results[i]['result'] == 'Informational' %} +
+ {% else %} +
+ {% endif %} + {{ test_results[i]['result'] }}
+
+ {% endfor %} +
+ +
+
+ {% endfor %} + {# Steps to resolve #} + {% if steps_to_resolve|length > 0 %} + {% set page_index.value = page_index.value+1 %} +
+
+

Testrun report

+

+ {{ device['manufacturer'] }} + {{ device['model']}} +

+ Testrun +
+

Steps to Resolve

+ {% for step in steps_to_resolve %} +
+
+ {{ loop.index }}. +
+ Name
{{ step['name'] }} +
+
+ Description
{{ step["description"] }} +
+
+
+ Steps to resolve + {% for recommedtation in step['recommendations'] %} +
{{ loop.index }}. {{ recommedtation }} + {% endfor %} +
+
+ {% endfor %} + +
+ {% endif %} + {# Modules reports #} + {% for module in module_reports %} + {% set page_index.value = page_index.value+1 %} +
+
+

Testrun report

+

+ {{ device['manufacturer'] }} + {{ device['model']}} +

+ Testrun +
+
+ {{ module }} +
+ +
+
+ {% endfor %} + + From a139168ac61a2bd80f8d51de9bc372da3679bb95 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Wed, 4 Sep 2024 12:39:34 +0300 Subject: [PATCH 7/8] change Device configuration margin --- resources/report/test_report_styles.css | 2 +- resources/report/test_report_template.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/report/test_report_styles.css b/resources/report/test_report_styles.css index eb9e26ed6..4422c36bf 100644 --- a/resources/report/test_report_styles.css +++ b/resources/report/test_report_styles.css @@ -233,7 +233,7 @@ .summary-device-modules { position: absolute; left: 3.2in; - top: .3in; + top: .2in; } .summary-device-module-label { diff --git a/resources/report/test_report_template.html b/resources/report/test_report_template.html index 04aa927d5..fedbd1aee 100644 --- a/resources/report/test_report_template.html +++ b/resources/report/test_report_template.html @@ -69,9 +69,9 @@

Device Configuration

{% else %}
{% endif %} -
Test status
+
Test Status
Complete
-
Test result
+
Test Result
{{ test_status }}
Started
{{ json_data['started']}}
From a56713db6c3613e00629fea380d6a32c3998d64e Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Wed, 4 Sep 2024 13:12:13 +0300 Subject: [PATCH 8/8] pylint --- framework/python/src/common/testreport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 18dc1ed53..5931a2f5c 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -17,7 +17,7 @@ from weasyprint import HTML from io import BytesIO from common import util -from common.statuses import TestResult, TestrunStatus +from common.statuses import TestrunStatus import base64 import os from test_orc.test_case import TestCase