wiliot-certificate 4.4.3__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.
- {brg_certificate → certificate}/ag/wlt_cmd_if.html +10 -4
- {brg_certificate → certificate}/ag/wlt_types_ag.py +1878 -519
- certificate/cert_common.py +1488 -0
- certificate/cert_config.py +480 -0
- {brg_certificate → certificate}/cert_data_sim.py +134 -46
- {brg_certificate → certificate}/cert_defines.py +129 -128
- {brg_certificate → certificate}/cert_gw_sim.py +183 -53
- {brg_certificate → certificate}/cert_mqtt.py +179 -64
- {brg_certificate → certificate}/cert_prints.py +35 -33
- {brg_certificate → certificate}/cert_protobuf.py +15 -6
- {brg_certificate → certificate}/cert_results.py +240 -64
- certificate/cert_utils.py +634 -0
- certificate/certificate.py +205 -0
- certificate/certificate_cli.py +76 -0
- certificate/certificate_eth_test_list.txt +76 -0
- certificate/certificate_sanity_test_list.txt +66 -0
- certificate/certificate_test_list.txt +76 -0
- {brg_certificate → certificate}/tests/calibration/interval_test/interval_test.json +3 -2
- {brg_certificate → certificate}/tests/calibration/interval_test/interval_test.py +7 -6
- certificate/tests/calibration/output_power_test/output_power_test.json +23 -0
- certificate/tests/calibration/output_power_test/output_power_test.py +39 -0
- {brg_certificate → certificate}/tests/calibration/pattern_test/pattern_test.json +2 -1
- {brg_certificate → certificate}/tests/calibration/pattern_test/pattern_test.py +20 -15
- certificate/tests/cloud_connectivity/acl_ext_adv_test/acl_ext_adv_test.json +15 -0
- certificate/tests/cloud_connectivity/acl_ext_adv_test/acl_ext_adv_test.py +140 -0
- certificate/tests/cloud_connectivity/acl_test/acl_test.json +15 -0
- certificate/tests/cloud_connectivity/acl_test/acl_test.py +96 -0
- certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.json +19 -0
- certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.py +41 -0
- certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.json +19 -0
- certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.py +215 -0
- certificate/tests/cloud_connectivity/connection_test/connection_test.json +18 -0
- certificate/tests/cloud_connectivity/connection_test/connection_test.py +67 -0
- certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.json +15 -0
- certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.py +80 -0
- certificate/tests/cloud_connectivity/downlink_test/downlink_test.json +21 -0
- certificate/tests/cloud_connectivity/downlink_test/downlink_test.py +201 -0
- certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.json +17 -0
- certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.py +104 -0
- certificate/tests/cloud_connectivity/reboot_test/reboot_test.json +18 -0
- certificate/tests/cloud_connectivity/reboot_test/reboot_test.py +59 -0
- certificate/tests/cloud_connectivity/registration_test/registration_test.json +20 -0
- certificate/tests/cloud_connectivity/registration_test/registration_test.py +384 -0
- certificate/tests/cloud_connectivity/registration_test/registration_test_cli.py +90 -0
- certificate/tests/cloud_connectivity/stress_test/stress_test.json +17 -0
- certificate/tests/cloud_connectivity/stress_test/stress_test.py +101 -0
- certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.json +25 -0
- certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.py +92 -0
- certificate/tests/cloud_connectivity/uplink_test/uplink_test.json +20 -0
- certificate/tests/cloud_connectivity/uplink_test/uplink_test.py +169 -0
- {brg_certificate → certificate}/tests/datapath/aging_test/aging_test.json +2 -1
- certificate/tests/datapath/aging_test/aging_test.py +142 -0
- certificate/tests/datapath/event_ble5_test/event_ble5_test.json +17 -0
- certificate/tests/datapath/event_ble5_test/event_ble5_test.py +89 -0
- certificate/tests/datapath/event_test/event_test.json +17 -0
- certificate/tests/datapath/event_test/event_test.py +80 -0
- {brg_certificate → certificate}/tests/datapath/num_of_tags_test/num_of_tags_test.json +4 -3
- {brg_certificate → certificate}/tests/datapath/num_of_tags_test/num_of_tags_test.py +19 -13
- certificate/tests/datapath/output_power_test/output_power_test.json +23 -0
- {brg_certificate → certificate}/tests/datapath/output_power_test/output_power_test.py +17 -6
- {brg_certificate → certificate}/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.json +2 -1
- {brg_certificate → certificate}/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.py +13 -11
- {brg_certificate → certificate}/tests/datapath/pacer_interval_test/pacer_interval_test.json +2 -1
- {brg_certificate → certificate}/tests/datapath/pacer_interval_test/pacer_interval_test.py +9 -7
- {brg_certificate → certificate}/tests/datapath/pattern_test/pattern_test.json +3 -2
- {brg_certificate → certificate}/tests/datapath/pattern_test/pattern_test.py +18 -6
- certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.json +20 -0
- certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.py +61 -0
- {brg_certificate → certificate}/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.json +2 -1
- {brg_certificate → certificate}/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.py +15 -14
- certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.json +19 -0
- certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.py +85 -0
- {brg_certificate → certificate}/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.json +2 -1
- {brg_certificate → certificate}/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.py +10 -9
- {brg_certificate → certificate}/tests/datapath/pkt_filter_test/pkt_filter_test.json +2 -1
- {brg_certificate → certificate}/tests/datapath/pkt_filter_test/pkt_filter_test.py +10 -9
- {brg_certificate → certificate}/tests/datapath/rssi_threshold_test/rssi_threshold_test.json +3 -2
- {brg_certificate → certificate}/tests/datapath/rssi_threshold_test/rssi_threshold_test.py +9 -8
- 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
- certificate/tests/datapath/rx_channel_hopping_test/rx_channel_hopping_test.py +77 -0
- {brg_certificate → certificate}/tests/datapath/rx_channel_test/rx_channel_test.json +3 -2
- {brg_certificate → certificate}/tests/datapath/rx_channel_test/rx_channel_test.py +7 -6
- {brg_certificate → certificate}/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.json +8 -7
- {brg_certificate → certificate}/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +113 -73
- {brg_certificate → certificate}/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.json +8 -7
- {brg_certificate → certificate}/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.py +112 -72
- {brg_certificate → certificate}/tests/datapath/stress_gen3_test/stress_gen3_test.json +4 -3
- {brg_certificate → certificate}/tests/datapath/stress_gen3_test/stress_gen3_test.py +15 -11
- {brg_certificate → certificate}/tests/datapath/stress_test/stress_test.json +4 -3
- {brg_certificate → certificate}/tests/datapath/stress_test/stress_test.py +15 -11
- {brg_certificate → certificate}/tests/datapath/tx_repetition_test/tx_repetition_test.json +3 -1
- {brg_certificate → certificate}/tests/datapath/tx_repetition_test/tx_repetition_test.py +14 -13
- certificate/tests/edge_mgmt/action_blink_test/action_blink_test.json +15 -0
- certificate/tests/edge_mgmt/action_blink_test/action_blink_test.py +24 -0
- certificate/tests/edge_mgmt/action_get_battery_sensor_test/action_get_battery_sensor_test.json +15 -0
- certificate/tests/edge_mgmt/action_get_battery_sensor_test/action_get_battery_sensor_test.py +43 -0
- certificate/tests/edge_mgmt/action_get_module_test/action_get_module_test.json +15 -0
- certificate/tests/edge_mgmt/action_get_module_test/action_get_module_test.py +42 -0
- certificate/tests/edge_mgmt/action_get_pof_data_test/action_get_pof_data_test.json +15 -0
- certificate/tests/edge_mgmt/action_get_pof_data_test/action_get_pof_data_test.py +44 -0
- certificate/tests/edge_mgmt/action_gw_hb_test/action_gw_hb_test.json +16 -0
- certificate/tests/edge_mgmt/action_gw_hb_test/action_gw_hb_test.py +42 -0
- certificate/tests/edge_mgmt/action_reboot_test/action_reboot_test.json +15 -0
- certificate/tests/edge_mgmt/action_reboot_test/action_reboot_test.py +49 -0
- certificate/tests/edge_mgmt/action_restore_defaults_test/action_restore_defaults_test.json +15 -0
- certificate/tests/edge_mgmt/action_restore_defaults_test/action_restore_defaults_test.py +102 -0
- certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.json +15 -0
- certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.py +45 -0
- {brg_certificate → certificate}/tests/edge_mgmt/periodic_msgs_test/periodic_msgs_test.json +3 -2
- {brg_certificate → certificate}/tests/edge_mgmt/periodic_msgs_test/periodic_msgs_test.py +22 -11
- {brg_certificate → certificate}/tests/energy2400/duty_cycle_test/duty_cycle_test.json +2 -1
- {brg_certificate → certificate}/tests/energy2400/duty_cycle_test/duty_cycle_test.py +7 -6
- certificate/tests/energy2400/output_power_test/output_power_test.json +23 -0
- {brg_certificate → certificate}/tests/energy2400/output_power_test/output_power_test.py +17 -6
- {brg_certificate → certificate}/tests/energy2400/pattern_test/pattern_test.json +2 -1
- {brg_certificate → certificate}/tests/energy2400/pattern_test/pattern_test.py +7 -6
- certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.json +26 -0
- certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +379 -0
- certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.json +20 -0
- certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.py +173 -0
- certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +24 -0
- certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +350 -0
- {brg_certificate → certificate}/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.json +2 -1
- {brg_certificate → certificate}/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.py +7 -6
- {brg_certificate → certificate}/tests/energy_sub1g/pattern_test/pattern_test.json +2 -1
- {brg_certificate → certificate}/tests/energy_sub1g/pattern_test/pattern_test.py +7 -6
- {brg_certificate → certificate}/tests/pwr_mgmt/pwr_mgmt_test/pwr_mgmt_test.json +2 -1
- {brg_certificate → certificate}/tests/pwr_mgmt/pwr_mgmt_test/pwr_mgmt_test.py +10 -10
- {brg_certificate → certificate}/tests/sensors/ext_sensor_test/ext_sensor_test.json +5 -4
- certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +450 -0
- certificate/wlt_types.py +122 -0
- {gw_certificate → common}/api_if/202/status.json +6 -0
- {gw_certificate → common}/api_if/203/status.json +6 -0
- {gw_certificate → common}/api_if/204/status.json +6 -0
- common/api_if/206/data.json +85 -0
- common/api_if/206/status.json +69 -0
- common/api_if/api_validation.py +91 -0
- common/web/templates/generator.html +210 -0
- common/web/templates/index.html +20 -0
- common/web/templates/menu.html +54 -0
- common/web/templates/parser.html +53 -0
- {brg_certificate/ag → common/web/templates}/wlt_types.html +1216 -191
- common/web/web_utils.py +399 -0
- {brg_certificate → common}/wltPb_pb2.py +14 -12
- {gw_certificate/common → common}/wltPb_pb2.pyi +16 -2
- gui_certificate/gui_certificate_cli.py +14 -0
- gui_certificate/server.py +1267 -0
- gui_certificate/templates/cert_run.html +1273 -0
- wiliot_certificate-4.5.0.dist-info/METADATA +99 -0
- wiliot_certificate-4.5.0.dist-info/RECORD +168 -0
- {wiliot_certificate-4.4.3.dist-info → wiliot_certificate-4.5.0.dist-info}/WHEEL +1 -1
- wiliot_certificate-4.5.0.dist-info/entry_points.txt +5 -0
- wiliot_certificate-4.5.0.dist-info/top_level.txt +3 -0
- brg_certificate/ag/energous_v0_defines.py +0 -925
- brg_certificate/ag/energous_v1_defines.py +0 -931
- brg_certificate/ag/energous_v2_defines.py +0 -925
- brg_certificate/ag/energous_v3_defines.py +0 -925
- brg_certificate/ag/energous_v4_defines.py +0 -925
- brg_certificate/ag/fanstel_lan_v0_defines.py +0 -925
- brg_certificate/ag/fanstel_lte_v0_defines.py +0 -925
- brg_certificate/ag/fanstel_wifi_v0_defines.py +0 -925
- brg_certificate/ag/minew_lte_v0_defines.py +0 -925
- brg_certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +0 -142
- brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +0 -785
- brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +0 -139
- brg_certificate/ag/wlt_types_ag_jsons/calibration.json +0 -394
- brg_certificate/ag/wlt_types_ag_jsons/custom.json +0 -515
- brg_certificate/ag/wlt_types_ag_jsons/datapath.json +0 -672
- brg_certificate/ag/wlt_types_ag_jsons/energy2400.json +0 -550
- brg_certificate/ag/wlt_types_ag_jsons/energySub1g.json +0 -595
- brg_certificate/ag/wlt_types_ag_jsons/externalSensor.json +0 -598
- brg_certificate/ag/wlt_types_ag_jsons/interface.json +0 -938
- brg_certificate/ag/wlt_types_ag_jsons/powerManagement.json +0 -1234
- brg_certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +0 -105
- brg_certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +0 -77
- brg_certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +0 -61
- brg_certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +0 -110
- brg_certificate/brg_certificate.py +0 -225
- brg_certificate/brg_certificate_cli.py +0 -63
- brg_certificate/cert_common.py +0 -923
- brg_certificate/cert_config.py +0 -402
- brg_certificate/cert_utils.py +0 -362
- brg_certificate/certificate_bcc_sanity_test_list.txt +0 -40
- brg_certificate/certificate_bcc_test_list.txt +0 -48
- brg_certificate/certificate_sanity_test_list.txt +0 -43
- brg_certificate/certificate_test_list.txt +0 -53
- brg_certificate/config/eclipse.json +0 -10
- brg_certificate/config/hivemq.json +0 -10
- brg_certificate/config/mosquitto.json +0 -10
- brg_certificate/config/mosquitto.md +0 -95
- brg_certificate/config/wiliot-dev.json +0 -10
- brg_certificate/restore_brg.py +0 -61
- brg_certificate/tests/calibration/output_power_test/output_power_test.json +0 -16
- brg_certificate/tests/calibration/output_power_test/output_power_test.py +0 -28
- brg_certificate/tests/datapath/aging_test/aging_test.py +0 -143
- brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.json +0 -16
- brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.py +0 -73
- brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.json +0 -17
- brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.py +0 -118
- brg_certificate/tests/edge_mgmt/actions_test/actions_test.json +0 -14
- brg_certificate/tests/edge_mgmt/actions_test/actions_test.py +0 -396
- brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.json +0 -20
- brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.py +0 -94
- brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.json +0 -19
- brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.py +0 -87
- brg_certificate/tests/edge_mgmt/leds_test/leds_test.json +0 -17
- brg_certificate/tests/edge_mgmt/leds_test/leds_test.py +0 -223
- brg_certificate/tests/edge_mgmt/ota_test/ota_test.json +0 -17
- brg_certificate/tests/edge_mgmt/ota_test/ota_test.py +0 -128
- brg_certificate/tests/energy2400/output_power_test/output_power_test.json +0 -16
- brg_certificate/tests/energy2400/signal_indicator_ble5_10_250k_test/signal_indicator_ble5_10_250k_test.json +0 -20
- brg_certificate/tests/energy2400/signal_indicator_ble5_10_250k_test/signal_indicator_ble5_10_250k_test.py +0 -321
- brg_certificate/tests/energy2400/signal_indicator_ble5_10_500k_test/signal_indicator_ble5_10_500k_test.json +0 -20
- brg_certificate/tests/energy2400/signal_indicator_ble5_10_500k_test/signal_indicator_ble5_10_500k_test.py +0 -321
- brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.json +0 -20
- brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.py +0 -141
- brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +0 -20
- brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +0 -276
- brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.json +0 -20
- brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.py +0 -390
- brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.json +0 -16
- brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.py +0 -28
- brg_certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +0 -305
- brg_certificate/wltPb_pb2.pyi +0 -234
- brg_certificate/wlt_types.py +0 -113
- gw_certificate/ag/ut_defines.py +0 -364
- gw_certificate/ag/wlt_types.py +0 -85
- gw_certificate/ag/wlt_types_ag.py +0 -5310
- gw_certificate/ag/wlt_types_data.py +0 -64
- gw_certificate/api/extended_api.py +0 -23
- gw_certificate/api_if/200/data.json +0 -106
- gw_certificate/api_if/200/status.json +0 -47
- gw_certificate/api_if/201/data.json +0 -98
- gw_certificate/api_if/201/status.json +0 -53
- gw_certificate/api_if/205/logs.json +0 -12
- gw_certificate/api_if/api_validation.py +0 -38
- gw_certificate/api_if/gw_capabilities.py +0 -54
- gw_certificate/cert_results.py +0 -145
- gw_certificate/common/analysis_data_bricks.py +0 -60
- gw_certificate/common/debug.py +0 -42
- gw_certificate/common/serialization_formatter.py +0 -93
- gw_certificate/common/utils.py +0 -8
- gw_certificate/common/utils_defines.py +0 -15
- gw_certificate/common/wltPb_pb2.py +0 -84
- gw_certificate/gw_certificate.py +0 -154
- gw_certificate/gw_certificate_cli.py +0 -87
- gw_certificate/interface/4.4.93_app.zip +0 -0
- gw_certificate/interface/4.4.93_sd_bl_app.zip +0 -0
- gw_certificate/interface/ble_simulator.py +0 -61
- gw_certificate/interface/ble_sniffer.py +0 -189
- gw_certificate/interface/flash_fw.py +0 -90
- gw_certificate/interface/if_defines.py +0 -36
- gw_certificate/interface/mqtt.py +0 -563
- gw_certificate/interface/nrfutil-linux +0 -0
- gw_certificate/interface/nrfutil-mac +0 -0
- gw_certificate/interface/nrfutil.exe +0 -0
- gw_certificate/interface/pkt_generator.py +0 -594
- gw_certificate/interface/uart_if.py +0 -236
- gw_certificate/interface/uart_ports.py +0 -20
- gw_certificate/templates/results.html +0 -241
- gw_certificate/templates/stage.html +0 -22
- gw_certificate/templates/table.html +0 -6
- gw_certificate/templates/test.html +0 -38
- gw_certificate/tests/__init__.py +0 -10
- gw_certificate/tests/actions.py +0 -289
- gw_certificate/tests/bad_crc_to_PER_quantization.csv +0 -51
- gw_certificate/tests/connection.py +0 -188
- gw_certificate/tests/downlink.py +0 -172
- gw_certificate/tests/generic.py +0 -238
- gw_certificate/tests/registration.py +0 -340
- gw_certificate/tests/static/__init__.py +0 -0
- gw_certificate/tests/static/connection_defines.py +0 -9
- gw_certificate/tests/static/downlink_defines.py +0 -9
- gw_certificate/tests/static/generated_packet_table.py +0 -195
- gw_certificate/tests/static/packet_table.csv +0 -10067
- gw_certificate/tests/static/references.py +0 -5
- gw_certificate/tests/static/uplink_defines.py +0 -14
- gw_certificate/tests/throughput.py +0 -240
- gw_certificate/tests/uplink.py +0 -853
- wiliot_certificate-4.4.3.dist-info/METADATA +0 -211
- wiliot_certificate-4.4.3.dist-info/RECORD +0 -210
- wiliot_certificate-4.4.3.dist-info/entry_points.txt +0 -3
- wiliot_certificate-4.4.3.dist-info/top_level.txt +0 -3
- {brg_certificate → certificate}/__init__.py +0 -0
- {gw_certificate → common}/api_if/202/data.json +0 -0
- {gw_certificate/api_if/200 → common/api_if/202}/logs.json +0 -0
- {gw_certificate → common}/api_if/203/data.json +0 -0
- {gw_certificate/api_if/201 → common/api_if/203}/logs.json +0 -0
- {gw_certificate → common}/api_if/204/data.json +0 -0
- {gw_certificate/api_if/202 → common/api_if/204}/logs.json +0 -0
- {gw_certificate → common}/api_if/205/data.json +0 -0
- {gw_certificate/api_if/203 → common/api_if/205}/logs.json +0 -0
- {gw_certificate → common}/api_if/205/status.json +0 -0
- {gw_certificate/api_if/204 → common/api_if/206}/logs.json +0 -0
- {gw_certificate → common/api_if}/__init__.py +0 -0
- {gw_certificate/api_if → gui_certificate}/__init__.py +0 -0
- {wiliot_certificate-4.4.3.dist-info → wiliot_certificate-4.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1267 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import tempfile
|
|
4
|
+
import datetime
|
|
5
|
+
from flask import Flask, render_template, request, session, redirect, url_for, flash, send_file
|
|
6
|
+
from jinja2 import ChoiceLoader, PackageLoader
|
|
7
|
+
from werkzeug.utils import secure_filename
|
|
8
|
+
from certificate.certificate_cli import CertificateCLI
|
|
9
|
+
from certificate.cert_defines import *
|
|
10
|
+
import certificate.cert_utils as cert_utils
|
|
11
|
+
import common.web.web_utils as web_utils
|
|
12
|
+
import serial.tools.list_ports
|
|
13
|
+
|
|
14
|
+
# Certificate Run Defines
|
|
15
|
+
DATA_REAL_TAGS = "REAL"
|
|
16
|
+
DATA_SIMULATION = "SIM"
|
|
17
|
+
TEST_LIST_DEFAULT_FILE = "certificate_sanity_test_list.txt"
|
|
18
|
+
PREFERRED_TERMINAL = os.getenv("CLI_TERMINAL", "auto")
|
|
19
|
+
CERT_TESTS_ROOT = os.path.join(BASE_DIR, "tests")
|
|
20
|
+
|
|
21
|
+
# Module Names
|
|
22
|
+
MODULE_CLOUD_CONNECTIVITY = "cloud_connectivity"
|
|
23
|
+
MODULE_EDGE_MGMT = "edge_mgmt"
|
|
24
|
+
|
|
25
|
+
# Flavor constants
|
|
26
|
+
FLAVOR_GW_ONLY = "gw_only"
|
|
27
|
+
FLAVOR_BRIDGE_ONLY = "bridge_only"
|
|
28
|
+
FLAVOR_COMBO = "combo"
|
|
29
|
+
|
|
30
|
+
# Upload configuration - use system temp directory
|
|
31
|
+
TEMP_BASE = tempfile.gettempdir()
|
|
32
|
+
UPLOAD_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_uploaded_schemas")
|
|
33
|
+
CONFIG_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_saved_configs")
|
|
34
|
+
TEST_LIST_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_test_lists")
|
|
35
|
+
CUSTOM_BROKER_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_custom_brokers")
|
|
36
|
+
ALLOWED_EXTENSIONS = {'json'}
|
|
37
|
+
MAX_UPLOAD_SIZE = 16 * 1024 * 1024 # 16MB
|
|
38
|
+
|
|
39
|
+
# Default custom broker configuration (from hivemq.json)
|
|
40
|
+
DEFAULT_CUSTOM_BROKER = {
|
|
41
|
+
"port": 8883,
|
|
42
|
+
"brokerUrl": "mqtts://broker.hivemq.com",
|
|
43
|
+
"username": "",
|
|
44
|
+
"password": "",
|
|
45
|
+
"ownerId": "wiliot"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
CUSTOM_BROKER_HELP = {
|
|
49
|
+
"ownerId": "Used to construct topics, e.g.: data-v2/<ownerId>/<gatewayId>"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
app = Flask(__name__)
|
|
53
|
+
app.secret_key = os.urandom(24) # Required for sessions
|
|
54
|
+
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
55
|
+
app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_SIZE
|
|
56
|
+
|
|
57
|
+
# Create folders if they don't exist
|
|
58
|
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
59
|
+
os.makedirs(CONFIG_FOLDER, exist_ok=True)
|
|
60
|
+
os.makedirs(TEST_LIST_FOLDER, exist_ok=True)
|
|
61
|
+
os.makedirs(CUSTOM_BROKER_FOLDER, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
# extend Jinja search path to include shared dir
|
|
64
|
+
app.jinja_loader = ChoiceLoader([
|
|
65
|
+
app.jinja_loader,
|
|
66
|
+
PackageLoader("common.web", "templates"),
|
|
67
|
+
])
|
|
68
|
+
# Update jinja_env globals
|
|
69
|
+
app.jinja_env.globals.update(
|
|
70
|
+
FDM=web_utils.FDM,
|
|
71
|
+
CERT_WEB=web_utils.CERT_WEB,
|
|
72
|
+
**web_utils.GLOBAL_DEFINES_TO_UPDATE
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def allowed_file(filename):
|
|
76
|
+
"""Check if file extension is allowed."""
|
|
77
|
+
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
78
|
+
|
|
79
|
+
def get_mandatory_modules_for_flavor(flavor):
|
|
80
|
+
"""Get mandatory modules for a given flavor."""
|
|
81
|
+
if flavor == FLAVOR_GW_ONLY:
|
|
82
|
+
return [MODULE_CLOUD_CONNECTIVITY]
|
|
83
|
+
elif flavor == FLAVOR_BRIDGE_ONLY:
|
|
84
|
+
return [MODULE_EDGE_MGMT] # Interface module
|
|
85
|
+
elif flavor == FLAVOR_COMBO:
|
|
86
|
+
return [MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT]
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
def get_mandatory_modules_by_schema(schema_data, flavor):
|
|
90
|
+
"""Get modules that are mandatory by schema (modules declared in schema)."""
|
|
91
|
+
if not schema_data or "modules" not in schema_data:
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
# Only apply to bridge flavors (bridge_only and combo)
|
|
95
|
+
if flavor not in (FLAVOR_BRIDGE_ONLY, FLAVOR_COMBO):
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
schema_modules = schema_data.get("modules", {})
|
|
99
|
+
if not isinstance(schema_modules, dict):
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
# Map schema module names to test module names
|
|
103
|
+
mandatory_by_schema = []
|
|
104
|
+
for schema_mod_name in schema_modules.keys():
|
|
105
|
+
# Map schema module name to test module name
|
|
106
|
+
test_mod_name = test_module_and_schema_module_to_other(schema_mod_name)
|
|
107
|
+
if test_mod_name not in mandatory_by_schema and test_mod_name != 'custom':
|
|
108
|
+
mandatory_by_schema.append(test_mod_name)
|
|
109
|
+
|
|
110
|
+
return mandatory_by_schema
|
|
111
|
+
|
|
112
|
+
# TODO: This is a temporary mapping to map test module names to schema module names - remove this
|
|
113
|
+
def test_module_and_schema_module_to_other(module_name):
|
|
114
|
+
"""Map test module names and schema module names to one another."""
|
|
115
|
+
mapping = {
|
|
116
|
+
"calibration": "calibration",
|
|
117
|
+
"datapath": "datapath",
|
|
118
|
+
"energy2400": "energy2400",
|
|
119
|
+
"energy_sub1g": "energySub1g", # snake_case to camelCase
|
|
120
|
+
"pwr_mgmt": "powerManagement", # abbreviated to full camelCase
|
|
121
|
+
"sensors": "externalSensor", # different name
|
|
122
|
+
"custom": "custom"
|
|
123
|
+
}
|
|
124
|
+
for k, v in mapping.items():
|
|
125
|
+
if module_name == k:
|
|
126
|
+
return v
|
|
127
|
+
if module_name == v:
|
|
128
|
+
return k
|
|
129
|
+
return module_name # Return as-is if not in mapping
|
|
130
|
+
|
|
131
|
+
def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
|
|
132
|
+
"""Filter modules and tests based on selected flavor."""
|
|
133
|
+
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
134
|
+
filtered_modules = []
|
|
135
|
+
|
|
136
|
+
# Get schema module order if available
|
|
137
|
+
schema_module_order = []
|
|
138
|
+
if schema_data and "modules" in schema_data:
|
|
139
|
+
schema_module_order = list(schema_data["modules"].keys())
|
|
140
|
+
|
|
141
|
+
for mod in tests_schema.get("modules", []):
|
|
142
|
+
mod_name = mod.get("name", "")
|
|
143
|
+
filtered_tests = []
|
|
144
|
+
|
|
145
|
+
for test in mod.get("tests", []):
|
|
146
|
+
test_meta = test.get("meta", {})
|
|
147
|
+
gw_only_test = test_meta.get("gwOnlyTest", 0)
|
|
148
|
+
|
|
149
|
+
# Filter tests based on flavor
|
|
150
|
+
if flavor == FLAVOR_GW_ONLY:
|
|
151
|
+
# For GW only, include tests marked as gwOnlyTest or from cloud_connectivity module
|
|
152
|
+
if gw_only_test == 1 or mod_name == MODULE_CLOUD_CONNECTIVITY:
|
|
153
|
+
filtered_tests.append(test)
|
|
154
|
+
elif flavor == FLAVOR_BRIDGE_ONLY:
|
|
155
|
+
# For Bridge only, exclude GW-only tests (but include all others)
|
|
156
|
+
if gw_only_test == 0:
|
|
157
|
+
filtered_tests.append(test)
|
|
158
|
+
elif flavor == FLAVOR_COMBO:
|
|
159
|
+
# Include all tests for combo
|
|
160
|
+
filtered_tests.append(test)
|
|
161
|
+
|
|
162
|
+
# Get modules mandatory by schema
|
|
163
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
|
|
164
|
+
is_mandatory_by_schema = mod_name in mandatory_by_schema
|
|
165
|
+
|
|
166
|
+
# Only include module if it has tests or is mandatory (by flavor or schema)
|
|
167
|
+
# For mandatory modules, include even if no tests match (they'll be shown but empty)
|
|
168
|
+
if filtered_tests or mod_name in mandatory_modules or is_mandatory_by_schema:
|
|
169
|
+
mod_copy = mod.copy()
|
|
170
|
+
mod_copy["tests"] = filtered_tests
|
|
171
|
+
mod_copy["is_mandatory"] = mod_name in mandatory_modules
|
|
172
|
+
mod_copy["is_mandatory_by_schema"] = is_mandatory_by_schema
|
|
173
|
+
filtered_modules.append(mod_copy)
|
|
174
|
+
|
|
175
|
+
# Sort modules: cloud_connectivity first, then edge_mgmt, then by schema order
|
|
176
|
+
def module_sort_key(mod):
|
|
177
|
+
mod_name = mod.get("name", "")
|
|
178
|
+
# cloud_connectivity always first
|
|
179
|
+
if mod_name == MODULE_CLOUD_CONNECTIVITY:
|
|
180
|
+
return (0, "")
|
|
181
|
+
# edge_mgmt always first among bridge modules
|
|
182
|
+
if mod_name == MODULE_EDGE_MGMT:
|
|
183
|
+
return (1, "")
|
|
184
|
+
# Then modules in schema order
|
|
185
|
+
if schema_module_order:
|
|
186
|
+
try:
|
|
187
|
+
# Map test module name to schema module name
|
|
188
|
+
schema_mod_name = test_module_and_schema_module_to_other(mod_name)
|
|
189
|
+
idx = schema_module_order.index(schema_mod_name)
|
|
190
|
+
return (2, idx)
|
|
191
|
+
except ValueError:
|
|
192
|
+
pass
|
|
193
|
+
return (3, mod_name)
|
|
194
|
+
|
|
195
|
+
filtered_modules.sort(key=module_sort_key)
|
|
196
|
+
|
|
197
|
+
return filtered_modules
|
|
198
|
+
|
|
199
|
+
def get_mandatory_tests_for_modules(modules):
|
|
200
|
+
"""Get list of mandatory test IDs from selected modules."""
|
|
201
|
+
mandatory_tests = []
|
|
202
|
+
for mod in modules:
|
|
203
|
+
for test in mod.get("tests", []):
|
|
204
|
+
if test.get("meta", {}).get("mandatory", 0) == 1:
|
|
205
|
+
mandatory_tests.append(test.get("id"))
|
|
206
|
+
return mandatory_tests
|
|
207
|
+
|
|
208
|
+
def _check_missing_defaults(properties, path_prefix=""):
|
|
209
|
+
"""
|
|
210
|
+
Recursively check if all properties have default values.
|
|
211
|
+
Returns list of field paths missing defaults.
|
|
212
|
+
Properties with nested 'properties' (object types) don't need defaults themselves,
|
|
213
|
+
only their nested properties need defaults.
|
|
214
|
+
Array types with 'items' field also don't need defaults.
|
|
215
|
+
"""
|
|
216
|
+
missing_defaults = []
|
|
217
|
+
|
|
218
|
+
if not isinstance(properties, dict):
|
|
219
|
+
return missing_defaults
|
|
220
|
+
|
|
221
|
+
for field_name, field_def in properties.items():
|
|
222
|
+
if not isinstance(field_def, dict):
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
current_path = f"{path_prefix}.{field_name}" if path_prefix else field_name
|
|
226
|
+
|
|
227
|
+
# Check if this field has nested properties
|
|
228
|
+
has_nested_properties = "properties" in field_def and isinstance(field_def["properties"], dict)
|
|
229
|
+
|
|
230
|
+
# Check if this is an array type with items
|
|
231
|
+
is_array_with_items = field_def.get("type") == "array" and "items" in field_def
|
|
232
|
+
|
|
233
|
+
# Only check for default if this property doesn't have nested properties and isn't an array with items
|
|
234
|
+
# (Objects with nested properties and arrays with items don't need defaults themselves)
|
|
235
|
+
if not has_nested_properties and not is_array_with_items and "default" not in field_def:
|
|
236
|
+
missing_defaults.append(current_path)
|
|
237
|
+
|
|
238
|
+
# Recursively check nested properties if they exist
|
|
239
|
+
if has_nested_properties:
|
|
240
|
+
nested_missing = _check_missing_defaults(field_def["properties"], current_path)
|
|
241
|
+
missing_defaults.extend(nested_missing)
|
|
242
|
+
|
|
243
|
+
return missing_defaults
|
|
244
|
+
|
|
245
|
+
def validate_schema_file(filepath):
|
|
246
|
+
"""Validate the uploaded schema file."""
|
|
247
|
+
try:
|
|
248
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
249
|
+
data = json.load(f)
|
|
250
|
+
|
|
251
|
+
# Basic validation
|
|
252
|
+
if "properties" not in data and "modules" not in data:
|
|
253
|
+
return False, "Schema missing both 'properties' and 'modules' keys"
|
|
254
|
+
|
|
255
|
+
# Check for cloud_connectivity_flag or brg_flag
|
|
256
|
+
has_cloud = cert_utils.cloud_connectivity_flag(data)
|
|
257
|
+
has_brg = cert_utils.brg_flag(data)
|
|
258
|
+
|
|
259
|
+
if not (has_cloud or has_brg):
|
|
260
|
+
return False, "Schema must support either cloud connectivity or bridge device"
|
|
261
|
+
|
|
262
|
+
# Check for missing defaults in all fields
|
|
263
|
+
missing_defaults = []
|
|
264
|
+
|
|
265
|
+
# Check top-level properties (gateway properties)
|
|
266
|
+
if "properties" in data and isinstance(data["properties"], dict):
|
|
267
|
+
missing_defaults.extend(_check_missing_defaults(data["properties"], ""))
|
|
268
|
+
|
|
269
|
+
# Check module properties (bridge modules)
|
|
270
|
+
if "modules" in data and isinstance(data["modules"], dict):
|
|
271
|
+
for module_name, module_def in data["modules"].items():
|
|
272
|
+
if isinstance(module_def, dict) and "properties" in module_def:
|
|
273
|
+
module_missing = _check_missing_defaults(module_def["properties"], f"modules.{module_name}")
|
|
274
|
+
missing_defaults.extend(module_missing)
|
|
275
|
+
|
|
276
|
+
if missing_defaults:
|
|
277
|
+
# Format error message with clear list of missing fields
|
|
278
|
+
if len(missing_defaults) == 1:
|
|
279
|
+
return False, f"Field '{missing_defaults[0]}' is missing a 'default' value"
|
|
280
|
+
else:
|
|
281
|
+
missing_list = ", ".join(missing_defaults)
|
|
282
|
+
return False, f"The following {len(missing_defaults)} fields are missing 'default' values: {missing_list}"
|
|
283
|
+
|
|
284
|
+
return True, data
|
|
285
|
+
except json.JSONDecodeError as e:
|
|
286
|
+
return False, f"Invalid JSON: {e}"
|
|
287
|
+
except Exception as e:
|
|
288
|
+
return False, f"Error reading file: {e}"
|
|
289
|
+
|
|
290
|
+
def load_schema_data(schema_path):
|
|
291
|
+
"""Load schema data from file path. Returns None if file doesn't exist or can't be read."""
|
|
292
|
+
if not schema_path:
|
|
293
|
+
return None
|
|
294
|
+
try:
|
|
295
|
+
schema_path = os.path.normpath(str(schema_path).strip().strip('"').strip("'"))
|
|
296
|
+
if os.path.exists(schema_path):
|
|
297
|
+
with open(schema_path, "r", encoding="utf-8") as f:
|
|
298
|
+
return json.load(f)
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
def verify_schema_matches_selection(schema_data, flavor, selected_modules, selected_tests):
|
|
304
|
+
"""Verify that the validation schema matches the selected flavor/modules/tests."""
|
|
305
|
+
errors = []
|
|
306
|
+
warnings = []
|
|
307
|
+
|
|
308
|
+
has_cloud = cert_utils.cloud_connectivity_flag(schema_data)
|
|
309
|
+
has_brg = cert_utils.brg_flag(schema_data)
|
|
310
|
+
|
|
311
|
+
# Check flavor compatibility
|
|
312
|
+
if flavor == FLAVOR_GW_ONLY and not has_cloud:
|
|
313
|
+
errors.append("Schema does not support Gateway (cloud connectivity) but GW only flavor selected")
|
|
314
|
+
elif flavor == FLAVOR_BRIDGE_ONLY and not has_brg:
|
|
315
|
+
errors.append("Schema does not support Bridge but Bridge only flavor selected")
|
|
316
|
+
elif flavor == FLAVOR_COMBO:
|
|
317
|
+
if not has_cloud and not has_brg:
|
|
318
|
+
errors.append("Schema does not support Gateway or Bridge for Combo flavor")
|
|
319
|
+
elif not has_cloud:
|
|
320
|
+
warnings.append("Schema missing Gateway support for Combo flavor")
|
|
321
|
+
elif not has_brg:
|
|
322
|
+
warnings.append("Schema missing Bridge support for Combo flavor")
|
|
323
|
+
|
|
324
|
+
# Check module support (if schema has modules field)
|
|
325
|
+
# The schema modules dict contains the bridge modules that the device supports
|
|
326
|
+
# Note: cloud_connectivity is a gateway feature, not a bridge module, so it won't be in schema modules
|
|
327
|
+
# Note: edge_mgmt is not a configurable module in schema
|
|
328
|
+
# Only check bridge modules that are configurable in the schema
|
|
329
|
+
if has_brg and "modules" in schema_data:
|
|
330
|
+
schema_modules = schema_data.get("modules", {})
|
|
331
|
+
if isinstance(schema_modules, dict):
|
|
332
|
+
schema_module_names = set(schema_modules.keys())
|
|
333
|
+
# Only check bridge modules that are configurable (exclude edge_mgmt and cloud_connectivity)
|
|
334
|
+
bridge_modules_to_check = [m for m in selected_modules if m not in (MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT)]
|
|
335
|
+
for test_mod_name in bridge_modules_to_check:
|
|
336
|
+
# Map test module name to schema module name
|
|
337
|
+
schema_mod_name = test_module_and_schema_module_to_other(test_mod_name)
|
|
338
|
+
if schema_mod_name not in schema_module_names:
|
|
339
|
+
warnings.append(f"Module '{test_mod_name}' may not be supported according to the uploaded schema")
|
|
340
|
+
|
|
341
|
+
return len(errors) == 0, errors, warnings
|
|
342
|
+
|
|
343
|
+
def check_certification_status(flavor, selected_modules, selected_tests, tests_schema, schema_data=None, unsterile_run=False):
|
|
344
|
+
"""Check if the selection qualifies for certification or is test-only."""
|
|
345
|
+
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
346
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
|
|
347
|
+
all_mandatory_tests = []
|
|
348
|
+
|
|
349
|
+
# Get selected module names (handle both dict and string formats)
|
|
350
|
+
selected_module_names = [m.get("name") if isinstance(m, dict) else m for m in selected_modules]
|
|
351
|
+
|
|
352
|
+
# Get all mandatory tests from ALL selected modules (not just mandatory modules)
|
|
353
|
+
# When a module is selected, its mandatory tests become mandatory
|
|
354
|
+
for mod in tests_schema.get("modules", []):
|
|
355
|
+
mod_name = mod.get("name", "")
|
|
356
|
+
if mod_name in selected_module_names:
|
|
357
|
+
for test in mod.get("tests", []):
|
|
358
|
+
if test.get("meta", {}).get("mandatory", 0) == 1:
|
|
359
|
+
all_mandatory_tests.append(test.get("id"))
|
|
360
|
+
|
|
361
|
+
# Check if all mandatory modules are selected
|
|
362
|
+
missing_modules = [m for m in mandatory_modules if m not in selected_module_names]
|
|
363
|
+
|
|
364
|
+
# Check if all schema-mandatory modules are selected
|
|
365
|
+
missing_modules_by_schema = [m for m in mandatory_by_schema if m not in selected_module_names]
|
|
366
|
+
|
|
367
|
+
# Check if all mandatory tests (from selected modules) are selected
|
|
368
|
+
missing_tests = [t for t in all_mandatory_tests if t not in selected_tests]
|
|
369
|
+
|
|
370
|
+
# Check for "at least one additional module" requirement for Bridge Only and Combo
|
|
371
|
+
missing_additional_module = False
|
|
372
|
+
if flavor in (FLAVOR_BRIDGE_ONLY, FLAVOR_COMBO):
|
|
373
|
+
# Count non-mandatory selected modules (exclude cloud_connectivity and edge_mgmt)
|
|
374
|
+
additional_modules = [m for m in selected_module_names if m not in (MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT)]
|
|
375
|
+
if len(additional_modules) == 0:
|
|
376
|
+
missing_additional_module = True
|
|
377
|
+
|
|
378
|
+
# A certifying run must be sterile - if unsterile_run is set, force non-certifying
|
|
379
|
+
is_certified = (len(missing_modules) == 0 and len(missing_modules_by_schema) == 0 and
|
|
380
|
+
len(missing_tests) == 0 and not missing_additional_module and not unsterile_run)
|
|
381
|
+
|
|
382
|
+
return is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema
|
|
383
|
+
|
|
384
|
+
def _prepare_form_data_for_template(session, cert_schema):
|
|
385
|
+
"""Prepare form_data for template, ensuring custom_broker_path is included if available."""
|
|
386
|
+
form_data = session.get('form_data', {}).copy()
|
|
387
|
+
custom_broker_path = session.get('custom_broker_path')
|
|
388
|
+
if custom_broker_path:
|
|
389
|
+
custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
|
|
390
|
+
if custom_broker_field_name not in form_data:
|
|
391
|
+
form_data[custom_broker_field_name] = custom_broker_path
|
|
392
|
+
return form_data
|
|
393
|
+
|
|
394
|
+
def form_to_argv(form, parser, title) -> list[str]:
|
|
395
|
+
"""
|
|
396
|
+
Map POSTed form fields back into argv tokens the parser expects.
|
|
397
|
+
- For flags (store_true/false): include the flag when checked.
|
|
398
|
+
- For text/select: include '--opt value' only if non-empty (keeps defaults otherwise).
|
|
399
|
+
- Special-case overwrite_defaults: accept textarea of key=value lines -> join to key=value,... string.
|
|
400
|
+
"""
|
|
401
|
+
argv: list[str] = []
|
|
402
|
+
for a in parser._actions:
|
|
403
|
+
from argparse import _HelpAction, _VersionAction, _StoreTrueAction, _StoreFalseAction
|
|
404
|
+
if isinstance(a, (_HelpAction, _VersionAction)) or a.dest in ('tl', 'validation_schema'):
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
dest = f'{title}_{a.dest}'
|
|
408
|
+
opt = a.option_strings and max(a.option_strings, key=len) # prefer long option
|
|
409
|
+
val = form.get(dest, "")
|
|
410
|
+
|
|
411
|
+
# Special handling: overwrite_defaults textarea to csv string
|
|
412
|
+
if "overwrite_defaults" in dest:
|
|
413
|
+
# allow either CSV or line-per-pair; normalize to CSV for parser type fn
|
|
414
|
+
lines = [ln.strip().replace(" ", "") for ln in form.get(dest, "").splitlines() if ln.strip()]
|
|
415
|
+
if lines:
|
|
416
|
+
val = ",".join(lines)
|
|
417
|
+
|
|
418
|
+
if getattr(a, "choices", None) and a.nargs in ("+", "*"):
|
|
419
|
+
vals = form.getlist(dest + "[]")
|
|
420
|
+
if vals:
|
|
421
|
+
argv += [opt] + vals
|
|
422
|
+
continue
|
|
423
|
+
|
|
424
|
+
# store_true / store_false
|
|
425
|
+
if isinstance(a, _StoreTrueAction):
|
|
426
|
+
if form.get(dest): # checkbox "on"
|
|
427
|
+
argv += [opt]
|
|
428
|
+
continue
|
|
429
|
+
if isinstance(a, _StoreFalseAction):
|
|
430
|
+
if form.get(dest):
|
|
431
|
+
argv += [opt]
|
|
432
|
+
continue
|
|
433
|
+
|
|
434
|
+
# Positionals (you have none) vs optionals (you have only optionals)
|
|
435
|
+
if a.option_strings:
|
|
436
|
+
if val not in ("", None):
|
|
437
|
+
argv += [opt, val]
|
|
438
|
+
else:
|
|
439
|
+
if val not in ("", None):
|
|
440
|
+
argv += [val]
|
|
441
|
+
return argv
|
|
442
|
+
|
|
443
|
+
def _sort_tests_by_module_priority(test_ids: list[str]) -> list[str]:
|
|
444
|
+
"""
|
|
445
|
+
Sort test IDs to ensure edge_mgmt tests come first (after cloud_connectivity if present).
|
|
446
|
+
Test ID format: 'module/test_name'
|
|
447
|
+
"""
|
|
448
|
+
def test_sort_key(test_id: str):
|
|
449
|
+
module, _, test_name = test_id.partition('/')
|
|
450
|
+
# Connection test first (requires manual unplug and replug)
|
|
451
|
+
if module == MODULE_CLOUD_CONNECTIVITY and "connection" in test_name.lower():
|
|
452
|
+
return (0, test_id)
|
|
453
|
+
# other cloud_connectivity tests
|
|
454
|
+
if module == MODULE_CLOUD_CONNECTIVITY:
|
|
455
|
+
return (1, test_id)
|
|
456
|
+
# edge_mgmt second
|
|
457
|
+
if module == MODULE_EDGE_MGMT:
|
|
458
|
+
return (2, test_id)
|
|
459
|
+
# Others last
|
|
460
|
+
return (3, test_id)
|
|
461
|
+
|
|
462
|
+
return sorted(test_ids, key=test_sort_key)
|
|
463
|
+
|
|
464
|
+
def _write_testlist(selected_ids: list[str], form_data=None, is_certified=False) -> str:
|
|
465
|
+
"""
|
|
466
|
+
Create a temp test list file in the temp directory. If your CLI expects script paths, write id_to_py[tid].
|
|
467
|
+
If it expects logical names, write 'module/test'. Adjust once here.
|
|
468
|
+
"""
|
|
469
|
+
# Sort tests to ensure edge_mgmt comes first
|
|
470
|
+
sorted_test_ids = _sort_tests_by_module_priority(selected_ids)
|
|
471
|
+
|
|
472
|
+
test_list = f"custom_tl_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.txt"
|
|
473
|
+
path = os.path.join(TEST_LIST_FOLDER, test_list)
|
|
474
|
+
with open(path, 'w') as f:
|
|
475
|
+
# Add comment about certification status
|
|
476
|
+
if not is_certified:
|
|
477
|
+
f.write("# NON-CERTIFYING RUN (mandatory tests/modules missing)\n")
|
|
478
|
+
for test_id in sorted_test_ids:
|
|
479
|
+
tid_safe = test_id.replace('/', '_')
|
|
480
|
+
# Check for dynamic parameter first (non-array format)
|
|
481
|
+
dynamic_param_key = f"params_{tid_safe}_dynamic"
|
|
482
|
+
dynamic_param = form_data.get(dynamic_param_key) if form_data else None
|
|
483
|
+
|
|
484
|
+
if dynamic_param is not None and dynamic_param != "":
|
|
485
|
+
# Use dynamic parameter value
|
|
486
|
+
params = [str(dynamic_param)]
|
|
487
|
+
else:
|
|
488
|
+
# Use regular parameter array
|
|
489
|
+
param_key = f"params_{tid_safe}[]"
|
|
490
|
+
params = form_data.getlist(param_key) if form_data else [] # list[str]
|
|
491
|
+
# Example line format: "<module/test> <param1> <param2> ..."
|
|
492
|
+
line = " ".join([test_id] + params)
|
|
493
|
+
f.write(line + "\n")
|
|
494
|
+
print(f"Custom test list saved in {path}")
|
|
495
|
+
return path
|
|
496
|
+
|
|
497
|
+
def _write_custom_broker_config(form_data, cert_schema_title="cert_run") -> str:
|
|
498
|
+
"""
|
|
499
|
+
Generate a custom broker JSON configuration file from form data.
|
|
500
|
+
Extracts broker field values and creates a JSON file matching hivemq.json format.
|
|
501
|
+
"""
|
|
502
|
+
# Extract broker configuration from form_data
|
|
503
|
+
broker_config = {}
|
|
504
|
+
|
|
505
|
+
# Field names follow pattern: {schema_title}_custom_broker_{field_name}
|
|
506
|
+
field_prefix = f"{cert_schema_title}_custom_broker_"
|
|
507
|
+
|
|
508
|
+
# Extract values from form_data, using defaults if not provided
|
|
509
|
+
for field in DEFAULT_CUSTOM_BROKER.keys():
|
|
510
|
+
field_name = f"{field_prefix}{field}"
|
|
511
|
+
value = form_data.get(field_name, "")
|
|
512
|
+
|
|
513
|
+
if field == "port":
|
|
514
|
+
# Convert port to int if it's a number
|
|
515
|
+
try:
|
|
516
|
+
broker_config[field] = int(value) if value else DEFAULT_CUSTOM_BROKER[field]
|
|
517
|
+
except (ValueError, TypeError):
|
|
518
|
+
broker_config[field] = DEFAULT_CUSTOM_BROKER[field]
|
|
519
|
+
elif field == "ownerId":
|
|
520
|
+
# Convert ownerId to topics
|
|
521
|
+
broker_config[CUSTOM_BROKER_UPDATE_TOPIC] = f'update/{value}/<{GW_ID}>'
|
|
522
|
+
broker_config[CUSTOM_BROKER_DATA_TOPIC] = f'data/{value}/<{GW_ID}>'
|
|
523
|
+
broker_config[CUSTOM_BROKER_STATUS_TOPIC] = f'status/{value}/<{GW_ID}>'
|
|
524
|
+
else:
|
|
525
|
+
broker_config[field] = value
|
|
526
|
+
|
|
527
|
+
# Generate filename with timestamp
|
|
528
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
529
|
+
filename = f"custom_broker_{timestamp}.json"
|
|
530
|
+
filepath = os.path.join(CUSTOM_BROKER_FOLDER, filename)
|
|
531
|
+
|
|
532
|
+
# Write JSON file
|
|
533
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
534
|
+
json.dump(broker_config, f, indent=4)
|
|
535
|
+
|
|
536
|
+
print(f"Custom broker config saved in {filepath}")
|
|
537
|
+
return filepath
|
|
538
|
+
|
|
539
|
+
@app.route("/")
|
|
540
|
+
def index():
|
|
541
|
+
"""Redirect to step 1 or show current step."""
|
|
542
|
+
if 'current_step' not in session:
|
|
543
|
+
session['current_step'] = 1
|
|
544
|
+
session['flavor'] = None
|
|
545
|
+
session['schema_path'] = None
|
|
546
|
+
session['selected_modules'] = []
|
|
547
|
+
session['selected_tests'] = []
|
|
548
|
+
session['form_data'] = {}
|
|
549
|
+
return redirect(url_for('step', step_num=session.get('current_step', 1)))
|
|
550
|
+
|
|
551
|
+
@app.route("/step/<int:step_num>", methods=['GET', 'POST'])
|
|
552
|
+
def step(step_num):
|
|
553
|
+
"""Handle multi-step wizard navigation."""
|
|
554
|
+
if step_num < 1 or step_num > 5:
|
|
555
|
+
flash("Invalid step number", "error")
|
|
556
|
+
return redirect(url_for('index'))
|
|
557
|
+
|
|
558
|
+
# Initialize session if needed
|
|
559
|
+
if 'current_step' not in session:
|
|
560
|
+
session['current_step'] = 1
|
|
561
|
+
session['flavor'] = None
|
|
562
|
+
session['schema_path'] = None
|
|
563
|
+
session['selected_modules'] = []
|
|
564
|
+
session['selected_tests'] = []
|
|
565
|
+
session['form_data'] = {}
|
|
566
|
+
session['schema_mandatory_initialized'] = False
|
|
567
|
+
|
|
568
|
+
title = "Run Certificate"
|
|
569
|
+
tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
|
|
570
|
+
error = None
|
|
571
|
+
warning = None
|
|
572
|
+
|
|
573
|
+
if request.method == "POST":
|
|
574
|
+
action = request.form.get('action', 'next')
|
|
575
|
+
|
|
576
|
+
if action == 'back':
|
|
577
|
+
# For GW only, skip step 3 when going back from step 4
|
|
578
|
+
if step_num == 4 and session.get('flavor') == FLAVOR_GW_ONLY:
|
|
579
|
+
session['current_step'] = 2
|
|
580
|
+
else:
|
|
581
|
+
session['current_step'] = max(1, step_num - 1)
|
|
582
|
+
return redirect(url_for('step', step_num=session['current_step']))
|
|
583
|
+
|
|
584
|
+
# Step 1: Flavor selection
|
|
585
|
+
if step_num == 1:
|
|
586
|
+
flavor = request.form.get('flavor')
|
|
587
|
+
if flavor in [FLAVOR_GW_ONLY, FLAVOR_BRIDGE_ONLY, FLAVOR_COMBO]:
|
|
588
|
+
session['flavor'] = flavor
|
|
589
|
+
# Initialize mandatory modules only if not already set (preserves user selections)
|
|
590
|
+
if 'selected_modules' not in session or not session.get('selected_modules'):
|
|
591
|
+
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
592
|
+
session['selected_modules'] = mandatory_modules
|
|
593
|
+
session['current_step'] = 2
|
|
594
|
+
return redirect(url_for('step', step_num=2))
|
|
595
|
+
else:
|
|
596
|
+
error = "Please select a flavor"
|
|
597
|
+
|
|
598
|
+
# Step 2: Validation schema upload
|
|
599
|
+
elif step_num == 2:
|
|
600
|
+
# Only validate file if moving forward (not going back)
|
|
601
|
+
if action == 'next':
|
|
602
|
+
if 'schema_file' in request.files:
|
|
603
|
+
file = request.files['schema_file']
|
|
604
|
+
if file and file.filename and allowed_file(file.filename):
|
|
605
|
+
filename = secure_filename(file.filename)
|
|
606
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
607
|
+
filename = f"{timestamp}_{filename}"
|
|
608
|
+
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
609
|
+
file.save(filepath)
|
|
610
|
+
|
|
611
|
+
is_valid, result = validate_schema_file(filepath)
|
|
612
|
+
if is_valid:
|
|
613
|
+
# Store normalized path without quotes (don't store full schema_data to avoid cookie size issues)
|
|
614
|
+
normalized_path = os.path.normpath(filepath)
|
|
615
|
+
session['schema_path'] = normalized_path
|
|
616
|
+
# Don't store schema_data in session - load from file when needed
|
|
617
|
+
# Initialize mandatory modules only if not already set (preserves user selections)
|
|
618
|
+
flavor = session.get('flavor')
|
|
619
|
+
if 'selected_modules' not in session or not session.get('selected_modules'):
|
|
620
|
+
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
621
|
+
# Add schema-mandatory modules
|
|
622
|
+
schema_data = result # result is the validated schema data
|
|
623
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
|
|
624
|
+
all_mandatory = list(set(mandatory_modules + mandatory_by_schema))
|
|
625
|
+
session['selected_modules'] = all_mandatory
|
|
626
|
+
# Mark schema-mandatory modules as initialized
|
|
627
|
+
session['schema_mandatory_initialized'] = True
|
|
628
|
+
else:
|
|
629
|
+
# If modules already exist, add schema-mandatory modules and mark as initialized
|
|
630
|
+
schema_data = result
|
|
631
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
|
|
632
|
+
current_modules = session.get('selected_modules', [])
|
|
633
|
+
updated_modules = list(set(current_modules + mandatory_by_schema))
|
|
634
|
+
if len(updated_modules) > len(current_modules):
|
|
635
|
+
session['selected_modules'] = updated_modules
|
|
636
|
+
session['schema_mandatory_initialized'] = True
|
|
637
|
+
# For GW only, skip step 3 (modules)
|
|
638
|
+
if session.get('flavor') == FLAVOR_GW_ONLY:
|
|
639
|
+
session['current_step'] = 4
|
|
640
|
+
return redirect(url_for('step', step_num=4))
|
|
641
|
+
else:
|
|
642
|
+
session['current_step'] = 3
|
|
643
|
+
return redirect(url_for('step', step_num=3))
|
|
644
|
+
else:
|
|
645
|
+
error = f"Schema validation failed: {result}"
|
|
646
|
+
os.remove(filepath)
|
|
647
|
+
else:
|
|
648
|
+
error = "Please upload a valid JSON schema file"
|
|
649
|
+
else:
|
|
650
|
+
error = "Please select a validation schema file"
|
|
651
|
+
|
|
652
|
+
# Step 3: Module selection
|
|
653
|
+
elif step_num == 3:
|
|
654
|
+
# Skip step 3 for GW only (shouldn't reach here, but handle gracefully)
|
|
655
|
+
if session.get('flavor') == FLAVOR_GW_ONLY:
|
|
656
|
+
mandatory_modules = get_mandatory_modules_for_flavor(FLAVOR_GW_ONLY)
|
|
657
|
+
session['selected_modules'] = mandatory_modules
|
|
658
|
+
session['current_step'] = 4
|
|
659
|
+
return redirect(url_for('step', step_num=4))
|
|
660
|
+
|
|
661
|
+
selected_modules = request.form.getlist('modules[]')
|
|
662
|
+
|
|
663
|
+
# Get mandatory modules for this flavor
|
|
664
|
+
flavor = session.get('flavor')
|
|
665
|
+
mandatory_modules = get_mandatory_modules_for_flavor(flavor) if flavor else []
|
|
666
|
+
|
|
667
|
+
# Use submitted modules directly (mandatory modules can now be deselected)
|
|
668
|
+
all_selected_modules = selected_modules
|
|
669
|
+
|
|
670
|
+
# Allow moving forward even if requirements aren't met (warnings shown via JavaScript)
|
|
671
|
+
if all_selected_modules:
|
|
672
|
+
session['selected_modules'] = all_selected_modules
|
|
673
|
+
session['current_step'] = 4
|
|
674
|
+
return redirect(url_for('step', step_num=4))
|
|
675
|
+
|
|
676
|
+
# Step 4: Test selection
|
|
677
|
+
elif step_num == 4:
|
|
678
|
+
selected_tests = request.form.getlist('tests[]')
|
|
679
|
+
|
|
680
|
+
# Get mandatory tests from selected modules
|
|
681
|
+
flavor = session.get('flavor')
|
|
682
|
+
selected_modules = session.get('selected_modules', [])
|
|
683
|
+
schema_path = session.get('schema_path')
|
|
684
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
685
|
+
filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data) if flavor else []
|
|
686
|
+
|
|
687
|
+
# Find all mandatory tests from selected modules
|
|
688
|
+
mandatory_tests = []
|
|
689
|
+
for mod in filtered_modules:
|
|
690
|
+
if mod.get('name') in selected_modules:
|
|
691
|
+
for test in mod.get('tests', []):
|
|
692
|
+
if test.get('meta', {}).get('mandatory', 0) == 1:
|
|
693
|
+
mandatory_tests.append(test.get('id'))
|
|
694
|
+
|
|
695
|
+
# Use submitted tests directly (mandatory tests can now be deselected)
|
|
696
|
+
all_selected_tests = selected_tests
|
|
697
|
+
|
|
698
|
+
# Store all form data including CLI parameters and test parameters
|
|
699
|
+
form_data_dict = {}
|
|
700
|
+
for key in request.form:
|
|
701
|
+
if key.endswith('[]'):
|
|
702
|
+
form_data_dict[key] = request.form.getlist(key)
|
|
703
|
+
else:
|
|
704
|
+
form_data_dict[key] = request.form.get(key)
|
|
705
|
+
|
|
706
|
+
# Handle custom_broker configuration from form fields
|
|
707
|
+
cert_cli = CertificateCLI()
|
|
708
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
709
|
+
custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
|
|
710
|
+
|
|
711
|
+
# Generate custom broker JSON file from form fields
|
|
712
|
+
try:
|
|
713
|
+
broker_config_path = _write_custom_broker_config(form_data_dict, cert_schema['title'])
|
|
714
|
+
normalized_path = os.path.normpath(broker_config_path)
|
|
715
|
+
session['custom_broker_path'] = normalized_path
|
|
716
|
+
form_data_dict[custom_broker_field_name] = normalized_path
|
|
717
|
+
except Exception as e:
|
|
718
|
+
error = f"Error generating custom broker configuration: {e}"
|
|
719
|
+
|
|
720
|
+
# Validate all required fields (required for moving forward, but allow going back)
|
|
721
|
+
if action == 'next':
|
|
722
|
+
# Get required fields from CLI schema
|
|
723
|
+
cert_cli = CertificateCLI()
|
|
724
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
725
|
+
required_fields = []
|
|
726
|
+
missing_required = []
|
|
727
|
+
|
|
728
|
+
# Find all required fields (excluding validation_schema and tl which are handled separately)
|
|
729
|
+
for field in cert_schema.get('fields', []):
|
|
730
|
+
if field.get('required') and field.get('name') not in ('validation_schema', 'tl'):
|
|
731
|
+
required_fields.append(field)
|
|
732
|
+
|
|
733
|
+
# Check each required field
|
|
734
|
+
for field in required_fields:
|
|
735
|
+
field_name = f"{cert_schema['title']}_{field['name']}"
|
|
736
|
+
field_value = form_data_dict.get(field_name, '')
|
|
737
|
+
|
|
738
|
+
# Special handling for custom_broker - check if all required broker fields are filled
|
|
739
|
+
if field['name'] == 'custom_broker':
|
|
740
|
+
# Check all broker fields except username and password are filled
|
|
741
|
+
broker_field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
742
|
+
optional_fields = {'username', 'password'}
|
|
743
|
+
for broker_field_key in DEFAULT_CUSTOM_BROKER.keys():
|
|
744
|
+
if broker_field_key not in optional_fields:
|
|
745
|
+
broker_field_name = f"{broker_field_prefix}{broker_field_key}"
|
|
746
|
+
broker_field_value = form_data_dict.get(broker_field_name, '')
|
|
747
|
+
if not broker_field_value or (isinstance(broker_field_value, str) and not broker_field_value.strip()):
|
|
748
|
+
missing_required.append(f"custom_broker.{broker_field_key}")
|
|
749
|
+
break
|
|
750
|
+
# Also check if path was generated (should be generated above if fields are valid)
|
|
751
|
+
if not field_value and not session.get('custom_broker_path'):
|
|
752
|
+
# Only add if we haven't already added a broker field error
|
|
753
|
+
if not any('custom_broker.' in req for req in missing_required):
|
|
754
|
+
missing_required.append(field.get('name', field['label']))
|
|
755
|
+
else:
|
|
756
|
+
# For other fields, check if value is provided
|
|
757
|
+
if not field_value or (isinstance(field_value, str) and not field_value.strip()):
|
|
758
|
+
missing_required.append(field.get('name', field['label']))
|
|
759
|
+
|
|
760
|
+
if missing_required:
|
|
761
|
+
if len(missing_required) == 1:
|
|
762
|
+
error = f"Please provide {missing_required[0]} to continue"
|
|
763
|
+
else:
|
|
764
|
+
error = f"Please provide the following required fields to continue: {', '.join(missing_required)}"
|
|
765
|
+
else:
|
|
766
|
+
# Only save and move forward if all required fields are provided
|
|
767
|
+
if all_selected_tests:
|
|
768
|
+
session['selected_tests'] = all_selected_tests
|
|
769
|
+
session['form_data'] = form_data_dict
|
|
770
|
+
session['current_step'] = 5
|
|
771
|
+
return redirect(url_for('step', step_num=5))
|
|
772
|
+
else:
|
|
773
|
+
# Going back - save form data but don't validate required fields
|
|
774
|
+
if all_selected_tests:
|
|
775
|
+
session['selected_tests'] = all_selected_tests
|
|
776
|
+
session['form_data'] = form_data_dict
|
|
777
|
+
|
|
778
|
+
# Step 5: Review and run
|
|
779
|
+
elif step_num == 5:
|
|
780
|
+
if request.form.get('action') == 'run':
|
|
781
|
+
return execute_certificate()
|
|
782
|
+
elif request.form.get('action') == 'edit':
|
|
783
|
+
edit_step = int(request.form.get('edit_step', 1))
|
|
784
|
+
session['current_step'] = edit_step
|
|
785
|
+
return redirect(url_for('step', step_num=edit_step))
|
|
786
|
+
|
|
787
|
+
# Prepare data for template
|
|
788
|
+
flavor = session.get('flavor')
|
|
789
|
+
schema_path = session.get('schema_path')
|
|
790
|
+
selected_modules = session.get('selected_modules', [])
|
|
791
|
+
selected_tests = session.get('selected_tests', [])
|
|
792
|
+
|
|
793
|
+
# Initialize mandatory items only if not already in session (new session or after "Start New Run")
|
|
794
|
+
# This ensures mandatory items are only pre-checked at the beginning
|
|
795
|
+
if flavor and not selected_modules:
|
|
796
|
+
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
797
|
+
# Add schema-mandatory modules if schema is available
|
|
798
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
799
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
|
|
800
|
+
all_mandatory = list(set(mandatory_modules + mandatory_by_schema))
|
|
801
|
+
selected_modules = all_mandatory
|
|
802
|
+
session['selected_modules'] = selected_modules
|
|
803
|
+
|
|
804
|
+
# Ensure schema-mandatory modules are added when first reaching step 3 (only if not already initialized)
|
|
805
|
+
# Use a session flag to track if schema-mandatory modules have been initialized
|
|
806
|
+
if step_num == 3 and flavor and schema_path:
|
|
807
|
+
schema_mandatory_initialized = session.get('schema_mandatory_initialized', False)
|
|
808
|
+
if not schema_mandatory_initialized:
|
|
809
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
810
|
+
if schema_data:
|
|
811
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
|
|
812
|
+
# Add schema-mandatory modules that aren't already selected
|
|
813
|
+
current_modules = session.get('selected_modules', [])
|
|
814
|
+
updated_modules = list(set(current_modules + mandatory_by_schema))
|
|
815
|
+
if len(updated_modules) > len(current_modules):
|
|
816
|
+
selected_modules = updated_modules
|
|
817
|
+
session['selected_modules'] = selected_modules
|
|
818
|
+
# Mark as initialized so we don't re-add them when going back
|
|
819
|
+
session['schema_mandatory_initialized'] = True
|
|
820
|
+
|
|
821
|
+
# Initialize mandatory tests only if not already in session and we have selected modules
|
|
822
|
+
# Only initialize when first reaching step 4 (not on every GET request)
|
|
823
|
+
if flavor and selected_modules and not selected_tests and step_num == 4:
|
|
824
|
+
schema_path = session.get('schema_path')
|
|
825
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
826
|
+
filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data) if flavor else []
|
|
827
|
+
mandatory_tests = []
|
|
828
|
+
for mod in filtered_modules:
|
|
829
|
+
if mod.get('name') in selected_modules:
|
|
830
|
+
for test in mod.get('tests', []):
|
|
831
|
+
if test.get('meta', {}).get('mandatory', 0) == 1:
|
|
832
|
+
mandatory_tests.append(test.get('id'))
|
|
833
|
+
if mandatory_tests:
|
|
834
|
+
selected_tests = mandatory_tests
|
|
835
|
+
session['selected_tests'] = selected_tests
|
|
836
|
+
|
|
837
|
+
# Skip step 3 for GW only - redirect to step 4 if trying to access step 3
|
|
838
|
+
if step_num == 3 and flavor == FLAVOR_GW_ONLY:
|
|
839
|
+
if not selected_modules:
|
|
840
|
+
mandatory_modules = get_mandatory_modules_for_flavor(FLAVOR_GW_ONLY)
|
|
841
|
+
session['selected_modules'] = mandatory_modules
|
|
842
|
+
return redirect(url_for('step', step_num=4))
|
|
843
|
+
|
|
844
|
+
# Filter modules/tests based on flavor
|
|
845
|
+
filtered_modules = []
|
|
846
|
+
if flavor:
|
|
847
|
+
schema_path = session.get('schema_path')
|
|
848
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
849
|
+
filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data)
|
|
850
|
+
|
|
851
|
+
# Get CLI schemas for form fields - use certificate_cli for all flavors (needed early for unsterile_run check)
|
|
852
|
+
cert_cli = CertificateCLI()
|
|
853
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
854
|
+
|
|
855
|
+
# Check if unsterile_run is set in form_data
|
|
856
|
+
form_data = _prepare_form_data_for_template(session, cert_schema)
|
|
857
|
+
unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
|
|
858
|
+
unsterile_run = bool(form_data.get(unsterile_run_field))
|
|
859
|
+
|
|
860
|
+
# Get certification status
|
|
861
|
+
is_certified = True
|
|
862
|
+
missing_modules = []
|
|
863
|
+
missing_tests = []
|
|
864
|
+
missing_tests_details = [] # List of dicts with test id and label
|
|
865
|
+
missing_additional_module = False
|
|
866
|
+
missing_modules_by_schema = []
|
|
867
|
+
if step_num >= 4 and flavor and selected_modules and selected_tests:
|
|
868
|
+
# Convert selected_modules to module dicts for check_certification_status
|
|
869
|
+
module_dicts = [m for m in filtered_modules if m.get('name') in selected_modules]
|
|
870
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
871
|
+
is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema = check_certification_status(
|
|
872
|
+
flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
# Get test details (labels) for missing tests
|
|
876
|
+
if missing_tests:
|
|
877
|
+
for mod in filtered_modules:
|
|
878
|
+
for test in mod.get('tests', []):
|
|
879
|
+
test_id = test.get('id')
|
|
880
|
+
if test_id in missing_tests:
|
|
881
|
+
missing_tests_details.append({
|
|
882
|
+
'id': test_id,
|
|
883
|
+
'label': test.get('label', test_id),
|
|
884
|
+
'module': mod.get('name', '')
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
# Schema verification
|
|
888
|
+
schema_errors = []
|
|
889
|
+
schema_warnings = []
|
|
890
|
+
if step_num == 5 and schema_path and flavor:
|
|
891
|
+
schema_data = load_schema_data(schema_path)
|
|
892
|
+
if schema_data:
|
|
893
|
+
module_dicts = [m for m in filtered_modules if m.get('name') in selected_modules]
|
|
894
|
+
is_valid, errors, warnings = verify_schema_matches_selection(
|
|
895
|
+
schema_data, flavor, selected_modules, selected_tests
|
|
896
|
+
)
|
|
897
|
+
schema_errors = errors
|
|
898
|
+
schema_warnings = warnings
|
|
899
|
+
# Get missing schema-mandatory modules for display (recalculate if not already set)
|
|
900
|
+
if not missing_modules_by_schema and step_num >= 4 and flavor and selected_modules:
|
|
901
|
+
_, _, _, _, missing_modules_by_schema = check_certification_status(
|
|
902
|
+
flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
# Calculate total test count including mandatory tests from selected modules
|
|
907
|
+
total_test_count = len(selected_tests)
|
|
908
|
+
if step_num >= 4 and flavor and selected_modules:
|
|
909
|
+
# Find all mandatory tests from selected modules that might not be in selected_tests
|
|
910
|
+
for mod in filtered_modules:
|
|
911
|
+
if mod.get('name') in selected_modules:
|
|
912
|
+
for test in mod.get('tests', []):
|
|
913
|
+
test_id = test.get('id')
|
|
914
|
+
if test.get('meta', {}).get('mandatory', 0) == 1 and test_id not in selected_tests:
|
|
915
|
+
total_test_count += 1
|
|
916
|
+
|
|
917
|
+
# Prepare data for JavaScript validation
|
|
918
|
+
# Always initialize these to avoid Undefined errors in template
|
|
919
|
+
mandatory_modules_for_js = get_mandatory_modules_for_flavor(flavor) if flavor else []
|
|
920
|
+
schema_data_for_js = load_schema_data(schema_path) if schema_path else None
|
|
921
|
+
mandatory_modules_by_schema_for_js = get_mandatory_modules_by_schema(schema_data_for_js, flavor) if (schema_data_for_js and flavor) else []
|
|
922
|
+
mandatory_tests_for_js = []
|
|
923
|
+
|
|
924
|
+
if step_num in [3, 4] and flavor:
|
|
925
|
+
if step_num == 4 and selected_modules:
|
|
926
|
+
# For step 4, only include mandatory tests from selected modules
|
|
927
|
+
for mod in filtered_modules:
|
|
928
|
+
if mod.get('name') in selected_modules:
|
|
929
|
+
for test in mod.get('tests', []):
|
|
930
|
+
if test.get('meta', {}).get('mandatory', 0) == 1:
|
|
931
|
+
mandatory_tests_for_js.append({
|
|
932
|
+
'id': test.get('id'),
|
|
933
|
+
'label': test.get('label', test.get('id')),
|
|
934
|
+
'module': mod.get('name', '')
|
|
935
|
+
})
|
|
936
|
+
elif step_num == 3:
|
|
937
|
+
# For step 3, include all mandatory tests from all modules (for validation)
|
|
938
|
+
for mod in filtered_modules:
|
|
939
|
+
for test in mod.get('tests', []):
|
|
940
|
+
if test.get('meta', {}).get('mandatory', 0) == 1:
|
|
941
|
+
mandatory_tests_for_js.append({
|
|
942
|
+
'id': test.get('id'),
|
|
943
|
+
'label': test.get('label', test.get('id')),
|
|
944
|
+
'module': mod.get('name', '')
|
|
945
|
+
})
|
|
946
|
+
|
|
947
|
+
# Get run completion info before clearing (for modal display)
|
|
948
|
+
run_completed = session.get('run_completed', False)
|
|
949
|
+
run_terminal = session.get('run_terminal')
|
|
950
|
+
run_pid = session.get('run_pid')
|
|
951
|
+
run_is_certified = session.get('run_is_certified')
|
|
952
|
+
|
|
953
|
+
# Clear run flags after getting them (so modal only shows once)
|
|
954
|
+
if run_completed:
|
|
955
|
+
session.pop('run_completed', None)
|
|
956
|
+
session.pop('run_terminal', None)
|
|
957
|
+
session.pop('run_pid', None)
|
|
958
|
+
session.pop('run_is_certified', None)
|
|
959
|
+
|
|
960
|
+
# Get available COM ports for port dropdown
|
|
961
|
+
available_ports = []
|
|
962
|
+
try:
|
|
963
|
+
for port, desc, hwid in sorted(serial.tools.list_ports.comports()):
|
|
964
|
+
available_ports.append({
|
|
965
|
+
'value': port,
|
|
966
|
+
'label': f"{port}: {desc} [{hwid}]"
|
|
967
|
+
})
|
|
968
|
+
except Exception:
|
|
969
|
+
# If serial tools are not available, leave empty list
|
|
970
|
+
pass
|
|
971
|
+
|
|
972
|
+
return render_template('cert_run.html',
|
|
973
|
+
app=web_utils.CERT_WEB,
|
|
974
|
+
title=title,
|
|
975
|
+
step_num=step_num,
|
|
976
|
+
flavor=flavor,
|
|
977
|
+
schema_path=schema_path,
|
|
978
|
+
selected_modules=selected_modules,
|
|
979
|
+
selected_tests=selected_tests,
|
|
980
|
+
total_test_count=total_test_count,
|
|
981
|
+
filtered_modules=filtered_modules,
|
|
982
|
+
tests_schema=tests_schema,
|
|
983
|
+
cert_schema=cert_schema,
|
|
984
|
+
is_certified=is_certified,
|
|
985
|
+
missing_modules=missing_modules,
|
|
986
|
+
missing_tests=missing_tests,
|
|
987
|
+
missing_tests_details=missing_tests_details,
|
|
988
|
+
missing_additional_module=missing_additional_module,
|
|
989
|
+
schema_errors=schema_errors,
|
|
990
|
+
schema_warnings=schema_warnings,
|
|
991
|
+
missing_modules_by_schema=missing_modules_by_schema,
|
|
992
|
+
mandatory_modules_for_js=mandatory_modules_for_js,
|
|
993
|
+
mandatory_modules_by_schema_for_js=mandatory_modules_by_schema_for_js,
|
|
994
|
+
mandatory_tests_for_js=mandatory_tests_for_js,
|
|
995
|
+
error=error,
|
|
996
|
+
warning=warning,
|
|
997
|
+
form_data=_prepare_form_data_for_template(session, cert_schema),
|
|
998
|
+
custom_broker_defaults=DEFAULT_CUSTOM_BROKER,
|
|
999
|
+
custom_broker_help=CUSTOM_BROKER_HELP,
|
|
1000
|
+
custom_broker_field_keys=list(DEFAULT_CUSTOM_BROKER.keys()),
|
|
1001
|
+
run_completed=run_completed,
|
|
1002
|
+
run_terminal=run_terminal,
|
|
1003
|
+
run_pid=run_pid,
|
|
1004
|
+
run_is_certified=run_is_certified,
|
|
1005
|
+
available_ports=available_ports,
|
|
1006
|
+
unsterile_run=unsterile_run)
|
|
1007
|
+
|
|
1008
|
+
def execute_certificate():
|
|
1009
|
+
"""Execute the certificate run based on session data."""
|
|
1010
|
+
flavor = session.get('flavor')
|
|
1011
|
+
schema_path = session.get('schema_path')
|
|
1012
|
+
selected_tests = session.get('selected_tests', [])
|
|
1013
|
+
form_data = session.get('form_data', {})
|
|
1014
|
+
|
|
1015
|
+
if not flavor or not schema_path or not selected_tests:
|
|
1016
|
+
flash("Missing required information. Please start over.", "error")
|
|
1017
|
+
return redirect(url_for('step', step_num=1))
|
|
1018
|
+
|
|
1019
|
+
# Strip any quotes that might have been added and normalize path
|
|
1020
|
+
schema_path = str(schema_path).strip().strip('"').strip("'")
|
|
1021
|
+
schema_path = os.path.normpath(schema_path)
|
|
1022
|
+
|
|
1023
|
+
# Verify schema file exists
|
|
1024
|
+
if not os.path.exists(schema_path):
|
|
1025
|
+
flash(f"Validation schema file not found: {schema_path}", "error")
|
|
1026
|
+
return redirect(url_for('step', step_num=2))
|
|
1027
|
+
|
|
1028
|
+
# Check certification status
|
|
1029
|
+
tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
|
|
1030
|
+
filtered_modules = filter_modules_by_flavor(tests_schema, flavor)
|
|
1031
|
+
module_dicts = [m for m in filtered_modules if m.get('name') in session.get('selected_modules', [])]
|
|
1032
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
1033
|
+
# Check if unsterile_run is set in form_data
|
|
1034
|
+
cert_cli = CertificateCLI()
|
|
1035
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1036
|
+
unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
|
|
1037
|
+
unsterile_run = bool(form_data.get(unsterile_run_field))
|
|
1038
|
+
is_certified, _, _, _, _ = check_certification_status(flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run)
|
|
1039
|
+
|
|
1040
|
+
full_cmd = []
|
|
1041
|
+
|
|
1042
|
+
# Create a request-like object for form_to_argv
|
|
1043
|
+
class FormDict:
|
|
1044
|
+
def __init__(self, data):
|
|
1045
|
+
self.data = data
|
|
1046
|
+
def get(self, key, default=""):
|
|
1047
|
+
return self.data.get(key, default)
|
|
1048
|
+
def getlist(self, key):
|
|
1049
|
+
val = self.data.get(key)
|
|
1050
|
+
if isinstance(val, list):
|
|
1051
|
+
return val
|
|
1052
|
+
elif val:
|
|
1053
|
+
return [val]
|
|
1054
|
+
else:
|
|
1055
|
+
return []
|
|
1056
|
+
|
|
1057
|
+
# Ensure custom_broker_path from session is in form_data if available
|
|
1058
|
+
custom_broker_path = session.get('custom_broker_path')
|
|
1059
|
+
if custom_broker_path and custom_broker_path not in form_data.values():
|
|
1060
|
+
cert_cli = CertificateCLI()
|
|
1061
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1062
|
+
custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
|
|
1063
|
+
# Only add if not already present
|
|
1064
|
+
if custom_broker_field_name not in form_data:
|
|
1065
|
+
form_data[custom_broker_field_name] = custom_broker_path
|
|
1066
|
+
|
|
1067
|
+
form_dict = FormDict(form_data)
|
|
1068
|
+
|
|
1069
|
+
cert_cli = CertificateCLI()
|
|
1070
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1071
|
+
|
|
1072
|
+
# Create test list
|
|
1073
|
+
tl = _write_testlist(selected_tests, form_dict, is_certified)
|
|
1074
|
+
|
|
1075
|
+
argv = form_to_argv(form_dict, parser=cert_cli.parser, title=cert_schema['title'])
|
|
1076
|
+
# Use absolute path and ensure it's properly formatted (already normalized above)
|
|
1077
|
+
schema_path_abs = os.path.abspath(schema_path)
|
|
1078
|
+
full_cmd = ["wlt-cert-cli", "--tl", tl, "--validation_schema", schema_path_abs] + argv
|
|
1079
|
+
# Add --non_cert_run flag if not certified
|
|
1080
|
+
if not is_certified:
|
|
1081
|
+
full_cmd.append("--non_cert_run")
|
|
1082
|
+
|
|
1083
|
+
if full_cmd:
|
|
1084
|
+
p = web_utils.open_in_terminal(full_cmd, preferred=PREFERRED_TERMINAL)
|
|
1085
|
+
pid = getattr(p, "pid", None)
|
|
1086
|
+
|
|
1087
|
+
# Store run information for popup display
|
|
1088
|
+
session['run_completed'] = True
|
|
1089
|
+
session['run_terminal'] = PREFERRED_TERMINAL
|
|
1090
|
+
session['run_pid'] = str(pid) if pid else None
|
|
1091
|
+
session['run_is_certified'] = is_certified
|
|
1092
|
+
return redirect(url_for('step', step_num=5))
|
|
1093
|
+
|
|
1094
|
+
@app.route("/export_config", methods=['GET'])
|
|
1095
|
+
def export_config():
|
|
1096
|
+
"""Export current run configuration to a JSON file."""
|
|
1097
|
+
if not session.get('flavor') or not session.get('schema_path'):
|
|
1098
|
+
flash("No configuration to export. Please complete at least step 1 and 2.", "error")
|
|
1099
|
+
return redirect(url_for('step', step_num=session.get('current_step', 1)))
|
|
1100
|
+
|
|
1101
|
+
# Extract custom broker form field values from form_data
|
|
1102
|
+
form_data = session.get('form_data', {})
|
|
1103
|
+
cert_cli = CertificateCLI()
|
|
1104
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1105
|
+
custom_broker_fields = {}
|
|
1106
|
+
field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
1107
|
+
for field_name in DEFAULT_CUSTOM_BROKER.keys():
|
|
1108
|
+
full_field_name = f"{field_prefix}{field_name}"
|
|
1109
|
+
if full_field_name in form_data:
|
|
1110
|
+
custom_broker_fields[field_name] = form_data[full_field_name]
|
|
1111
|
+
|
|
1112
|
+
# Prepare configuration data
|
|
1113
|
+
config = {
|
|
1114
|
+
'version': '1.0',
|
|
1115
|
+
'exported_at': datetime.datetime.now().isoformat(),
|
|
1116
|
+
'flavor': session.get('flavor'),
|
|
1117
|
+
'schema_path': session.get('schema_path'),
|
|
1118
|
+
'selected_modules': session.get('selected_modules', []),
|
|
1119
|
+
'selected_tests': session.get('selected_tests', []),
|
|
1120
|
+
'form_data': form_data,
|
|
1121
|
+
'schema_data': load_schema_data(session.get('schema_path')), # Load schema data from file for export
|
|
1122
|
+
'custom_broker_path': session.get('custom_broker_path'), # Include custom broker file path for backward compatibility
|
|
1123
|
+
'custom_broker_fields': custom_broker_fields # Include custom broker form field values
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
# Create filename with timestamp
|
|
1127
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
1128
|
+
flavor_name = session.get('flavor', 'unknown').replace('_', '-')
|
|
1129
|
+
filename = f"certificate_run_{flavor_name}_{timestamp}.json"
|
|
1130
|
+
filepath = os.path.join(CONFIG_FOLDER, filename)
|
|
1131
|
+
|
|
1132
|
+
# Save configuration
|
|
1133
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
1134
|
+
json.dump(config, f, indent=2, ensure_ascii=False)
|
|
1135
|
+
|
|
1136
|
+
# Send file for download
|
|
1137
|
+
return send_file(filepath, as_attachment=True, download_name=filename, mimetype='application/json')
|
|
1138
|
+
|
|
1139
|
+
@app.route("/import_config", methods=['POST'])
|
|
1140
|
+
def import_config():
|
|
1141
|
+
"""Import a previously saved run configuration."""
|
|
1142
|
+
if 'config_file' not in request.files:
|
|
1143
|
+
flash("No file selected", "error")
|
|
1144
|
+
return redirect(url_for('step', step_num=1))
|
|
1145
|
+
|
|
1146
|
+
file = request.files['config_file']
|
|
1147
|
+
if not file or not file.filename:
|
|
1148
|
+
flash("No file selected", "error")
|
|
1149
|
+
return redirect(url_for('step', step_num=1))
|
|
1150
|
+
|
|
1151
|
+
if not allowed_file(file.filename):
|
|
1152
|
+
flash("Invalid file type. Please upload a JSON file.", "error")
|
|
1153
|
+
return redirect(url_for('step', step_num=1))
|
|
1154
|
+
|
|
1155
|
+
try:
|
|
1156
|
+
# Read and parse JSON
|
|
1157
|
+
config_data = json.load(file)
|
|
1158
|
+
|
|
1159
|
+
# Validate configuration structure
|
|
1160
|
+
if 'version' not in config_data or 'flavor' not in config_data:
|
|
1161
|
+
flash("Invalid configuration file format", "error")
|
|
1162
|
+
return redirect(url_for('step', step_num=1))
|
|
1163
|
+
|
|
1164
|
+
# Restore session data
|
|
1165
|
+
session['flavor'] = config_data.get('flavor')
|
|
1166
|
+
session['schema_path'] = config_data.get('schema_path')
|
|
1167
|
+
session['selected_modules'] = config_data.get('selected_modules', [])
|
|
1168
|
+
session['selected_tests'] = config_data.get('selected_tests', [])
|
|
1169
|
+
session['form_data'] = config_data.get('form_data', {})
|
|
1170
|
+
# Don't restore schema_data to session (load from file when needed to avoid cookie size issues)
|
|
1171
|
+
# If schema_data is in config, save it back to the schema_path file if the path exists
|
|
1172
|
+
schema_path = config_data.get('schema_path')
|
|
1173
|
+
schema_data = config_data.get('schema_data')
|
|
1174
|
+
if schema_path and schema_data and os.path.exists(schema_path):
|
|
1175
|
+
# Schema file exists, no need to restore schema_data to session
|
|
1176
|
+
pass
|
|
1177
|
+
elif schema_path and schema_data:
|
|
1178
|
+
# Schema file doesn't exist but we have the data - save it
|
|
1179
|
+
try:
|
|
1180
|
+
with open(schema_path, 'w', encoding='utf-8') as f:
|
|
1181
|
+
json.dump(schema_data, f, indent=2)
|
|
1182
|
+
except Exception:
|
|
1183
|
+
pass # If we can't save, user will need to re-upload
|
|
1184
|
+
# Handle custom broker configuration
|
|
1185
|
+
custom_broker_fields = config_data.get('custom_broker_fields', {})
|
|
1186
|
+
if custom_broker_fields:
|
|
1187
|
+
# Restore broker form field values to form_data
|
|
1188
|
+
cert_cli = CertificateCLI()
|
|
1189
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1190
|
+
field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
1191
|
+
for field_name, field_value in custom_broker_fields.items():
|
|
1192
|
+
full_field_name = f"{field_prefix}{field_name}"
|
|
1193
|
+
session['form_data'][full_field_name] = field_value
|
|
1194
|
+
# Regenerate broker config file from form fields
|
|
1195
|
+
try:
|
|
1196
|
+
broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
|
|
1197
|
+
session['custom_broker_path'] = os.path.normpath(broker_config_path)
|
|
1198
|
+
except Exception:
|
|
1199
|
+
pass # If generation fails, user can reconfigure in step 4
|
|
1200
|
+
else:
|
|
1201
|
+
# Backward compatibility: try to use existing path
|
|
1202
|
+
session['custom_broker_path'] = config_data.get('custom_broker_path')
|
|
1203
|
+
|
|
1204
|
+
# Validate schema file still exists
|
|
1205
|
+
if session['schema_path'] and not os.path.exists(session['schema_path']):
|
|
1206
|
+
flash(f"Warning: Schema file not found at {session['schema_path']}. Please re-upload it in step 2.", "warning")
|
|
1207
|
+
session['current_step'] = 2
|
|
1208
|
+
# Validate custom_broker file still exists if present (or regenerate if we have fields)
|
|
1209
|
+
elif session.get('custom_broker_path') and not os.path.exists(session['custom_broker_path']):
|
|
1210
|
+
if custom_broker_fields:
|
|
1211
|
+
# Regenerate from fields if file doesn't exist
|
|
1212
|
+
try:
|
|
1213
|
+
cert_cli = CertificateCLI()
|
|
1214
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1215
|
+
broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
|
|
1216
|
+
session['custom_broker_path'] = os.path.normpath(broker_config_path)
|
|
1217
|
+
except Exception:
|
|
1218
|
+
flash(f"Warning: Custom broker configuration will be regenerated in step 4.", "warning")
|
|
1219
|
+
if session.get('current_step', 0) < 4:
|
|
1220
|
+
session['current_step'] = 4
|
|
1221
|
+
else:
|
|
1222
|
+
flash(f"Warning: Custom broker file not found at {session['custom_broker_path']}. Please reconfigure it in step 4.", "warning")
|
|
1223
|
+
if session.get('current_step', 0) < 4:
|
|
1224
|
+
session['current_step'] = 4
|
|
1225
|
+
else:
|
|
1226
|
+
# Determine appropriate step based on what's configured
|
|
1227
|
+
if session.get('selected_tests'):
|
|
1228
|
+
session['current_step'] = 5
|
|
1229
|
+
elif session.get('selected_modules'):
|
|
1230
|
+
session['current_step'] = 4
|
|
1231
|
+
elif session.get('schema_path'):
|
|
1232
|
+
if session.get('flavor') == FLAVOR_GW_ONLY:
|
|
1233
|
+
session['current_step'] = 4
|
|
1234
|
+
else:
|
|
1235
|
+
session['current_step'] = 3
|
|
1236
|
+
else:
|
|
1237
|
+
session['current_step'] = 2
|
|
1238
|
+
|
|
1239
|
+
flash("Configuration loaded successfully!", "success")
|
|
1240
|
+
return redirect(url_for('step', step_num=session['current_step']))
|
|
1241
|
+
|
|
1242
|
+
except json.JSONDecodeError:
|
|
1243
|
+
flash("Invalid JSON file", "error")
|
|
1244
|
+
return redirect(url_for('step', step_num=1))
|
|
1245
|
+
except Exception as e:
|
|
1246
|
+
flash(f"Error loading configuration: {str(e)}", "error")
|
|
1247
|
+
return redirect(url_for('step', step_num=1))
|
|
1248
|
+
|
|
1249
|
+
@app.route("/clear")
|
|
1250
|
+
def clear_session():
|
|
1251
|
+
"""Clear session and start fresh."""
|
|
1252
|
+
session.clear()
|
|
1253
|
+
# Initialize session flags
|
|
1254
|
+
session['schema_mandatory_initialized'] = False
|
|
1255
|
+
return redirect(url_for('step', step_num=1))
|
|
1256
|
+
|
|
1257
|
+
@app.route("/parser")
|
|
1258
|
+
def parser():
|
|
1259
|
+
return web_utils.utils_parser(web_utils.CERT_WEB)
|
|
1260
|
+
|
|
1261
|
+
@app.route("/generator")
|
|
1262
|
+
def generator():
|
|
1263
|
+
return web_utils.utils_generator(web_utils.CERT_WEB)
|
|
1264
|
+
|
|
1265
|
+
@app.route("/data_structs", methods=['GET', 'POST'])
|
|
1266
|
+
def data_structs():
|
|
1267
|
+
return web_utils.utils_data_structs(web_utils.CERT_WEB)
|