wiliot-certificate 4.4.2__py3-none-any.whl → 4.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. {brg_certificate → certificate}/ag/wlt_cmd_if.html +10 -4
  2. {brg_certificate → certificate}/ag/wlt_types_ag.py +1878 -519
  3. certificate/cert_common.py +1488 -0
  4. certificate/cert_config.py +480 -0
  5. {brg_certificate → certificate}/cert_data_sim.py +134 -46
  6. {brg_certificate → certificate}/cert_defines.py +129 -128
  7. {brg_certificate → certificate}/cert_gw_sim.py +183 -53
  8. {brg_certificate → certificate}/cert_mqtt.py +179 -64
  9. {brg_certificate → certificate}/cert_prints.py +35 -33
  10. {brg_certificate → certificate}/cert_protobuf.py +15 -6
  11. {brg_certificate → certificate}/cert_results.py +240 -64
  12. certificate/cert_utils.py +634 -0
  13. certificate/certificate.py +205 -0
  14. certificate/certificate_cli.py +76 -0
  15. certificate/certificate_eth_test_list.txt +76 -0
  16. certificate/certificate_sanity_test_list.txt +66 -0
  17. certificate/certificate_test_list.txt +76 -0
  18. {brg_certificate → certificate}/tests/calibration/interval_test/interval_test.json +3 -2
  19. {brg_certificate → certificate}/tests/calibration/interval_test/interval_test.py +7 -6
  20. certificate/tests/calibration/output_power_test/output_power_test.json +23 -0
  21. certificate/tests/calibration/output_power_test/output_power_test.py +39 -0
  22. {brg_certificate → certificate}/tests/calibration/pattern_test/pattern_test.json +2 -1
  23. {brg_certificate → certificate}/tests/calibration/pattern_test/pattern_test.py +20 -15
  24. certificate/tests/cloud_connectivity/acl_ext_adv_test/acl_ext_adv_test.json +15 -0
  25. certificate/tests/cloud_connectivity/acl_ext_adv_test/acl_ext_adv_test.py +140 -0
  26. certificate/tests/cloud_connectivity/acl_test/acl_test.json +15 -0
  27. certificate/tests/cloud_connectivity/acl_test/acl_test.py +96 -0
  28. certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.json +19 -0
  29. certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.py +41 -0
  30. certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.json +19 -0
  31. certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.py +215 -0
  32. certificate/tests/cloud_connectivity/connection_test/connection_test.json +18 -0
  33. certificate/tests/cloud_connectivity/connection_test/connection_test.py +67 -0
  34. certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.json +15 -0
  35. certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.py +80 -0
  36. certificate/tests/cloud_connectivity/downlink_test/downlink_test.json +21 -0
  37. certificate/tests/cloud_connectivity/downlink_test/downlink_test.py +201 -0
  38. certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.json +17 -0
  39. certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.py +104 -0
  40. certificate/tests/cloud_connectivity/reboot_test/reboot_test.json +18 -0
  41. certificate/tests/cloud_connectivity/reboot_test/reboot_test.py +59 -0
  42. certificate/tests/cloud_connectivity/registration_test/registration_test.json +20 -0
  43. certificate/tests/cloud_connectivity/registration_test/registration_test.py +384 -0
  44. certificate/tests/cloud_connectivity/registration_test/registration_test_cli.py +90 -0
  45. certificate/tests/cloud_connectivity/stress_test/stress_test.json +17 -0
  46. certificate/tests/cloud_connectivity/stress_test/stress_test.py +101 -0
  47. certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.json +25 -0
  48. certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.py +92 -0
  49. certificate/tests/cloud_connectivity/uplink_test/uplink_test.json +20 -0
  50. certificate/tests/cloud_connectivity/uplink_test/uplink_test.py +169 -0
  51. {brg_certificate → certificate}/tests/datapath/aging_test/aging_test.json +2 -1
  52. certificate/tests/datapath/aging_test/aging_test.py +142 -0
  53. certificate/tests/datapath/event_ble5_test/event_ble5_test.json +17 -0
  54. certificate/tests/datapath/event_ble5_test/event_ble5_test.py +89 -0
  55. certificate/tests/datapath/event_test/event_test.json +17 -0
  56. certificate/tests/datapath/event_test/event_test.py +80 -0
  57. {brg_certificate → certificate}/tests/datapath/num_of_tags_test/num_of_tags_test.json +4 -3
  58. {brg_certificate → certificate}/tests/datapath/num_of_tags_test/num_of_tags_test.py +19 -13
  59. certificate/tests/datapath/output_power_test/output_power_test.json +23 -0
  60. {brg_certificate → certificate}/tests/datapath/output_power_test/output_power_test.py +17 -6
  61. {brg_certificate → certificate}/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.json +2 -1
  62. {brg_certificate → certificate}/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.py +13 -11
  63. {brg_certificate → certificate}/tests/datapath/pacer_interval_test/pacer_interval_test.json +2 -1
  64. {brg_certificate → certificate}/tests/datapath/pacer_interval_test/pacer_interval_test.py +9 -7
  65. {brg_certificate → certificate}/tests/datapath/pattern_test/pattern_test.json +3 -2
  66. {brg_certificate → certificate}/tests/datapath/pattern_test/pattern_test.py +18 -6
  67. certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.json +20 -0
  68. certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.py +61 -0
  69. {brg_certificate → certificate}/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.json +2 -1
  70. {brg_certificate → certificate}/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.py +15 -14
  71. certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.json +19 -0
  72. certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.py +85 -0
  73. {brg_certificate → certificate}/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.json +2 -1
  74. {brg_certificate → certificate}/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.py +10 -9
  75. {brg_certificate → certificate}/tests/datapath/pkt_filter_test/pkt_filter_test.json +2 -1
  76. {brg_certificate → certificate}/tests/datapath/pkt_filter_test/pkt_filter_test.py +10 -9
  77. {brg_certificate → certificate}/tests/datapath/rssi_threshold_test/rssi_threshold_test.json +3 -2
  78. {brg_certificate → certificate}/tests/datapath/rssi_threshold_test/rssi_threshold_test.py +9 -8
  79. brg_certificate/tests/datapath/output_power_test/output_power_test.json → certificate/tests/datapath/rx_channel_hopping_test/rx_channel_hopping_test.json +6 -4
  80. certificate/tests/datapath/rx_channel_hopping_test/rx_channel_hopping_test.py +77 -0
  81. {brg_certificate → certificate}/tests/datapath/rx_channel_test/rx_channel_test.json +3 -2
  82. {brg_certificate → certificate}/tests/datapath/rx_channel_test/rx_channel_test.py +7 -6
  83. {brg_certificate → certificate}/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.json +8 -7
  84. {brg_certificate → certificate}/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +113 -73
  85. {brg_certificate → certificate}/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.json +8 -7
  86. {brg_certificate → certificate}/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.py +112 -72
  87. {brg_certificate → certificate}/tests/datapath/stress_gen3_test/stress_gen3_test.json +4 -3
  88. {brg_certificate → certificate}/tests/datapath/stress_gen3_test/stress_gen3_test.py +15 -11
  89. {brg_certificate → certificate}/tests/datapath/stress_test/stress_test.json +4 -3
  90. {brg_certificate → certificate}/tests/datapath/stress_test/stress_test.py +15 -11
  91. {brg_certificate → certificate}/tests/datapath/tx_repetition_test/tx_repetition_test.json +3 -1
  92. {brg_certificate → certificate}/tests/datapath/tx_repetition_test/tx_repetition_test.py +14 -13
  93. certificate/tests/edge_mgmt/action_blink_test/action_blink_test.json +15 -0
  94. certificate/tests/edge_mgmt/action_blink_test/action_blink_test.py +24 -0
  95. certificate/tests/edge_mgmt/action_get_battery_sensor_test/action_get_battery_sensor_test.json +15 -0
  96. certificate/tests/edge_mgmt/action_get_battery_sensor_test/action_get_battery_sensor_test.py +43 -0
  97. certificate/tests/edge_mgmt/action_get_module_test/action_get_module_test.json +15 -0
  98. certificate/tests/edge_mgmt/action_get_module_test/action_get_module_test.py +42 -0
  99. certificate/tests/edge_mgmt/action_get_pof_data_test/action_get_pof_data_test.json +15 -0
  100. certificate/tests/edge_mgmt/action_get_pof_data_test/action_get_pof_data_test.py +44 -0
  101. certificate/tests/edge_mgmt/action_gw_hb_test/action_gw_hb_test.json +16 -0
  102. certificate/tests/edge_mgmt/action_gw_hb_test/action_gw_hb_test.py +42 -0
  103. certificate/tests/edge_mgmt/action_reboot_test/action_reboot_test.json +15 -0
  104. certificate/tests/edge_mgmt/action_reboot_test/action_reboot_test.py +49 -0
  105. certificate/tests/edge_mgmt/action_restore_defaults_test/action_restore_defaults_test.json +15 -0
  106. certificate/tests/edge_mgmt/action_restore_defaults_test/action_restore_defaults_test.py +102 -0
  107. certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.json +15 -0
  108. certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.py +45 -0
  109. {brg_certificate → certificate}/tests/edge_mgmt/periodic_msgs_test/periodic_msgs_test.json +3 -2
  110. {brg_certificate → certificate}/tests/edge_mgmt/periodic_msgs_test/periodic_msgs_test.py +22 -11
  111. {brg_certificate → certificate}/tests/energy2400/duty_cycle_test/duty_cycle_test.json +2 -1
  112. {brg_certificate → certificate}/tests/energy2400/duty_cycle_test/duty_cycle_test.py +7 -6
  113. certificate/tests/energy2400/output_power_test/output_power_test.json +23 -0
  114. {brg_certificate → certificate}/tests/energy2400/output_power_test/output_power_test.py +17 -6
  115. {brg_certificate → certificate}/tests/energy2400/pattern_test/pattern_test.json +2 -1
  116. {brg_certificate → certificate}/tests/energy2400/pattern_test/pattern_test.py +7 -6
  117. certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.json +26 -0
  118. certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +379 -0
  119. certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.json +20 -0
  120. certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.py +173 -0
  121. certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +24 -0
  122. certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +350 -0
  123. {brg_certificate → certificate}/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.json +2 -1
  124. {brg_certificate → certificate}/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.py +7 -6
  125. {brg_certificate → certificate}/tests/energy_sub1g/pattern_test/pattern_test.json +2 -1
  126. {brg_certificate → certificate}/tests/energy_sub1g/pattern_test/pattern_test.py +7 -6
  127. {brg_certificate → certificate}/tests/pwr_mgmt/pwr_mgmt_test/pwr_mgmt_test.json +2 -1
  128. {brg_certificate → certificate}/tests/pwr_mgmt/pwr_mgmt_test/pwr_mgmt_test.py +10 -10
  129. {brg_certificate → certificate}/tests/sensors/ext_sensor_test/ext_sensor_test.json +5 -4
  130. certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +450 -0
  131. certificate/wlt_types.py +122 -0
  132. {gw_certificate → common}/api_if/202/status.json +6 -0
  133. {gw_certificate → common}/api_if/203/status.json +6 -0
  134. {gw_certificate → common}/api_if/204/status.json +6 -0
  135. common/api_if/206/data.json +85 -0
  136. common/api_if/206/status.json +69 -0
  137. common/api_if/api_validation.py +91 -0
  138. common/web/templates/generator.html +210 -0
  139. common/web/templates/index.html +20 -0
  140. common/web/templates/menu.html +54 -0
  141. common/web/templates/parser.html +53 -0
  142. {brg_certificate/ag → common/web/templates}/wlt_types.html +1216 -191
  143. common/web/web_utils.py +399 -0
  144. {brg_certificate → common}/wltPb_pb2.py +14 -12
  145. {gw_certificate/common → common}/wltPb_pb2.pyi +16 -2
  146. gui_certificate/gui_certificate_cli.py +14 -0
  147. gui_certificate/server.py +1267 -0
  148. gui_certificate/templates/cert_run.html +1273 -0
  149. wiliot_certificate-4.5.0.dist-info/METADATA +99 -0
  150. wiliot_certificate-4.5.0.dist-info/RECORD +168 -0
  151. {wiliot_certificate-4.4.2.dist-info → wiliot_certificate-4.5.0.dist-info}/WHEEL +1 -1
  152. wiliot_certificate-4.5.0.dist-info/entry_points.txt +5 -0
  153. wiliot_certificate-4.5.0.dist-info/top_level.txt +3 -0
  154. brg_certificate/ag/energous_v0_defines.py +0 -925
  155. brg_certificate/ag/energous_v1_defines.py +0 -931
  156. brg_certificate/ag/energous_v2_defines.py +0 -925
  157. brg_certificate/ag/energous_v3_defines.py +0 -925
  158. brg_certificate/ag/energous_v4_defines.py +0 -925
  159. brg_certificate/ag/fanstel_lan_v0_defines.py +0 -925
  160. brg_certificate/ag/fanstel_lte_v0_defines.py +0 -925
  161. brg_certificate/ag/fanstel_wifi_v0_defines.py +0 -925
  162. brg_certificate/ag/minew_lte_v0_defines.py +0 -925
  163. brg_certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +0 -142
  164. brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +0 -785
  165. brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +0 -139
  166. brg_certificate/ag/wlt_types_ag_jsons/calibration.json +0 -394
  167. brg_certificate/ag/wlt_types_ag_jsons/custom.json +0 -515
  168. brg_certificate/ag/wlt_types_ag_jsons/datapath.json +0 -672
  169. brg_certificate/ag/wlt_types_ag_jsons/energy2400.json +0 -550
  170. brg_certificate/ag/wlt_types_ag_jsons/energySub1g.json +0 -595
  171. brg_certificate/ag/wlt_types_ag_jsons/externalSensor.json +0 -598
  172. brg_certificate/ag/wlt_types_ag_jsons/interface.json +0 -938
  173. brg_certificate/ag/wlt_types_ag_jsons/powerManagement.json +0 -1234
  174. brg_certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +0 -105
  175. brg_certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +0 -77
  176. brg_certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +0 -61
  177. brg_certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +0 -110
  178. brg_certificate/brg_certificate.py +0 -225
  179. brg_certificate/brg_certificate_cli.py +0 -63
  180. brg_certificate/cert_common.py +0 -923
  181. brg_certificate/cert_config.py +0 -402
  182. brg_certificate/cert_utils.py +0 -362
  183. brg_certificate/certificate_bcc_sanity_test_list.txt +0 -40
  184. brg_certificate/certificate_bcc_test_list.txt +0 -48
  185. brg_certificate/certificate_sanity_test_list.txt +0 -43
  186. brg_certificate/certificate_test_list.txt +0 -53
  187. brg_certificate/config/eclipse.json +0 -10
  188. brg_certificate/config/hivemq.json +0 -10
  189. brg_certificate/config/mosquitto.json +0 -10
  190. brg_certificate/config/mosquitto.md +0 -95
  191. brg_certificate/config/wiliot-dev.json +0 -10
  192. brg_certificate/restore_brg.py +0 -61
  193. brg_certificate/tests/calibration/output_power_test/output_power_test.json +0 -16
  194. brg_certificate/tests/calibration/output_power_test/output_power_test.py +0 -28
  195. brg_certificate/tests/datapath/aging_test/aging_test.py +0 -143
  196. brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.json +0 -16
  197. brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.py +0 -73
  198. brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.json +0 -17
  199. brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.py +0 -118
  200. brg_certificate/tests/edge_mgmt/actions_test/actions_test.json +0 -14
  201. brg_certificate/tests/edge_mgmt/actions_test/actions_test.py +0 -396
  202. brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.json +0 -20
  203. brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.py +0 -94
  204. brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.json +0 -19
  205. brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.py +0 -87
  206. brg_certificate/tests/edge_mgmt/leds_test/leds_test.json +0 -17
  207. brg_certificate/tests/edge_mgmt/leds_test/leds_test.py +0 -223
  208. brg_certificate/tests/edge_mgmt/ota_test/ota_test.json +0 -17
  209. brg_certificate/tests/edge_mgmt/ota_test/ota_test.py +0 -128
  210. brg_certificate/tests/energy2400/output_power_test/output_power_test.json +0 -16
  211. brg_certificate/tests/energy2400/signal_indicator_ble5_10_250k_test/signal_indicator_ble5_10_250k_test.json +0 -20
  212. brg_certificate/tests/energy2400/signal_indicator_ble5_10_250k_test/signal_indicator_ble5_10_250k_test.py +0 -321
  213. brg_certificate/tests/energy2400/signal_indicator_ble5_10_500k_test/signal_indicator_ble5_10_500k_test.json +0 -20
  214. brg_certificate/tests/energy2400/signal_indicator_ble5_10_500k_test/signal_indicator_ble5_10_500k_test.py +0 -321
  215. brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.json +0 -20
  216. brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.py +0 -141
  217. brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +0 -20
  218. brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +0 -276
  219. brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.json +0 -20
  220. brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.py +0 -390
  221. brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.json +0 -16
  222. brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.py +0 -28
  223. brg_certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +0 -305
  224. brg_certificate/wltPb_pb2.pyi +0 -234
  225. brg_certificate/wlt_types.py +0 -113
  226. gw_certificate/ag/ut_defines.py +0 -364
  227. gw_certificate/ag/wlt_types.py +0 -85
  228. gw_certificate/ag/wlt_types_ag.py +0 -5310
  229. gw_certificate/ag/wlt_types_data.py +0 -64
  230. gw_certificate/api/extended_api.py +0 -23
  231. gw_certificate/api_if/200/data.json +0 -106
  232. gw_certificate/api_if/200/status.json +0 -47
  233. gw_certificate/api_if/201/data.json +0 -98
  234. gw_certificate/api_if/201/status.json +0 -53
  235. gw_certificate/api_if/205/logs.json +0 -12
  236. gw_certificate/api_if/api_validation.py +0 -38
  237. gw_certificate/api_if/gw_capabilities.py +0 -54
  238. gw_certificate/cert_results.py +0 -145
  239. gw_certificate/common/analysis_data_bricks.py +0 -60
  240. gw_certificate/common/debug.py +0 -42
  241. gw_certificate/common/serialization_formatter.py +0 -93
  242. gw_certificate/common/utils.py +0 -8
  243. gw_certificate/common/utils_defines.py +0 -15
  244. gw_certificate/common/wltPb_pb2.py +0 -84
  245. gw_certificate/gw_certificate.py +0 -154
  246. gw_certificate/gw_certificate_cli.py +0 -87
  247. gw_certificate/interface/4.4.91_app.zip +0 -0
  248. gw_certificate/interface/4.4.91_sd_bl_app.zip +0 -0
  249. gw_certificate/interface/ble_simulator.py +0 -61
  250. gw_certificate/interface/ble_sniffer.py +0 -189
  251. gw_certificate/interface/flash_fw.py +0 -90
  252. gw_certificate/interface/if_defines.py +0 -36
  253. gw_certificate/interface/mqtt.py +0 -563
  254. gw_certificate/interface/nrfutil-linux +0 -0
  255. gw_certificate/interface/nrfutil-mac +0 -0
  256. gw_certificate/interface/nrfutil.exe +0 -0
  257. gw_certificate/interface/pkt_generator.py +0 -594
  258. gw_certificate/interface/uart_if.py +0 -236
  259. gw_certificate/interface/uart_ports.py +0 -20
  260. gw_certificate/templates/results.html +0 -241
  261. gw_certificate/templates/stage.html +0 -22
  262. gw_certificate/templates/table.html +0 -6
  263. gw_certificate/templates/test.html +0 -38
  264. gw_certificate/tests/__init__.py +0 -10
  265. gw_certificate/tests/actions.py +0 -289
  266. gw_certificate/tests/bad_crc_to_PER_quantization.csv +0 -51
  267. gw_certificate/tests/connection.py +0 -188
  268. gw_certificate/tests/downlink.py +0 -172
  269. gw_certificate/tests/generic.py +0 -238
  270. gw_certificate/tests/registration.py +0 -340
  271. gw_certificate/tests/static/__init__.py +0 -0
  272. gw_certificate/tests/static/connection_defines.py +0 -9
  273. gw_certificate/tests/static/downlink_defines.py +0 -9
  274. gw_certificate/tests/static/generated_packet_table.py +0 -195
  275. gw_certificate/tests/static/packet_table.csv +0 -10067
  276. gw_certificate/tests/static/references.py +0 -5
  277. gw_certificate/tests/static/uplink_defines.py +0 -14
  278. gw_certificate/tests/throughput.py +0 -240
  279. gw_certificate/tests/uplink.py +0 -853
  280. wiliot_certificate-4.4.2.dist-info/METADATA +0 -211
  281. wiliot_certificate-4.4.2.dist-info/RECORD +0 -210
  282. wiliot_certificate-4.4.2.dist-info/entry_points.txt +0 -3
  283. wiliot_certificate-4.4.2.dist-info/top_level.txt +0 -3
  284. {brg_certificate → certificate}/__init__.py +0 -0
  285. {gw_certificate → common}/api_if/202/data.json +0 -0
  286. {gw_certificate/api_if/200 → common/api_if/202}/logs.json +0 -0
  287. {gw_certificate → common}/api_if/203/data.json +0 -0
  288. {gw_certificate/api_if/201 → common/api_if/203}/logs.json +0 -0
  289. {gw_certificate → common}/api_if/204/data.json +0 -0
  290. {gw_certificate/api_if/202 → common/api_if/204}/logs.json +0 -0
  291. {gw_certificate → common}/api_if/205/data.json +0 -0
  292. {gw_certificate/api_if/203 → common/api_if/205}/logs.json +0 -0
  293. {gw_certificate → common}/api_if/205/status.json +0 -0
  294. {gw_certificate/api_if/204 → common/api_if/206}/logs.json +0 -0
  295. {gw_certificate → common/api_if}/__init__.py +0 -0
  296. {gw_certificate/api_if → gui_certificate}/__init__.py +0 -0
  297. {wiliot_certificate-4.4.2.dist-info → wiliot_certificate-4.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1273 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <!-- Required meta tags -->
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>{{ app }}</title>
8
+ <link rel="icon" href="https://www.wiliot.com/favicon.ico" type="image/x-icon" />
9
+ <!-- Bootstrap CSS -->
10
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
11
+ <style>
12
+ /* Progress indicator */
13
+ .step-indicator {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ margin-bottom: 2rem;
17
+ position: relative;
18
+ }
19
+ .step-indicator::before {
20
+ content: '';
21
+ position: absolute;
22
+ top: 20px;
23
+ left: 0;
24
+ right: 0;
25
+ height: 2px;
26
+ background: #dee2e6;
27
+ z-index: 0;
28
+ }
29
+ .step-indicator .step {
30
+ position: relative;
31
+ z-index: 1;
32
+ background: white;
33
+ width: 40px;
34
+ height: 40px;
35
+ border-radius: 50%;
36
+ border: 2px solid #dee2e6;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ font-weight: bold;
41
+ color: #6c757d;
42
+ }
43
+ .step-indicator .step.active {
44
+ border-color: #00AB83;
45
+ background: #00AB83;
46
+ color: white;
47
+ }
48
+ .step-indicator .step.completed {
49
+ border-color: #00AB83;
50
+ background: #00AB83;
51
+ color: white;
52
+ }
53
+ .step-indicator > div {
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-items: center;
57
+ flex: 1;
58
+ }
59
+ .step-indicator .step-label {
60
+ margin-top: 0.5rem;
61
+ font-size: 0.875rem;
62
+ text-align: center;
63
+ color: #6c757d;
64
+ }
65
+ .step-indicator .step-label.active {
66
+ color: #00AB83;
67
+ font-weight: 600;
68
+ }
69
+ .step-indicator .step.active span,
70
+ .step-indicator .step.completed span {
71
+ color: white;
72
+ }
73
+
74
+ /* Step content */
75
+ .step-content {
76
+ min-height: 400px;
77
+ padding: 2rem 0;
78
+ }
79
+
80
+ /* Mandatory badge */
81
+ .mandatory-badge {
82
+ background: #dc3545;
83
+ color: white;
84
+ font-size: 0.75rem;
85
+ padding: 0.25rem 0.5rem;
86
+ border-radius: 0.25rem;
87
+ margin-left: 0.5rem;
88
+ }
89
+
90
+ /* Knowledge base link */
91
+ .kb-link {
92
+ color: #0066cc;
93
+ text-decoration: none;
94
+ margin-left: 0.5rem;
95
+ font-size: 0.875rem;
96
+ }
97
+ .kb-link:hover {
98
+ text-decoration: underline;
99
+ }
100
+ /* Chevron rotation */
101
+ .test-toggle .chev {
102
+ transition: transform .15s ease;
103
+ transform: rotate(0deg);
104
+ }
105
+ .test-toggle.collapsed .chev {
106
+ transform: rotate(-90deg);
107
+ }
108
+
109
+ /* Certification warning */
110
+ .cert-warning {
111
+ background: #fff3cd;
112
+ border: 1px solid #ffc107;
113
+ border-radius: 0.5rem;
114
+ padding: 1rem;
115
+ margin: 1rem 0;
116
+ }
117
+
118
+ /* Flavor cards */
119
+ .flavor-card {
120
+ border: 2px solid #dee2e6;
121
+ border-radius: 0.5rem;
122
+ padding: 1.5rem;
123
+ margin-bottom: 1rem;
124
+ cursor: pointer;
125
+ transition: all 0.2s;
126
+ }
127
+ .flavor-card:hover {
128
+ border-color: #00AB83;
129
+ background: #f0fdfa;
130
+ }
131
+ .flavor-card.selected {
132
+ border-color: #00AB83;
133
+ background: #e6f7f4;
134
+ }
135
+ .flavor-card input[type="radio"] {
136
+ margin-right: 0.5rem;
137
+ }
138
+ </style>
139
+ </head>
140
+ <body>
141
+ <div class="container">
142
+ {% include 'menu.html' %}
143
+ <h1 style="color:#00AB83">{{ title }}</h1>
144
+ <hr>
145
+
146
+ {% macro kb_link(doc) -%}
147
+ {% if doc %}
148
+ {% if doc is string %}
149
+ <a href="{{ doc }}" target="_blank" class="kb-link" title="Knowledge Base">📚 KB</a>
150
+ {% elif doc is iterable %}
151
+ {% for url in doc %}
152
+ <a href="{{ url }}" target="_blank" class="kb-link" title="Knowledge Base">📚 KB</a>
153
+ {% endfor %}
154
+ {% endif %}
155
+ {% endif %}
156
+ {%- endmacro %}
157
+
158
+ {% set non_cert_text = "NOT READY FOR CERTIFICATION" %}
159
+ {% set unsterile_run_warning = "Unsterile run mode is set - a certifying run must be sterile." %}
160
+
161
+ <!-- Progress Indicator -->
162
+ <div class="step-indicator">
163
+ <div>
164
+ <div class="step {{ 'completed' if step_num > 1 else 'active' if step_num == 1 else '' }}">
165
+ <span>1</span>
166
+ </div>
167
+ <div class="step-label {{ 'active' if step_num >= 1 else '' }}">Flavor</div>
168
+ </div>
169
+ <div>
170
+ <div class="step {{ 'completed' if step_num > 2 else 'active' if step_num == 2 else '' }}">
171
+ <span>2</span>
172
+ </div>
173
+ <div class="step-label {{ 'active' if step_num >= 2 else '' }}">Schema</div>
174
+ </div>
175
+ <div>
176
+ <div class="step {{ 'completed' if (step_num > 3 or (step_num >= 3 and flavor == 'gw_only')) else 'active' if step_num == 3 and flavor != 'gw_only' else '' }}">
177
+ <span>3</span>
178
+ </div>
179
+ <div class="step-label {{ 'active' if (step_num >= 3 or (flavor == 'gw_only' and step_num >= 2)) else '' }}">Modules</div>
180
+ </div>
181
+ <div>
182
+ <div class="step {{ 'completed' if step_num > 4 else 'active' if step_num == 4 else '' }}">
183
+ <span>4</span>
184
+ </div>
185
+ <div class="step-label {{ 'active' if step_num >= 4 else '' }}">Tests</div>
186
+ </div>
187
+ <div>
188
+ <div class="step {{ 'active' if step_num == 5 else '' }}">
189
+ <span>5</span>
190
+ </div>
191
+ <div class="step-label {{ 'active' if step_num >= 5 else '' }}">Review</div>
192
+ </div>
193
+ </div>
194
+
195
+ <!-- Flash Messages -->
196
+ {% with messages = get_flashed_messages(with_categories=true) %}
197
+ {% if messages %}
198
+ {% for category, message in messages %}
199
+ <div class="alert alert-{{ 'danger' if category == 'error' else 'success' if category == 'success' else 'warning' if category == 'warning' else 'info' }}" role="alert">
200
+ {{ message }}
201
+ </div>
202
+ {% endfor %}
203
+ {% endif %}
204
+ {% endwith %}
205
+
206
+ <!-- Error/Warning Messages -->
207
+ {% if error %}
208
+ <div class="alert alert-danger" role="alert">
209
+ {{ error }}
210
+ </div>
211
+ {% endif %}
212
+
213
+ {% if warning %}
214
+ <div class="alert alert-warning" role="alert">
215
+ {{ warning }}
216
+ </div>
217
+ {% endif %}
218
+
219
+ <!-- Step Content -->
220
+ <div class="step-content">
221
+ <form method="POST" enctype="multipart/form-data" id="mainForm">
222
+ <!-- Step 1: Flavor Selection -->
223
+ {% if step_num == 1 %}
224
+ <h3>Step 1: Select Certification Flavor</h3>
225
+ <p class="text-muted">Choose the type of certification you want to run.</p>
226
+
227
+ <div class="flavor-card" onclick="document.getElementById('flavor_gw').click()">
228
+ <input type="radio" name="flavor" id="flavor_gw" value="gw_only" required>
229
+ <label for="flavor_gw" style="cursor: pointer; font-weight: bold; font-size: 1.1rem;">
230
+ Gateway Only
231
+ </label>
232
+ <p class="mt-2 mb-0 text-muted">
233
+ <strong>Mandatory Module:</strong> Cloud Connectivity<br>
234
+ Tests gateway's BLE scans & MQTT data uploads capabilities.
235
+ </p>
236
+ </div>
237
+
238
+ <div class="flavor-card" onclick="document.getElementById('flavor_brg').click()">
239
+ <input type="radio" name="flavor" id="flavor_brg" value="bridge_only" required>
240
+ <label for="flavor_brg" style="cursor: pointer; font-weight: bold; font-size: 1.1rem;">
241
+ Bridge Only
242
+ </label>
243
+ <p class="mt-2 mb-0 text-muted">
244
+ <strong>Mandatory Module:</strong> Edge Management<br>
245
+ Requires at least one additional module beyond the mandatory Edge Management module.
246
+ </p>
247
+ </div>
248
+
249
+ <div class="flavor-card" onclick="document.getElementById('flavor_combo').click()">
250
+ <input type="radio" name="flavor" id="flavor_combo" value="combo" required>
251
+ <label for="flavor_combo" style="cursor: pointer; font-weight: bold; font-size: 1.1rem;">
252
+ Combo (Gateway + Bridge)
253
+ </label>
254
+ <p class="mt-2 mb-0 text-muted">
255
+ <strong>Mandatory Modules:</strong> Cloud Connectivity + Edge Management<br>
256
+ Requires at least one additional bridge module beyond the mandatory Edge Management module.<br>
257
+ Tests both gateway and bridge capabilities.
258
+ </p>
259
+ </div>
260
+
261
+ <script>
262
+ document.querySelectorAll('input[name="flavor"]').forEach(radio => {
263
+ radio.addEventListener('change', function() {
264
+ document.querySelectorAll('.flavor-card').forEach(card => {
265
+ card.classList.remove('selected');
266
+ });
267
+ this.closest('.flavor-card').classList.add('selected');
268
+ });
269
+ });
270
+ </script>
271
+
272
+ <!-- Step 2: Validation Schema Upload -->
273
+ {% elif step_num == 2 %}
274
+ <h3>Step 2: Upload Validation Schema</h3>
275
+ <p class="text-muted">Upload the JSON validation schema file for your device. <a href="https://community.wiliot.com/customers/s/article/Validation-Schema" target="_blank" class="kb-link">📚 KB</a></p>
276
+
277
+ <div class="mb-3">
278
+ <label for="schema_file" class="form-label">Validation Schema File (JSON)</label>
279
+ <input type="file" class="form-control" id="schema_file" name="schema_file" accept=".json">
280
+ <div class="form-text">Select a JSON validation schema file that matches your selected flavor.</div>
281
+ </div>
282
+
283
+ {% if schema_path %}
284
+ <div class="alert alert-success">
285
+ Schema uploaded: {{ schema_path.split('/')[-1] }}
286
+ </div>
287
+ {% endif %}
288
+
289
+ <!-- Step 3: Module Selection -->
290
+ {% elif step_num == 3 %}
291
+ <h3>Step 3: Select Modules</h3>
292
+ <p class="text-muted">Select the modules you want to test. Mandatory modules are pre-selected.</p>
293
+
294
+ {% if flavor == 'bridge_only' %}
295
+ <div class="alert alert-info">
296
+ <strong>Note:</strong> Bridge only requires at least one additional module beyond the mandatory Edge Management module.
297
+ </div>
298
+ {% elif flavor == 'combo' %}
299
+ <div class="alert alert-info">
300
+ <strong>Note:</strong> Combo requires at least one additional bridge module beyond the mandatory Edge Management module.
301
+ </div>
302
+ {% endif %}
303
+
304
+ <div id="step3_warning" class="cert-warning" style="display: none;">
305
+ <strong>⚠️ Warning:</strong> {{ non_cert_text }}<br>
306
+ <small id="step3_warning_text"></small>
307
+ </div>
308
+
309
+ {% for mod in filtered_modules %}
310
+ {% set is_mandatory = mod.is_mandatory | default(False) %}
311
+ {% set is_selected = mod.name in selected_modules %}
312
+ {% set has_selections = selected_modules|length > 0 %}
313
+ {% set mod_doc = None %}
314
+ {% if mod.tests and mod.tests|length > 0 %}
315
+ {% for test in mod.tests %}
316
+ {% if test.meta.documentation and not mod_doc %}
317
+ {% set mod_doc = test.meta.documentation %}
318
+ {% endif %}
319
+ {% endfor %}
320
+ {% endif %}
321
+
322
+ <div class="card mb-3">
323
+ <div class="card-body">
324
+ <div class="form-check">
325
+ <input class="form-check-input"
326
+ type="checkbox"
327
+ name="modules[]"
328
+ id="module_{{ mod.name }}"
329
+ value="{{ mod.name }}"
330
+ {% if is_selected or (is_mandatory and not has_selections) or (mod.is_mandatory_by_schema and not has_selections) %}checked{% endif %}>
331
+ <label class="form-check-label" for="module_{{ mod.name }}" style="font-weight: bold; font-size: 1.1rem;">
332
+ {{ mod.name }}
333
+ {% if is_mandatory %}
334
+ <span class="mandatory-badge">Mandatory</span>
335
+ {% endif %}
336
+ {% if mod.is_mandatory_by_schema %}
337
+ <span class="mandatory-badge" style="background: #0066cc;">Mandatory by Schema</span>
338
+ {% endif %}
339
+ {{ kb_link(mod_doc) }}
340
+ </label>
341
+ <p class="text-muted mt-1 mb-0">
342
+ {{ mod.tests|length }} test(s) available
343
+ </p>
344
+ </div>
345
+ </div>
346
+ </div>
347
+ {% endfor %}
348
+
349
+ <!-- Step 4: Test Selection -->
350
+ {% elif step_num == 4 %}
351
+ <h3>Step 4: Select Tests</h3>
352
+ <p class="text-muted">Select the specific tests to run. Mandatory tests are pre-selected and cannot be deselected.</p>
353
+
354
+ <div id="step4_warning" class="cert-warning" style="display: none;">
355
+ <strong>⚠️ Warning:</strong> {{ non_cert_text }}<br>
356
+ <small id="step4_warning_text"></small>
357
+ </div>
358
+
359
+ {% for mod in filtered_modules %}
360
+ {% if mod.name in selected_modules %}
361
+ {% set mod_id = 'mod_' ~ mod.name|replace('/', '_') %}
362
+ <div class="card mb-3">
363
+ <div class="card-header d-flex align-items-center">
364
+ <button class="btn btn-sm btn-link text-decoration-none test-toggle me-2 collapsed"
365
+ type="button"
366
+ data-bs-toggle="collapse"
367
+ data-bs-target="#{{ mod_id }}"
368
+ aria-expanded="false">
369
+ <svg class="chev" width="16" height="16" viewBox="0 0 16 16">
370
+ <path d="M4.646 5.646a.5.5 0 0 1 .708 0L8 8.293l2.646-2.647a.5.5 0 1 1 .708.708L8.354 9.354a.5.5 0 0 1-.708 0L4.646 6.354a.5.5 0 0 1 0-.708z" fill="currentColor"/>
371
+ </svg>
372
+ </button>
373
+ <strong>{{ mod.name }}</strong>
374
+ <span class="text-muted ms-2">({{ mod.tests|length }} tests)</span>
375
+ <div class="ms-auto d-flex gap-2">
376
+ <button type="button"
377
+ class="btn btn-sm btn-primary"
378
+ data-mod-select-all="{{ mod.name }}">
379
+ Select All
380
+ </button>
381
+ <button type="button"
382
+ class="btn btn-sm btn-secondary"
383
+ data-mod-deselect-all="{{ mod.name }}">
384
+ Deselect All
385
+ </button>
386
+ </div>
387
+ </div>
388
+ <div id="{{ mod_id }}" class="collapse">
389
+ <div class="card-body">
390
+ <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
391
+ {% for t in mod.tests %}
392
+ {% set test_id = 'test_' ~ t.id|replace('/','_') %}
393
+ {% set is_mandatory = t.meta.mandatory | default(0) == 1 %}
394
+ {% set is_selected = t.id in selected_tests %}
395
+ {% set has_selections = selected_tests|length > 0 %}
396
+ {% set tip_id = 'tip_' ~ t.id|replace('/','_') %}
397
+ {% set test_doc = t.meta.documentation %}
398
+
399
+ <div class="col">
400
+ <div class="form-check">
401
+ <input class="form-check-input"
402
+ type="checkbox"
403
+ id="{{ test_id }}"
404
+ name="tests[]"
405
+ value="{{ t.id }}"
406
+ data-mod="{{ mod.name }}"
407
+ {% if is_selected %}checked{% elif is_mandatory and not has_selections %}checked{% endif %}>
408
+ <label class="form-check-label {% if is_mandatory %}fw-bold{% endif %}" for="{{ test_id }}">
409
+ {{ t.label }}
410
+ {% if is_mandatory %}
411
+ <span class="mandatory-badge">Mandatory</span>
412
+ {% endif %}
413
+ {{ kb_link(test_doc) }}
414
+ </label>
415
+ {% if t.tooltip %}
416
+ <div class="text-muted" style="font-size: 0.875rem; font-weight: normal; margin-top: 0.25rem;">{{ t.tooltip }}</div>
417
+ {% endif %}
418
+ </div>
419
+
420
+ {# Test parameters #}
421
+ {% set params_list = t.meta.allSupportedValues | default([]) %}
422
+ {% if params_list %}
423
+ {% set has_dynamic = "dynamic_parameters" in params_list %}
424
+ {% if has_dynamic %}
425
+ {# Use params_list[1] as default if it exists, otherwise "1" #}
426
+ {% if params_list|length > 1 %}
427
+ {% set default_value = params_list[1] %}
428
+ {% else %}
429
+ {% set default_value = "1" %}
430
+ {% endif %}
431
+ {# Check if value exists in form_data (for restoring saved values) #}
432
+ {% set dynamic_param_name = "params_" ~ t.id|replace('/','_') ~ "_dynamic" %}
433
+ {% set saved_value = form_data.get(dynamic_param_name, default_value) %}
434
+ {% endif %}
435
+ <div id="{{ test_id }}_params" class="ms-4 mt-1 ps-2 border-start d-none">
436
+ {% if has_dynamic %}
437
+ <div class="small text-muted mb-1">Dynamic Parameter</div>
438
+ {# Dynamic parameter: integer input #}
439
+ <div class="d-flex align-items-center gap-2">
440
+ <label class="small" for="{{ test_id }}_dynamic_param">Value:</label>
441
+ <input type="number"
442
+ class="form-control form-control-sm"
443
+ style="width: 100px;"
444
+ id="{{ test_id }}_dynamic_param"
445
+ name="params_{{ t.id|replace('/','_') }}_dynamic"
446
+ value="{{ saved_value }}"
447
+ min="1"
448
+ step="1">
449
+ </div>
450
+ {% else %}
451
+ <div class="small text-muted mb-1">Parameters</div>
452
+ {# Regular parameters: checkboxes #}
453
+ <div class="d-flex flex-wrap gap-2">
454
+ {% for p in params_list %}
455
+ {% set pid = test_id ~ '_p_' ~ loop.index %}
456
+ <div class="form-check">
457
+ <input class="form-check-input"
458
+ type="checkbox"
459
+ id="{{ pid }}"
460
+ name="params_{{ t.id|replace('/','_') }}[]"
461
+ value="{{ p }}"
462
+ checked>
463
+ <label class="form-check-label small" for="{{ pid }}">{{ p }}</label>
464
+ </div>
465
+ {% endfor %}
466
+ </div>
467
+ {% endif %}
468
+ </div>
469
+ {% endif %}
470
+ </div>
471
+ {% endfor %}
472
+ </div>
473
+ </div>
474
+ </div>
475
+ </div>
476
+ {% endif %}
477
+ {% endfor %}
478
+
479
+ <!-- Additional form fields for CLI parameters -->
480
+ {% set cert_schema = cert_schema %}
481
+
482
+ <hr class="my-4">
483
+ <h4>Run Parameters</h4>
484
+ {% set common_parameters = ['dut', 'custom_broker'] %}
485
+
486
+ {% for f in cert_schema.fields %}
487
+ {% if f.name != 'validation_schema' and f.name != 'tl' and f.name != 'custom_broker' and f.name in common_parameters %}
488
+ {% set arg_label = (f.option_strings|join(', ') if f.option_strings else f.name) %}
489
+ {% set tip_id = 'help_' ~ f.name %}
490
+ {% set field_name = cert_schema.title ~ '_' ~ f.name %}
491
+ {% set field_value = form_data.get(field_name, f.default if f.default is not none else '') %}
492
+
493
+ <div class="row mb-3">
494
+ <label for="{{ field_name }}" class="col-sm-3 col-form-label">
495
+ {{ arg_label }}{% if f.required %} <span class="text-danger">*</span>{% endif %}
496
+ </label>
497
+ {% if f.widget == "select" %}
498
+ <div class="col-sm-4">
499
+ <select id="{{ field_name }}" class="form-select" name="{{ field_name }}">
500
+ {% if f.default == None %}
501
+ <option value="">select value</option>
502
+ {% endif %}
503
+ {% for c in f.choices %}
504
+ <option value="{{ c }}" {% if c == field_value %}selected{% endif %}>{{ c }}</option>
505
+ {% endfor %}
506
+ </select>
507
+ </div>
508
+ {% elif f.is_flag %}
509
+ <div class="col-sm-4">
510
+ <div class="form-check">
511
+ <input type="checkbox" class="form-check-input" id="{{ field_name }}" name="{{ field_name }}" {% if field_value %}checked{% endif %}>
512
+ </div>
513
+ </div>
514
+ {% else %}
515
+ <div class="col-sm-4">
516
+ <input type="{{ f.type }}" class="form-control" id="{{ field_name }}" name="{{ field_name }}" value="{{ field_value }}">
517
+ </div>
518
+ {% endif %}
519
+ {% if f.help or f.help_link %}
520
+ <div class="col-sm-5 d-flex align-items-center">
521
+ <span class="text-muted" style="font-size: 0.875rem; font-weight: normal;">
522
+ {{ f.help }}
523
+ {% if f.help_link %}
524
+ <a href="{{ f.help_link }}" target="_blank" class="kb-link" title="Knowledge Base">Knowledge Base</a>
525
+ {% endif %}
526
+ </span>
527
+ </div>
528
+ {% endif %}
529
+ </div>
530
+ {% endif %}
531
+ {% endfor %}
532
+
533
+ <!-- Custom Broker Configuration -->
534
+ {% set cert_schema = cert_schema %}
535
+ {% set broker_field_prefix = cert_schema.title ~ '_custom_broker_' %}
536
+ <div class="card mb-3">
537
+ <div class="card-header d-flex align-items-center">
538
+ <button class="btn btn-sm btn-link text-decoration-none test-toggle me-2 collapsed"
539
+ type="button"
540
+ data-bs-toggle="collapse"
541
+ data-bs-target="#customBrokerConfig"
542
+ aria-expanded="false">
543
+ <svg class="chev" width="16" height="16" viewBox="0 0 16 16">
544
+ <path d="M4.646 5.646a.5.5 0 0 1 .708 0L8 8.293l2.646-2.647a.5.5 0 1 1 .708.708L8.354 9.354a.5.5 0 0 1-.708 0L4.646 6.354a.5.5 0 0 1 0-.708z" fill="currentColor"/>
545
+ </svg>
546
+ </button>
547
+ <strong>Custom MQTT Broker Configuration</strong>
548
+ <span class="text-danger ms-2">*</span>
549
+ </div>
550
+ <div id="customBrokerConfig" class="collapse">
551
+ <div class="card-body">
552
+ {% for field_key, field_default in custom_broker_defaults.items() %}
553
+ {% set field_name = broker_field_prefix ~ field_key %}
554
+ {% set field_value = form_data.get(field_name, field_default) %}
555
+ {% set input_type = 'number' if field_key == 'port' else 'password' if field_key == 'password' else 'text' %}
556
+ {% set is_required = field_key not in ['username', 'password'] %}
557
+ <div class="row mb-3">
558
+ <label for="{{ field_name }}" class="col-sm-3 col-form-label">
559
+ {{ field_key }}{% if is_required %} <span class="text-danger">*</span>{% endif %}
560
+ </label>
561
+ <div class="col-sm-4">
562
+ <input type="{{ input_type }}" class="form-control" id="{{ field_name }}" name="{{ field_name }}" value="{{ field_value }}">
563
+ </div>
564
+ {% if field_key in custom_broker_help.keys() %}
565
+ <div class="col-sm-5 d-flex align-items-center">
566
+ <span class="text-muted" style="font-size: 0.875rem; font-weight: normal;">
567
+ {{ custom_broker_help[field_key] }}
568
+ </span>
569
+ </div>
570
+ {% endif %}
571
+ </div>
572
+ {% endfor %}
573
+ </div>
574
+ </div>
575
+ </div>
576
+
577
+ <div class="card mb-3">
578
+ <div class="card-header d-flex align-items-center">
579
+ <button class="btn btn-sm btn-link text-decoration-none test-toggle me-2 collapsed"
580
+ type="button"
581
+ data-bs-toggle="collapse"
582
+ data-bs-target="#advancedParams"
583
+ aria-expanded="false">
584
+ <svg class="chev" width="16" height="16" viewBox="0 0 16 16">
585
+ <path d="M4.646 5.646a.5.5 0 0 1 .708 0L8 8.293l2.646-2.647a.5.5 0 1 1 .708.708L8.354 9.354a.5.5 0 0 1-.708 0L4.646 6.354a.5.5 0 0 1 0-.708z" fill="currentColor"/>
586
+ </svg>
587
+ </button>
588
+ <strong>Advanced Parameters</strong>
589
+ </div>
590
+ <div id="advancedParams" class="collapse">
591
+ <div class="card-body">
592
+ {% for f in cert_schema.fields %}
593
+ {% if f.name != 'validation_schema' and f.name != 'tl' and f.name not in common_parameters %}
594
+ {% set arg_label = (f.option_strings|join(', ') if f.option_strings else f.name) %}
595
+ {% set tip_id = 'help_' ~ f.name %}
596
+ {% set field_name = cert_schema.title ~ '_' ~ f.name %}
597
+ {% set field_value = form_data.get(field_name, f.default if f.default is not none else '') %}
598
+
599
+ <div class="row mb-3">
600
+ <label for="{{ field_name }}" class="col-sm-3 col-form-label">
601
+ {{ arg_label }}
602
+ </label>
603
+ {% if f.widget == "select" %}
604
+ <div class="col-sm-4">
605
+ <select id="{{ field_name }}" class="form-select" name="{{ field_name }}">
606
+ {% if f.default == None %}
607
+ <option value="">select value</option>
608
+ {% endif %}
609
+ {% for c in f.choices %}
610
+ <option value="{{ c }}" {% if c == field_value %}selected{% endif %}>{{ c }}</option>
611
+ {% endfor %}
612
+ </select>
613
+ </div>
614
+ {% elif f.is_flag %}
615
+ <div class="col-sm-4">
616
+ <div class="form-check">
617
+ <input type="checkbox" class="form-check-input" id="{{ field_name }}" name="{{ field_name }}" {% if field_value %}checked{% endif %}>
618
+ </div>
619
+ </div>
620
+ {% elif f.name == "overwrite_defaults" %}
621
+ <div class="col-sm-4">
622
+ <textarea id="{{ field_name }}" class="form-control" name="{{ field_name }}" rows="3" placeholder="key1=value1&#10;key2=value2">{{ field_value }}</textarea>
623
+ </div>
624
+ {% elif f.name == "port" %}
625
+ <div class="col-sm-4">
626
+ <select id="{{ field_name }}" class="form-select" name="{{ field_name }}">
627
+ <option value="">Select a port</option>
628
+ {% for port_info in available_ports %}
629
+ <option value="{{ port_info.value }}" {% if port_info.value == field_value %}selected{% endif %}>{{ port_info.label }}</option>
630
+ {% endfor %}
631
+ {% if field_value and field_value not in available_ports|map(attribute='value')|list %}
632
+ <option value="{{ field_value }}" selected>{{ field_value }} (not available)</option>
633
+ {% endif %}
634
+ </select>
635
+ </div>
636
+ {% else %}
637
+ <div class="col-sm-4">
638
+ <input type="{{ f.type }}" class="form-control" id="{{ field_name }}" name="{{ field_name }}" value="{{ field_value }}">
639
+ </div>
640
+ {% endif %}
641
+ {% if f.help or f.help_link %}
642
+ <div class="col-sm-5 d-flex align-items-center">
643
+ <span class="text-muted" style="font-size: 0.875rem; font-weight: normal;">
644
+ {{ f.help }}
645
+ {% if f.help_link %}
646
+ <a href="{{ f.help_link }}" target="_blank" class="kb-link" title="Knowledge Base">Knowledge Base</a>
647
+ {% endif %}
648
+ </span>
649
+ </div>
650
+ {% endif %}
651
+ </div>
652
+ {% endif %}
653
+ {% endfor %}
654
+ </div>
655
+ </div>
656
+ </div>
657
+
658
+ <!-- Step 5: Review and Run -->
659
+ {% elif step_num == 5 %}
660
+ <h3>Step 5: Review and Run</h3>
661
+
662
+ <!-- Summary -->
663
+ <div class="card mb-4">
664
+ <div class="card-header">
665
+ <h5 class="mb-0">Configuration Summary</h5>
666
+ </div>
667
+ <div class="card-body">
668
+ <p><strong>Flavor:</strong>
669
+ {% if flavor == 'gw_only' %}Gateway Only
670
+ {% elif flavor == 'bridge_only' %}Bridge Only
671
+ {% elif flavor == 'combo' %}Combo (Gateway + Bridge)
672
+ {% endif %}
673
+ </p>
674
+ <p><strong>Validation Schema:</strong> {{ schema_path.split('/')[-1] if schema_path else 'Not uploaded' }}</p>
675
+ <p><strong>Selected Modules:</strong> {{ selected_modules|join(', ') }}</p>
676
+ <p><strong>Selected Tests:</strong> {{ total_test_count }} test(s)</p>
677
+ <p><strong>Certification Status:</strong>
678
+ {% if is_certified %}
679
+ <span class="badge bg-success">READY FOR CERTIFICATION</span>
680
+ <small class="text-muted d-block mt-1">All mandatory modules and tests are selected</small>
681
+ {% else %}
682
+ <span class="badge bg-warning text-dark">{{ non_cert_text }}</span>
683
+ <small class="text-muted d-block mt-1">
684
+ {% if not unsterile_run %}
685
+ Some mandatory modules or tests are missing
686
+ {% endif %}
687
+ </small>
688
+ {% endif %}
689
+ </p>
690
+
691
+ {% if unsterile_run %}
692
+ <div class="alert alert-warning">
693
+ <strong>⚠️ Warning:</strong> {{ unsterile_run_warning }}
694
+ </div>
695
+ {% endif %}
696
+
697
+ {% if not is_certified %}
698
+ {% if missing_modules %}
699
+ <div class="alert alert-warning">
700
+ <strong>Missing Mandatory Modules:</strong>
701
+ <ul class="mb-0">
702
+ {% for mod in missing_modules %}
703
+ <li>{{ mod }}</li>
704
+ {% endfor %}
705
+ </ul>
706
+ </div>
707
+ {% endif %}
708
+ {% if missing_additional_module %}
709
+ <div class="alert alert-warning">
710
+ <strong>Missing Additional Module:</strong>
711
+ <ul class="mb-0">
712
+ {% if flavor == 'bridge_only' %}
713
+ <li>Bridge Only requires at least one additional module beyond the mandatory module (edge_mgmt)</li>
714
+ {% elif flavor == 'combo' %}
715
+ <li>Combo requires at least one additional bridge module beyond the mandatory modules (cloud_connectivity and edge_mgmt)</li>
716
+ {% endif %}
717
+ </ul>
718
+ </div>
719
+ {% endif %}
720
+ {% if missing_tests_details %}
721
+ <div class="alert alert-warning">
722
+ <strong>Missing Mandatory Tests from Selected Modules:</strong>
723
+ <ul class="mb-0">
724
+ {% for test in missing_tests_details %}
725
+ <li>{{ test.label }} <small class="text-muted">({{ test.module }})</small></li>
726
+ {% endfor %}
727
+ </ul>
728
+ <small class="d-block mt-2">When a module is selected, its mandatory tests must also be selected.</small>
729
+ </div>
730
+ {% endif %}
731
+ {% endif %}
732
+ </div>
733
+ </div>
734
+
735
+ <!-- Schema Verification -->
736
+ <div class="card mb-4">
737
+ <div class="card-header">
738
+ <h5 class="mb-0">Schema Verification</h5>
739
+ </div>
740
+ <div class="card-body">
741
+ {% if schema_errors or schema_warnings or missing_modules_by_schema %}
742
+ <p><strong>Schema Status:</strong>
743
+ <span class="badge bg-warning text-dark">SCHEMA DOES NOT MATCH RUN SETTINGS</span>
744
+ </p>
745
+ {% if schema_errors %}
746
+ <div class="alert alert-danger">
747
+ <strong>Errors:</strong>
748
+ <ul class="mb-0">
749
+ {% for err in schema_errors %}
750
+ <li>{{ err }}</li>
751
+ {% endfor %}
752
+ </ul>
753
+ </div>
754
+ {% endif %}
755
+ {% if schema_warnings %}
756
+ <div class="alert alert-warning">
757
+ <strong>Warnings:</strong>
758
+ <ul class="mb-0">
759
+ {% for warn in schema_warnings %}
760
+ <li>{{ warn }}</li>
761
+ {% endfor %}
762
+ </ul>
763
+ </div>
764
+ {% endif %}
765
+ {% if missing_modules_by_schema %}
766
+ <div class="alert alert-warning mt-3">
767
+ <strong>Missing Modules Mandatory by Schema:</strong>
768
+ <ul class="mb-0">
769
+ {% for mod in missing_modules_by_schema %}
770
+ <li>{{ mod }}</li>
771
+ {% endfor %}
772
+ </ul>
773
+ <small class="d-block mt-2">These modules are declared in your validation schema but are not selected. They should be included for proper certification.</small>
774
+ </div>
775
+ {% endif %}
776
+ {% else %}
777
+ <p><strong>Schema Status:</strong>
778
+ <span class="badge bg-success">SCHEMA MATCHES RUN SETTINGS</span>
779
+ <small class="text-muted d-block mt-1">The uploaded schema matches the selected flavor, modules, and tests</small>
780
+ </p>
781
+ {% endif %}
782
+
783
+ </div>
784
+ </div>
785
+
786
+ <!-- Edit Links -->
787
+ <div class="mb-4">
788
+ <p>Need to make changes?</p>
789
+ <a href="{{ url_for('step', step_num=1) }}" class="btn btn-outline-secondary btn-sm me-2">Edit Flavor</a>
790
+ <a href="{{ url_for('step', step_num=2) }}" class="btn btn-outline-secondary btn-sm me-2">Edit Schema</a>
791
+ {% if flavor != 'gw_only' %}
792
+ <a href="{{ url_for('step', step_num=3) }}" class="btn btn-outline-secondary btn-sm me-2">Edit Modules</a>
793
+ {% endif %}
794
+ <a href="{{ url_for('step', step_num=4) }}" class="btn btn-outline-secondary btn-sm">Edit Tests</a>
795
+ </div>
796
+
797
+ <!-- Run Button -->
798
+ <div class="d-grid gap-2">
799
+ <button type="submit" name="action" value="run" class="btn btn-primary btn-lg">Run Certificate</button>
800
+ <div class="d-flex gap-2">
801
+ <a href="{{ url_for('export_config') }}" class="btn btn-outline-primary flex-fill">Export Run Settings</a>
802
+ <a href="{{ url_for('clear_session') }}" class="btn btn-outline-secondary flex-fill">Start New Run</a>
803
+ </div>
804
+ </div>
805
+
806
+ {% endif %}
807
+
808
+ <!-- Navigation Buttons -->
809
+ <div class="d-flex justify-content-between mt-4">
810
+ {% if step_num > 1 %}
811
+ <button type="submit" name="action" value="back" class="btn btn-secondary">← Back</button>
812
+ {% else %}
813
+ <div></div>
814
+ {% endif %}
815
+
816
+ {% if step_num < 5 %}
817
+ <button type="submit" name="action" value="next" class="btn btn-primary" id="nextBtn">Next →</button>
818
+ {% else %}
819
+ <div></div>
820
+ {% endif %}
821
+ </div>
822
+ </form>
823
+
824
+ <!-- Load Previous Run Settings -->
825
+ {% if step_num == 1 %}
826
+ <!-- Divider -->
827
+ <br>
828
+ <div class="position-relative my-4">
829
+ <hr>
830
+ <div class="position-absolute top-50 start-50 translate-middle bg-white px-3">
831
+ <span class="text-muted fw-bold">OR Use Previous Run Settings...</span>
832
+ </div>
833
+ </div>
834
+ <br>
835
+ <!-- Load Previous Run Settings -->
836
+ <div class="card mb-4">
837
+ <div class="card-header">
838
+ <h5 class="mb-0">Load Previous Run Settings</h5>
839
+ </div>
840
+ <div class="card-body">
841
+ <form method="POST" action="{{ url_for('import_config') }}" enctype="multipart/form-data">
842
+ <div class="mb-3">
843
+ <label for="config_file" class="form-label">Load Saved Run Settings (JSON)</label>
844
+ <input type="file" class="form-control" id="config_file" name="config_file" accept=".json">
845
+ <div class="form-text">Select a previously exported certificate run settings file.</div>
846
+ </div>
847
+ <button type="submit" class="btn btn-primary">Load Run Settings</button>
848
+ </form>
849
+ </div>
850
+ </div>
851
+ {% endif %}
852
+ </div>
853
+
854
+ <br><br>
855
+
856
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
857
+ <script>
858
+ // Configuration data from backend
859
+ const stepNum = {{ step_num }};
860
+ const selectedFlavor = {{ flavor|tojson }};
861
+ const mandatoryModules = {{ mandatory_modules_for_js|tojson|safe }};
862
+ const mandatoryTests = {{ mandatory_tests_for_js|tojson|safe }};
863
+ const currentSelectedModules = {{ selected_modules|tojson|safe }};
864
+ const brgCertSchema = {{ cert_schema|tojson|safe }};
865
+ const customBrokerFieldKeys = {{ custom_broker_field_keys|tojson|safe }};
866
+
867
+ // Step 3: Module selection validation
868
+ const mandatoryModulesBySchema = {{ mandatory_modules_by_schema_for_js|tojson|safe }};
869
+ function checkStep3Validation() {
870
+ if (stepNum !== 3) return;
871
+
872
+ const warningDiv = document.getElementById('step3_warning');
873
+ const warningText = document.getElementById('step3_warning_text');
874
+ if (!warningDiv || !warningText) return;
875
+
876
+ const selectedModules = Array.from(document.querySelectorAll('input[name="modules[]"]:checked'))
877
+ .map(cb => cb.value);
878
+
879
+ let warnings = [];
880
+
881
+ // Check if mandatory modules are deselected
882
+ const missingMandatory = mandatoryModules.filter(m => !selectedModules.includes(m));
883
+ if (missingMandatory.length > 0) {
884
+ warnings.push('Missing mandatory modules: ' + missingMandatory.join(', '));
885
+ }
886
+
887
+ // Check if schema-mandatory modules are deselected
888
+ if (mandatoryModulesBySchema && mandatoryModulesBySchema.length > 0) {
889
+ const missingBySchema = mandatoryModulesBySchema.filter(m => !selectedModules.includes(m));
890
+ if (missingBySchema.length > 0) {
891
+ warnings.push('Missing modules mandatory by schema: ' + missingBySchema.join(', '));
892
+ }
893
+ }
894
+
895
+ // Check Bridge/Combo requirement for at least one additional module
896
+ if (selectedFlavor === 'bridge_only' || selectedFlavor === 'combo') {
897
+ const nonMandatorySelected = selectedModules.filter(m => !mandatoryModules.includes(m));
898
+ if (nonMandatorySelected.length === 0) {
899
+ if (selectedFlavor === 'bridge_only') {
900
+ warnings.push('Bridge only requires at least one additional module beyond the mandatory Edge Management module');
901
+ } else {
902
+ warnings.push('Combo requires at least one additional bridge module beyond the mandatory Edge Management module');
903
+ }
904
+ }
905
+ }
906
+
907
+ if (warnings.length > 0) {
908
+ warningText.innerHTML = warnings.join('<br>');
909
+ warningDiv.style.display = 'block';
910
+ } else {
911
+ warningDiv.style.display = 'none';
912
+ }
913
+ }
914
+
915
+ // Step 4: Test selection validation
916
+ function checkStep4Validation() {
917
+ if (stepNum !== 4) return;
918
+
919
+ const warningDiv = document.getElementById('step4_warning');
920
+ const warningText = document.getElementById('step4_warning_text');
921
+ if (!warningDiv || !warningText) return;
922
+
923
+ const selectedTests = Array.from(document.querySelectorAll('input[name="tests[]"]:checked'))
924
+ .map(cb => cb.value);
925
+
926
+ // Use modules from backend (selected in step 3)
927
+ const selectedModules = currentSelectedModules || [];
928
+
929
+ let warnings = [];
930
+
931
+ // Check if unsterile_run is enabled
932
+ const unsterileRunField = document.querySelector('input[id*="unsterile_run"], input[name*="unsterile_run"]');
933
+ if (unsterileRunField && unsterileRunField.checked) {
934
+ warnings.push('{{ unsterile_run_warning }}');
935
+ }
936
+
937
+ // Check if mandatory modules are deselected (from step 3)
938
+ const missingMandatoryModules = mandatoryModules.filter(m => !selectedModules.includes(m));
939
+ if (missingMandatoryModules.length > 0) {
940
+ warnings.push('Missing mandatory modules: ' + missingMandatoryModules.join(', '));
941
+ }
942
+
943
+ // Check if schema-mandatory modules are deselected
944
+ if (mandatoryModulesBySchema && mandatoryModulesBySchema.length > 0) {
945
+ const missingBySchema = mandatoryModulesBySchema.filter(m => !selectedModules.includes(m));
946
+ if (missingBySchema.length > 0) {
947
+ warnings.push('Missing modules mandatory by schema: ' + missingBySchema.join(', '));
948
+ }
949
+ }
950
+
951
+ // Check if mandatory tests from selected modules are deselected
952
+ // When a module is selected, its mandatory tests become mandatory
953
+ const missingMandatoryTests = mandatoryTests
954
+ .filter(t => selectedModules.includes(t.module) && !selectedTests.includes(t.id))
955
+ .map(t => t.label + ' (' + t.module + ')');
956
+ if (missingMandatoryTests.length > 0) {
957
+ warnings.push('Missing mandatory tests from selected modules: ' + missingMandatoryTests.length + ' test(s)');
958
+ }
959
+
960
+ if (warnings.length > 0) {
961
+ warningText.innerHTML = warnings.join('<br>');
962
+ warningDiv.style.display = 'block';
963
+ } else {
964
+ warningDiv.style.display = 'none';
965
+ }
966
+ }
967
+
968
+ // Module select all / deselect all functionality
969
+ document.addEventListener('click', (e) => {
970
+ // Handle Select All button
971
+ if (e.target.matches('button[data-mod-select-all]')) {
972
+ e.preventDefault();
973
+ const modName = e.target.getAttribute('data-mod-select-all');
974
+
975
+ document.querySelectorAll(`input[type=checkbox][data-mod="${modName}"]:not([disabled])`).forEach(cb => {
976
+ cb.checked = true;
977
+ const params = document.getElementById(cb.id + '_params');
978
+ if (params) {
979
+ params.classList.remove('d-none');
980
+ // Also select all parameters for this test
981
+ const paramCheckboxes = params.querySelectorAll('input[type=checkbox][name^="params_"]');
982
+ paramCheckboxes.forEach(paramCb => {
983
+ paramCb.checked = true;
984
+ });
985
+ }
986
+ });
987
+
988
+ // Trigger validation and save
989
+ if (stepNum === 4) {
990
+ checkStep4Validation();
991
+ debouncedSave();
992
+ }
993
+ }
994
+
995
+ // Handle Deselect All button
996
+ if (e.target.matches('button[data-mod-deselect-all]')) {
997
+ e.preventDefault();
998
+ const modName = e.target.getAttribute('data-mod-deselect-all');
999
+
1000
+ document.querySelectorAll(`input[type=checkbox][data-mod="${modName}"]:not([disabled])`).forEach(cb => {
1001
+ cb.checked = false;
1002
+ const params = document.getElementById(cb.id + '_params');
1003
+ if (params) {
1004
+ params.classList.add('d-none');
1005
+ // Also deselect all parameters for this test
1006
+ const paramCheckboxes = params.querySelectorAll('input[type=checkbox][name^="params_"]');
1007
+ paramCheckboxes.forEach(paramCb => {
1008
+ paramCb.checked = false;
1009
+ });
1010
+ }
1011
+ });
1012
+
1013
+ // Trigger validation and save
1014
+ if (stepNum === 4) {
1015
+ checkStep4Validation();
1016
+ debouncedSave();
1017
+ }
1018
+ }
1019
+ });
1020
+
1021
+ document.addEventListener('change', (e) => {
1022
+ // Show/hide params panel when test checkbox toggles
1023
+ if (e.target.matches('input[type=checkbox][name="tests[]"]')) {
1024
+ const params = document.getElementById(e.target.id + '_params');
1025
+ if (params) {
1026
+ params.classList.toggle('d-none', !e.target.checked);
1027
+
1028
+ // If test is being checked, select all its parameters
1029
+ if (e.target.checked) {
1030
+ const paramCheckboxes = params.querySelectorAll('input[type=checkbox][name^="params_"]');
1031
+ paramCheckboxes.forEach(paramCb => {
1032
+ paramCb.checked = true;
1033
+ });
1034
+ }
1035
+ }
1036
+
1037
+ // Trigger validation
1038
+ if (stepNum === 4) checkStep4Validation();
1039
+ }
1040
+
1041
+ // Handle parameter checkbox changes - deselect test if all parameters are deselected
1042
+ if (e.target.matches('input[type=checkbox][name^="params_"]')) {
1043
+ // Find the test checkbox associated with this parameter
1044
+ // Parameter name format: params_{{ test_id|replace('/','_') }}[]
1045
+ const paramName = e.target.name;
1046
+ const testIdMatch = paramName.match(/^params_(.+)\[\]$/);
1047
+ if (testIdMatch) {
1048
+ const testIdBase = testIdMatch[1];
1049
+ // Find all parameter checkboxes for this test
1050
+ const allParams = document.querySelectorAll(`input[type=checkbox][name="${paramName}"]`);
1051
+ const checkedParams = document.querySelectorAll(`input[type=checkbox][name="${paramName}"]:checked`);
1052
+
1053
+ // If all parameters are unchecked, uncheck the test (including mandatory tests)
1054
+ if (checkedParams.length === 0 && allParams.length > 0) {
1055
+ // Find the test checkbox - need to match by test ID
1056
+ // Test IDs are in format like "module_test_id" or similar
1057
+ // We need to find the test checkbox that has parameters with this name pattern
1058
+ const testCheckboxes = document.querySelectorAll('input[type=checkbox][name="tests[]"]');
1059
+ testCheckboxes.forEach(testCb => {
1060
+ const testParamsDiv = document.getElementById(testCb.id + '_params');
1061
+ if (testParamsDiv) {
1062
+ // Check if this test has parameters with the matching name
1063
+ const testParams = testParamsDiv.querySelectorAll(`input[type=checkbox][name="${paramName}"]`);
1064
+ if (testParams.length > 0) {
1065
+ // This is the test that owns these parameters
1066
+ // Uncheck the test (applies to both mandatory and non-mandatory)
1067
+ testCb.checked = false;
1068
+ testParamsDiv.classList.add('d-none');
1069
+ }
1070
+ }
1071
+ });
1072
+ }
1073
+ }
1074
+
1075
+ // Trigger validation
1076
+ if (stepNum === 4) checkStep4Validation();
1077
+ }
1078
+
1079
+ // Module checkbox change
1080
+ if (e.target.matches('input[name="modules[]"]')) {
1081
+ if (stepNum === 3) checkStep3Validation();
1082
+ if (stepNum === 4) checkStep4Validation();
1083
+ }
1084
+
1085
+ // Unsterile run checkbox change
1086
+ const unsterileRunField = document.querySelector('input[id*="unsterile_run"], input[name*="unsterile_run"]');
1087
+ if (unsterileRunField && e.target === unsterileRunField) {
1088
+ // Show/hide warning and update validation
1089
+ const warningDiv = document.getElementById('step4_warning');
1090
+ const warningText = document.getElementById('step4_warning_text');
1091
+ if (warningDiv && warningText) {
1092
+ // Re-run validation to update warning
1093
+ if (stepNum === 4) checkStep4Validation();
1094
+ }
1095
+ }
1096
+ });
1097
+
1098
+ // Initialize params visibility for pre-checked tests
1099
+ (function initParamsVisibility() {
1100
+ document.querySelectorAll('input[type=checkbox][name="tests[]"]:checked').forEach(cb => {
1101
+ const params = document.getElementById(cb.id + '_params');
1102
+ if (params) params.classList.remove('d-none');
1103
+ });
1104
+ })();
1105
+
1106
+ // Step 4: Required fields validation for Next button
1107
+ function validateRequiredFieldsBeforeNext() {
1108
+ if (stepNum !== 4) return { valid: true, missing: [] };
1109
+
1110
+ const nextBtn = document.getElementById('nextBtn');
1111
+ if (!nextBtn) return { valid: true, missing: [] };
1112
+
1113
+ // Get required fields from schema (excluding validation_schema and tl)
1114
+ if (!brgCertSchema || !brgCertSchema.fields) {
1115
+ return { valid: true, missing: [] };
1116
+ }
1117
+
1118
+ const requiredFields = brgCertSchema.fields.filter(f =>
1119
+ f.required && f.name !== 'validation_schema' && f.name !== 'tl'
1120
+ );
1121
+
1122
+ const missingFields = [];
1123
+
1124
+ for (const field of requiredFields) {
1125
+ const fieldName = `${brgCertSchema.title}_${field.name}`;
1126
+ let fieldValue = null;
1127
+
1128
+ // Special handling for custom_broker - check if all required broker fields have values (like dut)
1129
+ // Username and password are optional, all other fields are required
1130
+ if (field.name === 'custom_broker') {
1131
+ const brokerFieldPrefix = `${brgCertSchema.title}_custom_broker_`;
1132
+ const optionalFields = ['username', 'password'];
1133
+ let allRequiredFieldsFilled = true;
1134
+ for (const brokerField of customBrokerFieldKeys) {
1135
+ // Skip optional fields
1136
+ if (optionalFields.includes(brokerField)) {
1137
+ continue;
1138
+ }
1139
+ const input = document.querySelector(`input[name="${brokerFieldPrefix}${brokerField}"]`);
1140
+ if (!input || !input.value || (typeof input.value === 'string' && !input.value.trim())) {
1141
+ allRequiredFieldsFilled = false;
1142
+ break;
1143
+ }
1144
+ }
1145
+ if (allRequiredFieldsFilled) {
1146
+ fieldValue = 'configured';
1147
+ }
1148
+ } else {
1149
+ // For other fields, check input/select value
1150
+ const inputs = document.querySelectorAll(`input[name="${fieldName}"], select[name="${fieldName}"]`);
1151
+ for (const input of inputs) {
1152
+ if (input.value && input.value.trim()) {
1153
+ fieldValue = input.value.trim();
1154
+ break;
1155
+ }
1156
+ }
1157
+ }
1158
+
1159
+ if (!fieldValue) {
1160
+ missingFields.push(field.name || field.label);
1161
+ }
1162
+ }
1163
+
1164
+ return {
1165
+ valid: missingFields.length === 0,
1166
+ missing: missingFields
1167
+ };
1168
+ }
1169
+
1170
+ // Intercept form submission for Next button in step 4
1171
+ const mainForm = document.getElementById('mainForm');
1172
+ if (mainForm) {
1173
+ mainForm.addEventListener('submit', function(e) {
1174
+ const action = e.submitter?.value || (new FormData(e.target)).get('action');
1175
+ if (action === 'next' && stepNum === 4) {
1176
+ const validation = validateRequiredFieldsBeforeNext();
1177
+ if (!validation.valid) {
1178
+ e.preventDefault();
1179
+ if (validation.missing.length === 1) {
1180
+ alert(`Please provide ${validation.missing[0]} to continue`);
1181
+ } else {
1182
+ alert(`Please provide the following required fields to continue: ${validation.missing.join(', ')}`);
1183
+ }
1184
+ return false;
1185
+ }
1186
+ }
1187
+ });
1188
+ }
1189
+
1190
+ // Run initial validation on page load
1191
+ if (stepNum === 3) {
1192
+ checkStep3Validation();
1193
+ } else if (stepNum === 4) {
1194
+ checkStep4Validation();
1195
+ }
1196
+
1197
+ // Show run completion modal if run was just completed
1198
+ {% if run_completed %}
1199
+ (function() {
1200
+ const terminal = {{ run_terminal|tojson }} || 'Unknown';
1201
+ const pid = {{ run_pid|tojson }};
1202
+ const isCertified = {{ run_is_certified|tojson }};
1203
+
1204
+ function showModal() {
1205
+ if (stepNum !== 5) return;
1206
+
1207
+ const modalEl = document.getElementById('runCompletionModal');
1208
+ if (!modalEl) {
1209
+ console.error('Modal element not found');
1210
+ return;
1211
+ }
1212
+
1213
+ // Update modal content
1214
+ const terminalEl = document.getElementById('runTerminal');
1215
+ const pidEl = document.getElementById('runPid');
1216
+ const pidSection = document.getElementById('pidSection');
1217
+ const warningEl = document.getElementById('testRunWarning');
1218
+
1219
+ if (terminalEl) terminalEl.textContent = terminal;
1220
+ if (pid && pidEl && pidSection) {
1221
+ pidEl.textContent = pid;
1222
+ pidSection.style.display = 'block';
1223
+ } else if (pidSection) {
1224
+ pidSection.style.display = 'none';
1225
+ }
1226
+ if (warningEl) {
1227
+ warningEl.style.display = !isCertified ? 'block' : 'none';
1228
+ }
1229
+
1230
+ // Show modal
1231
+ if (typeof bootstrap !== 'undefined') {
1232
+ const modal = new bootstrap.Modal(modalEl);
1233
+ modal.show();
1234
+ } else {
1235
+ console.error('Bootstrap not loaded');
1236
+ }
1237
+ }
1238
+
1239
+ // Wait for DOM and Bootstrap to be ready
1240
+ if (document.readyState === 'loading') {
1241
+ document.addEventListener('DOMContentLoaded', function() {
1242
+ setTimeout(showModal, 100);
1243
+ });
1244
+ } else {
1245
+ setTimeout(showModal, 100);
1246
+ }
1247
+ })();
1248
+ {% endif %}
1249
+ </script>
1250
+
1251
+ <!-- Run Completion Modal -->
1252
+ <div class="modal fade" id="runCompletionModal" tabindex="-1" aria-labelledby="runCompletionModalLabel" aria-hidden="true">
1253
+ <div class="modal-dialog modal-dialog-centered">
1254
+ <div class="modal-content">
1255
+ <div class="modal-header">
1256
+ <h5 class="modal-title" id="runCompletionModalLabel">Certificate Run Started</h5>
1257
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
1258
+ </div>
1259
+ <div class="modal-body">
1260
+ <p><strong>Terminal:</strong> <span id="runTerminal">Unknown</span></p>
1261
+ <p id="pidSection" style="display: none;"><strong>PID:</strong> <span id="runPid"></span></p>
1262
+ <div id="testRunWarning" class="alert alert-warning mt-3 mb-0" style="display: none;">
1263
+ <strong>⚠️ WARNING:</strong> This is a non-certifying run (mandatory tests/modules missing)
1264
+ </div>
1265
+ </div>
1266
+ <div class="modal-footer">
1267
+ <button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
1268
+ </div>
1269
+ </div>
1270
+ </div>
1271
+ </div>
1272
+ </body>
1273
+ </html>