@@ -15,7 +15,7 @@ This article explains how to convert a Nexus Network Fabric Cable Validation Rep
1515
1616## Prerequisites
1717
18- - Requires Modules: ` json pandas as pd `
18+ - Requires Modules: ` json pandas as pd datetime `
1919
2020## Python Script for Cable Validation JSON to html conversion
2121
@@ -24,32 +24,48 @@ import json
2424import pandas as pd
2525from datetime import datetime
2626
27+ def color_status(val):
28+ """
29+ Takes a scalar and returns a string with
30+ the css property `'color: green'` for compliant,
31+ `'color: red'` for noncompliant and black for others
32+ """
33+ if val == 'Compliant':
34+ color = 'green'
35+ elif val == 'NonCompliant':
36+ color = 'red'
37+ else:
38+ color = 'black'
39+ return 'color: %s' % color
40+
2741now = datetime.now() # current date and time
2842date_time = now.strftime("%m-%d-%Y-%H-%M")
2943print("date and time:",date_time)
3044
31-
3245# Get the file name as input from the user
3346file_name = input("Please provide post validation json file: ")
3447# Load the JSON data from the file
3548with open(file_name, 'r') as f:
3649 data = json.load(f)
3750
38- # Prepare a list to store the data
39- data_list = []
51+ # Prepare two lists to store the data
52+ cable_validation_data = []
53+ cable_specification_validation_data = []
4054
4155# Loop through each rack in the racks list
4256for rack in data['racks']:
43- # Loop through each device in the networkDevices list
57+ # Loop through each device in the networkConfiguration list
4458 for device in rack['rackInfo']['networkConfiguration']['networkDevices']:
4559 # Loop through each interface map for the device
4660 for interface_map in device['fixedInterfaceMaps']:
4761 # Loop through each validation result for the interface map
4862 for validation_result in interface_map['validationResult']:
49- # Append the data to the list
63+ # Append the data to the list based on validation type
5064 temp_item = [device['name'], interface_map['name'], validation_result['status'], interface_map['destinationHostname'], interface_map['destinationPort'],validation_result['validationDetails']['deviceConfiguration'], validation_result['validationDetails']['error'] , validation_result['validationDetails']['reason'],'FixedInterface']
51- # print(temp_item)
52- data_list.append(temp_item)
65+ if validation_result['validationType'] == 'CableValidation':
66+ cable_validation_data.append(temp_item)
67+ elif validation_result['validationType'] == 'CableSpecificationValidation':
68+ cable_specification_validation_data.append(temp_item)
5369
5470 # Check if scaleSpecificInterfaceMaps is not None
5571 if device['scaleSpecificInterfaceMaps'] is not None:
@@ -61,98 +77,71 @@ for rack in data['racks']:
6177 for validation_result in interfacemaps['validationResult']:
6278 # Append the data to the list
6379 temp_item = [device['name'], interfacemaps['name'], validation_result['status'], interfacemaps['destinationHostname'], interfacemaps['destinationPort'], validation_result['validationDetails']['deviceConfiguration'], validation_result['validationDetails']['error'] , validation_result['validationDetails']['reason'], 'ScaleSpecificInterface']
64- # print(temp_item)
65- data_list.append(temp_item)
66-
67- # # Check if scaleSpecificInterfaceMaps is not None
68- # if device['topologySpecificInterfaceMaps'] is not None:
69- # # Loop through each scaleSpecificInterface_Map for the interface map
70- # for topo_map in device['topologySpecificInterfaceMaps']:
71- # # Loop through each interface map for the device.
72- # for interfacemaps in topo_map['InterfaceMaps']:
73- # # Loop through each validation result for the scaleSpecificInterface_Map
74- # for validation_result in interfacemaps['validationResult']:
75- # # Append the data to the list
76- # data_list.append([device['name'], interfacemaps['name'], validation_result['status'], interface_map['destinationHostname'], interface_map['destinationPort'], validation_result['validationDetails']['deviceConfiguration'], validation_result['validationDetails']['error'] , validation_result['validationDetails']['reason'], 'TopologySpecificInterface'])
77-
78- # Convert the list to a DataFrame
79- df = pd.DataFrame(data_list, columns=['Device Name', 'Interface Map Name', 'Validation Result Status', 'Destination Hostname ', 'Destination Port ','Device Configuration', 'Error', 'Reason','Map Type'])
80-
81- # Function to apply color based on validation result
82- def color_status(val):
83- color = 'red' if val == 'NonCompliant' else 'green' if val == 'Compliant' else 'black'
84- return 'color: %s' % color
85-
86- # Apply the color to the DataFrame
87- styled_df = df.style.applymap(color_status, subset=['Validation Result Status'])
88-
89- # Set CSS properties for th elements in dataframe
90- th_props = [
91- ('font-size', '18px'),
92- ('text-align', 'center'),
93- ('font-weight', 'bold'),
94- ('color', '#6d6d6d'),
95- ('background-color', '#f7f7f9')
96- ]
97-
98- # Set CSS properties for td elements in dataframe
99- td_props = [
100- ('font-size', '16px')
101- ]
102-
103- # Set table styles
104- styles = [
105- dict(selector="th", props=th_props),
106- dict(selector="td", props=td_props)
107- ]
108-
109- # Filter the DataFrame based on the 'Validation Result Status' column
110- df_compliant = df[df['Validation Result Status'] == 'Compliant']
111- df_noncompliant = df[df['Validation Result Status'] == 'NonCompliant']
112- df_unknown = df[df['Validation Result Status'] == 'Unknown']
113-
114- # Apply the color to the DataFrames
115- styled_df_compliant = df_compliant.style.applymap(color_status, subset=['Validation Result Status'])
116- styled_df_noncompliant = df_noncompliant.style.applymap(color_status, subset=['Validation Result Status'])
117- styled_df_unknown = df_unknown.style.applymap(color_status, subset=['Validation Result Status'])
118-
119- # Generate the DataFrames' HTML strings
120- df_html_compliant = styled_df_compliant.set_table_styles(styles).to_html()
121- df_html_noncompliant = styled_df_noncompliant.set_table_styles(styles).to_html()
122- df_html_unknown = styled_df_unknown.set_table_styles(styles).to_html()
123-
124- # Combine the HTML strings
125- html = f"""
80+ if validation_result['validationType'] == 'CableValidation':
81+ cable_validation_data.append(temp_item)
82+ elif validation_result['validationType'] == 'CableSpecificationValidation':
83+ cable_specification_validation_data.append(temp_item)
84+
85+ # Convert the lists to DataFrames
86+ cable_validation_df = pd.DataFrame(cable_validation_data, columns=['Device Name', 'Interface Map Name', 'Status', 'Destination Hostname', 'Destination Port', 'Device Configuration', 'Error', 'Reason', 'Interface Type'])
87+ cable_specification_validation_df = pd.DataFrame(cable_specification_validation_data, columns=['Device Name', 'Interface Map Name', 'Status', 'Destination Hostname', 'Destination Port', 'Device Configuration', 'Error', 'Reason', 'Interface Type'])
88+
89+ # Group the DataFrames by 'Status' and append each group's HTML representation to a string
90+ #html_string = '<html><head><style>table {border-collapse: collapse;} th, td {border: 1px solid black; padding: 5px;}</style></head><body>'
91+ html_string = """
12692<html>
12793<head>
12894 <style>
129- body {{
130- background-color: #f0f0f5;
131- }}
132- .box {{
133- border: 1px solid black;
134- margin: 10px;
135- padding: 10px;
136- }}
95+ body {
96+ font-family: Arial, sans-serif;
97+ max-width: 960px; /* Set the maximum width of the page */
98+ margin: 0 auto; /* Center the page */
99+ }
100+ h2 {color: #2A2A2A;}
101+ table {border-collapse: collapse; width: 100%;}
102+ th, td {border: 1px solid #ddd; padding: 8px;}
103+ tr:nth-child(even) {background-color: #f2f2f2;}
104+ th {padding-top: 12px; padding-bottom: 12px; text-align: left; background-color: #4CAF50; color: white;}
137105 </style>
138106</head>
139107<body>
140- <div class="box">
141- <h2>Compliant</h2>
142- {df_html_compliant}
143- </div>
144- <div class="box">
145- <h2>NonCompliant</h2>
146- {df_html_noncompliant}
147- </div>
148- <div class="box">
149- <h2>Unknown</h2>
150- {df_html_unknown}
151- </div>
152- </body>
153- </html>
108+ """
109+ for status, group_df in cable_validation_df.groupby('Status'):
110+ styled_group_df = group_df.style.applymap(color_status, subset=['Status']).set_table_attributes('class="dataframe"')
111+ html_string += f'<h2>Cable Validation - {status}</h2>'
112+ html_string += styled_group_df.to_html()
113+
114+ for status, group_df in cable_specification_validation_df.groupby('Status'):
115+ styled_group_df = group_df.style.applymap(color_status, subset=['Status']).set_table_attributes('class="dataframe"')
116+ html_string += f'<h2>Cable Specification Validation - {status}</h2>'
117+ html_string += styled_group_df.to_html()
118+
119+ html_string += '</body></html>'
120+
121+ # Write the string to an HTML file
122+ with open('CableValidationAndSpecification-{filename}.html'.format(filename = date_time), 'w') as f:
123+ f.write(html_string)
154124```
155- # Save the HTML string to a file
156- with open("reports/report-{filename}.html".format(filename = date_time), 'w') as f:
157- f.write(html)
125+
126+ ## Usage
127+ To run the conversion tool execute the following:
128+ ```
129+ python cable-html.py
130+ ..
131+ Please provide post validation json file: <CABLE_VALIDATION_FILENAME>.json
158132```
133+
134+ The report output has filename ` CableValidationAndSpecification-<DATE>.html ` .
135+
136+ ## Cable Validation Report HTML results
137+
138+ The report will be sparated into 6 sections:
139+ - Cable Validation - Compliant
140+ - Cable Validation - NonCompliant
141+ - Cable Validation - Unknown
142+ - Cable Specification Validation - Compliant
143+ - Cable Specification Validation - NonCompliant
144+ - Cable Specification Validation - Unknown
145+
146+ Sample report:
147+ :::image type="content" source="media\cable-validation-html.png" alt-text="Screenshot that shows the sample cable validation report." lightbox="media\cable-validation-html.png":::
0 commit comments